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_CLOSED = 'closed'
STATUS_PROPOSED = 'proposed'
## one more, 'selected' after proposed?
STATUS_PENDING = 'pending' ## 'working'
STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PROPOSED, 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
delegate :put_call, to: :strategy
delegate :long_or_short, to: :strategy
delegate :credit_or_debit, to: :strategy
belongs_to :next_strategy, class_name: 'Iro::Strategy', inverse_of: :next_position, optional: true
belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxts, 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 :nxts, 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
validates_associated :inner
belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer
validates_associated :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
outs = outs.select do |out|
out[:bidSize] + out[:askSize] > 0
end
if 'CALL' == pos.put_call
;
elsif 'PUT' == pos.put_call
outs = outs.reverse
end
## next_inner_strike
outs = outs.select do |out|
if Iro::Strategy::CREDIT == pos.credit_or_debit
if Iro::Strategy::SHORT == pos.long_or_short
## short credit call
out[:strikePrice] >= strategy.next_inner_strike
elsif Iro::Strategy::LONG == pos.long_or_short
## long credit put
out[:strikePrice] <= strategy.next_inner_strike
end
else
raise 'zz3 - @TODO: implement, debit spreads'
end
end
puts! outs[0][:strikePrice], 'after calc next_inner_strike'
puts! outs, 'outs'
## next_buffer_above_water
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
raise 'zz4 - this cannot happen'
end
end
puts! outs[0][:strikePrice], 'after calc next_buffer_above_water'
puts! outs, 'outs'
## next_inner_delta
outs = outs.select do |out|
if 'CALL' == pos.put_call
out_delta = out[:delta] rescue 1
out_delta <= strategy.next_inner_delta
elsif 'PUT' == pos.put_call
out_delta = out[:delta] rescue 0
out_delta <= strategy.next_inner_delta
else
raise 'zz5 - this cannot happen'
end
end
puts! outs[0][:strikePrice], 'after calc next_inner_delta'
puts! outs, 'outs'
inner = outs[0]
outs = outs.select do |out|
if 'CALL' == pos.put_call
out[:strikePrice] >= inner[:strikePrice].to_f + strategy.next_spread_amount
elsif 'PUT' == pos.put_call
out[:strikePrice] <= inner[:strikePrice].to_f - strategy.next_spread_amount
end
end
outer = outs[0]
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,
})
pos.autonxt.sync
pos.autonxt.save!
pos.save
return pos
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.long_or_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