app/models/iro/position.rb in iron_warbler-2.0.7.25 vs app/models/iro/position.rb in iron_warbler-2.0.7.26

- old
+ new

@@ -3,65 +3,75 @@ include Mongoid::Document include Mongoid::Timestamps include Mongoid::Paranoia store_in collection: 'iro_positions' + field :prev_gain_loss_amount, type: :float attr_accessor :next_gain_loss_amount + def prev_gain_loss_amount + out = autoprev.outer.end_price - autoprev.inner.end_price + out += inner.begin_price - outer.begin_price + end STATUS_ACTIVE = 'active' STATUS_PROPOSED = 'proposed' - STATUSES = [ nil, 'active', 'inactive', 'proposed' ] + STATUS_CLOSED = 'closed' + STATUS_PENDING = 'pending' + STATUSES = [ nil, STATUS_ACTIVE, STATUS_PROPOSED, STATUS_CLOSED, STATUS_PENDING ] field :status validates :status, presence: true scope :active, ->{ where( status: 'active' ) } belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions index({ purse_id: 1, ticker: 1 }) - belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxt - has_one :nxt, class_name: 'Iro::Position', inverse_of: :prev - belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions - def ticker - stock&.ticker || '-' - end + delegate :ticker, to: :stock belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions + field :long_or_short - # field :ticker - # validates :ticker, presence: true + def put_call + case strategy.kind + when Iro::Strategy::KIND_LONG_DEBIT_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' + end + end - belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer + belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxt, optional: true + belongs_to :autoprev, class_name: 'Iro::Position', inverse_of: :autonxt, optional: true + ## there are many of these, for viewing on the 'roll' view + has_many :nxt, class_name: 'Iro::Position', inverse_of: :prev + has_one :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev + + ## Options + belongs_to :inner, class_name: 'Iro::Option', inverse_of: :inner + belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer + accepts_nested_attributes_for :inner, :outer field :outer_strike, type: :float # validates :outer_strike, presence: true field :inner_strike, type: :float - validates :inner_strike, presence: true + # validates :inner_strike, presence: true field :expires_on validates :expires_on, presence: true field :quantity, type: :integer validates :quantity, presence: true def q; quantity; end field :begin_on - field :begin_outer_price, type: :float - field :begin_outer_delta, type: :float - field :begin_inner_price, type: :float - field :begin_inner_delta, type: :float - field :end_on - field :end_outer_price, type: :float - field :end_outer_delta, type: :float - field :end_inner_price, type: :float - field :end_inner_delta, type: :float - def begin_delta strategy.send("begin_delta_#{strategy.kind}", self) end def end_delta strategy.send("end_delta_#{strategy.kind}", self) @@ -84,11 +94,11 @@ }) update({ end_delta: out[:delta], end_price: out[:last], }) - print '_' + print '^' end def net_percent net_amount / max_gain end @@ -99,239 +109,174 @@ strategy.send("max_gain_#{strategy.kind}", self) end def max_loss # each strategy.send("max_loss_#{strategy.kind}", self) end - # def gain_loss_amount - # strategy.send("gain_loss_amount_#{strategy.kind}", self) - # end - field :next_delta, type: :float - field :next_outcome, type: :float - field :next_symbol - field :next_mark - field :next_reasons, type: :array, default: [] - field :rollp, type: :float - - - ## covered call - # def sync - # puts! [ inner_strike, expires_on, stock.ticker ], 'init sync' - # out = Tda::Option.get_quote({ - # contractType: 'CALL', - # strike: inner_strike, - # expirationDate: expires_on, - # ticker: stock.ticker, - # }) - # puts! out, 'sync' - # self.end_inner_price = ( out.bid + out.ask ) / 2 - # self.end_inner_delta = out.delta - # end - - ## long call spread - # def sync - # # puts! [ - # # [ inner_strike, expires_on, stock.ticker ], - # # [ outer_strike, expires_on, stock.ticker ], - # # ], 'init sync inner, outer' - # inner = Tda::Option.get_quote({ - # contractType: 'CALL', - # strike: inner_strike, - # expirationDate: expires_on, - # ticker: stock.ticker, - # }) - # outer = Tda::Option.get_quote({ - # contractType: 'CALL', - # strike: outer_strike, - # expirationDate: expires_on, - # ticker: stock.ticker, - # }) - # puts! [inner, outer], 'sync inner, outer' - # self.end_outer_price = ( outer.bid + outer.ask ) / 2 - # self.end_outer_delta = outer.delta - - # self.end_inner_price = ( inner.bid + inner.ask ) / 2 - # self.end_inner_delta = inner.delta - # end - def sync - put_call = Iro::Strategy::LONG == strategy.long_or_short ? 'CALL' : 'PUT' - puts! [ - [ inner_strike, expires_on, stock.ticker ], - [ outer_strike, expires_on, stock.ticker ], - ], 'init sync inner, outer' - inner = Tda::Option.get_quote({ - contractType: put_call, - strike: inner_strike, - expirationDate: expires_on, - ticker: stock.ticker, - }) - outer = Tda::Option.get_quote({ - contractType: put_call, - strike: outer_strike, - expirationDate: expires_on, - ticker: stock.ticker, - }) - puts! [inner, outer], 'sync inner, outer' - self.end_outer_price = ( outer.bid + outer.ask ) / 2 - self.end_outer_delta = outer.delta - - self.end_inner_price = ( inner.bid + inner.ask ) / 2 - self.end_inner_delta = inner.delta + inner.sync + outer.sync end - def sync_short_debit_put_spread - puts! [ - [ inner_strike, expires_on, stock.ticker ], - [ outer_strike, expires_on, stock.ticker ], - ], 'init sync inner, outer' - inner = Tda::Option.get_quote({ - contractType: 'PUT', - strike: inner_strike, - expirationDate: expires_on, - ticker: stock.ticker, - }) - outer = Tda::Option.get_quote({ - contractType: 'PUT', - strike: outer_strike, - expirationDate: expires_on, - ticker: stock.ticker, - }) - puts! [inner, outer], 'sync inner, outer' - self.end_outer_price = ( outer.bid + outer.ask ) / 2 - self.end_outer_delta = outer.delta - self.end_inner_price = ( inner.bid + inner.ask ) / 2 - self.end_inner_delta = inner.delta - end ## ## decisions ## + field :next_reasons, type: :array, default: [] + field :rollp, type: :float + + ## should_roll? def calc_rollp self.next_reasons = [] - self.next_symbol = nil - self.next_delta = nil + # self.next_symbol = nil + # self.next_delta = nil out = strategy.send( "calc_rollp_#{strategy.kind}", self ) self.rollp = out[0] self.next_reasons.push out[1] save - - # update({ - # next_delta: next_position[:delta], - # next_outcome: next_position[:mark] - end_price, - # next_symbol: next_position[:symbol], - # next_mark: next_position[:mark], - # should_rollp: out, - # # status: Iro::Position::STATE_PROPOSED, - # }) end + def calc_nxt + pos = self - ## expires_on = cc.expires_on ; nil - def can_roll? - ## only if less than 7 days left - ( expires_on.to_date - Time.now.to_date ).to_i < 7 - end - - ## strike = cc.strike ; strategy = cc.strategy ; nil - def near_below_water? - strike < current_underlying_strike + strategy.buffer_above_water - end - - - - ## 2023-03-18 _vp_ Continue. - ## 2023-03-19 _vp_ Continue. - ## 2023-08-05 _vp_ an Important method - ## - ## expires_on = cc.expires_on ; strategy = cc.strategy ; ticker = cc.ticker ; end_price = cc.end_price ; next_expires_on = cc.next_expires_on ; nil - ## - ## out.map { |p| [ p[:strikePrice], p[:delta] ] } - ## - def next_position - return @next_position if @next_position - return {} if ![ STATUS_ACTIVE, STATUS_PROPOSED ].include?( status ) - ## 7 days ahead - not configurable so far - out = Tda::Option.get_quotes({ - ticker: ticker, + outs = Tda::Option.get_quotes({ + contractType: pos.put_call, expirationDate: next_expires_on, - contractType: 'CALL', + ticker: ticker, }) + outs_bk = outs.dup - ## above_water - if strategy.buffer_above_water.present? - out = out.select do |i| - i[:strikePrice] > current_underlying_strike + strategy.buffer_above_water + # byebug + + ## strike price + outs = outs.select do |out| + out[:bidSize]+out[:askSize] > 0 + end + outs = outs.select do |out| + if Iro::Strategy::SHORT == pos.long_or_short + out[:strikePrice] > strategy.next_buffer_above_water + strategy.stock.last + elsif Iro::Strategy::LONG == pos.long_or_short + out[:strikePrice] < strategy.stock.last - strategy.next_buffer_above_water + else + throw 'zz4 - this cannot happen' end - # next_reasons.push "buffer_above_water above #{current_underlying_strike + strategy.buffer_above_water}" end + puts! outs[0][:strikePrice], 'after calc next_buffer_above_water' - if near_below_water? - msg = "Panic! climb at a loss. Skip the rest of the calculation." - next_reasons.push msg - ## @TODO: if not enough money in the purse, cannot roll? 2023-03-19 + outs = outs.select do |out| + if Iro::Strategy::SHORT == pos.long_or_short + out[:strikePrice] > strategy.next_inner_strike + elsif Iro::Strategy::LONG == pos.long_or_short + out[:strikePrice] < strategy.next_inner_strike + else + throw 'zz3 - this cannot happen' + end + end + puts! outs[0][:strikePrice], 'after calc next_inner_strike' - # byebug + ## delta + outs = outs.select do |out| + out_delta = out[:delta].abs rescue 0 + out_delta >= strategy.next_inner_delta.abs + end + puts! outs[0][:strikePrice], 'after calc next_inner_delta' - ## Take a small loss here. - prev = nil - out.each_with_index do |i, idx| - next if idx == 0 - if i[:last] < end_price - prev ||= i - end - end - out = [ prev ] + inner = outs[0] + outs = outs.select do |out| + out[:strikePrice] >= inner[:strikePrice].to_f + strategy.next_spread_amount + end + outer = outs[0] - else - ## Normal flow, making money. - ## @TODO: test! _vp_ 2023-03-19 + # byebug - ## next_min_strike - if strategy.next_min_strike.present? - out = out.select do |i| - i[:strikePrice] >= strategy.next_min_strike - end - # next_reasons.push "next_min_strike above #{strategy.next_min_strike}" - end - # json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_min_strike' + if inner && outer + o_attrs = { + expires_on: next_expires_on, + put_call: pos.put_call, + stock_id: pos.stock_id, + } + inner_ = Iro::Option.new(o_attrs.merge({ + strike: inner[:strikePrice], + begin_price: ( inner[:bid] + inner[:ask] )/2, + begin_delta: inner[:delta], + end_price: ( inner[:bid] + inner[:ask] )/2, + end_delta: inner[:delta], + })) + outer_ = Iro::Option.new(o_attrs.merge({ + strike: outer[:strikePrice], + begin_price: ( outer[:bid] + outer[:ask] )/2, + begin_delta: outer[:delta], + end_price: ( outer[:bid] + outer[:ask] )/2, + end_delta: outer[:delta], + })) + pos.autonxt ||= Iro::Position.new + pos.autonxt.update({ + prev_gain_loss_amount: 'a', + status: 'proposed', + stock: strategy.stock, + inner: inner_, + outer: outer_, + inner_strike: inner_.strike, + outer_strike: outer_.strike, + begin_on: Time.now.to_date, + expires_on: next_expires_on, + purse: purse, + strategy: strategy, + quantity: 1, + autoprev: pos, + }) - ## max_delta - if strategy.next_max_delta.present? - out = out.select do |i| - i[:delta] = 0.0 if i[:delta] == "NaN" - i[:delta] <= strategy.next_max_delta - end - # next_reasons.push "next_max_delta below #{strategy.next_max_delta}" - end - # json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_max_delta' - end + # byebug - @next_position = out[0] || {} + autonxt.sync + autonxt.save! + + else + throw 'zmq - should not happen' + end end - ## @TODO: Test this. _vp_ 2023-04-01 + + + ## ok def next_expires_on - out = expires_on.to_time + 7.days - while !out.friday? - out = out + 1.day + out = expires_on.to_datetime.next_occurring(:monday).next_occurring(:friday) + if !out.workday? + out = Time.previous_business_day(out ) end - while !out.workday? - out = out - 1.day - end return out end + ## ok + def self.long + where( long_or_short: Iro::Strategy::LONG ) + end + + ## ok + def self.short + where( long_or_short: Iro::Strategy::SHORT ) + end + def to_s out = "#{stock} (#{q}) #{expires_on.to_datetime.strftime('%b %d')} #{strategy.kind_short} [" - if outer_strike - out = out + "$#{outer_strike}->" + if Iro::Strategy::LONG == long_or_short + if outer.strike + out = out + "$#{outer.strike}->" + end + out = out + "$#{inner.strike}" + else + out = out + "$#{inner.strike}" + if outer.strike + out = out + "<-$#{outer.strike}" + end end - out = out + "$#{inner_strike}] " + out += "] " return out end end + +