class Iro::Strategy include Mongoid::Document include Mongoid::Timestamps include Mongoid::Paranoia store_in collection: 'iro_strategies' # field :slug # validates :slug, presence: true, uniqueness: true field :description LONG = 'long' SHORT = 'short' field :long_or_short, type: :string validates :long_or_short, presence: true CREDIT = 'credit' DEBIT = 'debit' field :credit_or_debit, type: :string validates :credit_or_debit, presence: true has_many :positions, class_name: 'Iro::Position', inverse_of: :strategy has_one :next_position, class_name: 'Iro::Position', inverse_of: :next_strategy belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies # has_and_belongs_to_many :purses, class_name: 'Iro::Purse', inverse_of: :strategies # KIND_COVERED_CALL = 'covered_call' # KIND_IRON_CONDOR = 'iron_condor' # KIND_LONG_CREDIT_PUT_SPREAD = 'long_credit_put_spread' # KIND_LONG_DEBIT_CALL_SPREAD = 'long_debit_call_spread' # KIND_SHORT_CREDIT_CALL_SPREAD = 'short_credit_call_spread' # KIND_SHORT_DEBIT_PUT_SPREAD = 'short_debit_put_spread' # KINDS = [ nil, # KIND_COVERED_CALL, # KIND_IRON_CONDOR, # KIND_LONG_CREDIT_PUT_SPREAD, # KIND_LONG_DEBIT_CALL_SPREAD, # KIND_SHORT_CREDIT_CALL_SPREAD, # KIND_SHORT_DEBIT_PUT_SPREAD, # ]; KIND_SPREAD = 'spread' KIND_WHEEL = 'wheel' field :kind def put_call case kind # when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD # put_call = 'PUT' # when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD # put_call = 'CALL' # when Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD # put_call = 'CALL' # when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD # put_call = 'PUT' # when Iro::Strategy::KIND_COVERED_CALL # put_call = 'CALL' when Iro::Strategy::KIND_SPREAD if credit_or_debit == CREDIT if long_or_short == LONG 'PUT' elsif long_or_short == SHORT 'CALL' else throw 'zz5 - should never happen' end else throw 'zz6 - debit spreads are not implemented' end when Iro::Strategy::KIND_WHEEL 'CALL' else throw 'zz9 - this should never happen' end end field :threshold_buffer_above_water, type: :float field :threshold_delta, type: :float field :threshold_netp, type: :float field :threshold_dte, type: :integer, default: 1 field :next_inner_delta, type: :float field :next_inner_strike, type: :float field :next_outer_delta, type: :float field :next_outer_strike, type: :float field :next_spread_amount, type: :float # e.g. $20 for a $2000 NVDA spread field :next_buffer_above_water, type: :float def begin_delta_wheel p p.inner.begin_delta end def begin_delta_spread p p.inner.begin_delta - p.outer.begin_delta end def breakeven_covered_call p p.inner.strike + p.inner.begin_price end def breakeven_long_debit_call_spread p p.inner.strike - p.max_gain end alias_method :breakeven_short_debit_put_spread, :breakeven_long_debit_call_spread def end_delta_wheel p p.inner.end_delta end def end_delta_spread p p.inner.end_delta - p.outer.end_delta end # def max_gain_covered_call p # p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really? # end # # def max_gain_long_credit_put_spread p # ## 100 * disallowed for gameui # p.inner.begin_price - p.outer.begin_price # end # def max_gain_long_debit_call_spread p # ## 100 * disallowed for gameui # ( p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66 # end # def max_gain_short_credit_call_spread p # p.inner.begin_price - p.outer.begin_price # end # def max_gain_short_debit_put_spread p # ## 100 * disallowed for gameui # ( p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66 # end def max_gain_spread p ## 100 * disallowed for gameui ( p.outer.strike - p.inner.strike ).abs - p.outer.begin_price + p.inner.begin_price # - 2*0.66 end def max_gain_wheel p p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really? end # def max_loss_covered_call p # p.inner.begin_price*10 # just suppose 10,000% # end # def max_loss_long_credit_put_spread p # out = p.inner.strike - p.outer.strike # end # def max_loss_long_debit_call_spread p # out = p.outer.strike - p.inner.strike # end # def max_loss_short_debit_put_spread p # different # out = p.inner.strike - p.outer.strike # end # def max_loss_short_credit_call_spread p # out = p.outer.strike - p.inner.strike # end def max_loss_spread p ( p.outer.strike - p.inner.strike ).abs end def max_loss_wheel p p.inner.begin_price*10 # just suppose 10,000% end def net_amount_spread p p.inner.begin_price - p.inner.end_price end ## 2024-05-09 @TODO def next_inner_strike_on expires_on outs = Tda::Option.get_quotes({ contractType: put_call, expirationDate: expires_on, ticker: stock.ticker, }) end ## ## decisions ## def calc_rollp_covered_call p if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1 return [ 0.99, '0 DTE, must exit' ] end if ( stock.last - buffer_above_water ) < p.inner.strike return [ 0.98, "Last #{'%.2f' % stock.last} is " + "#{'%.2f' % [p.inner.strike + buffer_above_water - stock.last]} " + "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ] end if p.inner.end_delta < threshold_delta return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ] end if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit." ] end return [ 0.33, '-' ] end ## @TODO def calc_rollp_long_debit_call_spread p if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1 return [ 0.99, '0 DTE, must exit' ] end if ( p.expires_on.to_date - Time.now.to_date ).to_i < 2 return [ 0.99, '1 DTE, must exit' ] end if ( stock.last - buffer_above_water ) < p.inner.strike return [ 0.95, "Last #{'%.2f' % stock.last} is " + "#{'%.2f' % [stock.last - p.inner.strike - buffer_above_water]} " + "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ] end if p.inner.end_delta < threshold_delta return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ] end if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ] end return [ 0.33, '-' ] end ## @TODO def calc_rollp_short_debit_put_spread p if ( p.expires_on.to_date - Time.now.to_date ).to_i <= min_dte return [ 0.99, "< #{min_dte}DTE, must exit" ] end if stock.last + buffer_above_water > p.inner.strike return [ 0.98, "Last #{'%.2f' % stock.last} is " + "#{'%.2f' % [stock.last + buffer_above_water - p.inner.strike]} " + "above #{'%.2f' % [p.inner.strike - buffer_above_water]} water" ] end if p.inner.end_delta.abs < threshold_delta.abs return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ] end if p.net_percent > threshold_netp return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ] end return [ 0.33, '-' ] end ## scopes def self.for_ticker ticker where( ticker: ticker ) end def slug "#{long_or_short} #{credit_or_debit} #{kind} #{stock}" end def to_s slug end def self.list long_or_short = nil these = long_or_short ? where( long_or_short: long_or_short ) : all [[nil,nil]] + these.map { |ttt| [ ttt, ttt.id ] } end end