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
+
+