class Iro::Position 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' 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 :stock, class_name: 'Iro::Stock', inverse_of: :positions delegate :ticker, to: :stock belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions field :long_or_short 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 :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 field :expires_on validates :expires_on, presence: true field :quantity, type: :integer validates :quantity, presence: true def q; quantity; end field :begin_on field :end_on def begin_delta strategy.send("begin_delta_#{strategy.kind}", self) end def end_delta strategy.send("end_delta_#{strategy.kind}", self) end def breakeven strategy.send("breakeven_#{strategy.kind}", self) end def current_underlying_strike Iro::Stock.find_by( ticker: ticker ).last end def refresh out = Tda::Option.get_quote({ contractType: 'CALL', strike: strike, expirationDate: expires_on, ticker: ticker, }) update({ end_delta: out[:delta], end_price: out[:last], }) print '^' end def net_percent net_amount / max_gain end def net_amount # each strategy.send("net_amount_#{strategy.kind}", self) end def max_gain # each strategy.send("max_gain_#{strategy.kind}", self) end def max_loss # each strategy.send("max_loss_#{strategy.kind}", self) end def sync inner.sync outer.sync 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 out = strategy.send( "calc_rollp_#{strategy.kind}", self ) self.rollp = out[0] self.next_reasons.push out[1] save end def calc_nxt pos = self ## 7 days ahead - not configurable so far outs = Tda::Option.get_quotes({ contractType: pos.put_call, expirationDate: next_expires_on, ticker: ticker, }) outs_bk = outs.dup # 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 end puts! outs[0][:strikePrice], 'after calc next_buffer_above_water' 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' ## 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' inner = outs[0] outs = outs.select do |out| out[:strikePrice] >= inner[:strikePrice].to_f + strategy.next_spread_amount end outer = outs[0] # byebug 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, }) # byebug autonxt.sync autonxt.save! else throw 'zmq - should not happen' end end ## ok def next_expires_on out = expires_on.to_datetime.next_occurring(:monday).next_occurring(:friday) if !out.workday? out = Time.previous_business_day(out ) 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 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 += "] " return out end end