Class: Iro::Position
- Inherits:
-
Object
- Object
- Iro::Position
- Includes:
- Mongoid::Document, Mongoid::Paranoia, Mongoid::Timestamps
- Defined in:
- app/models/iro/position.rb
Constant Summary collapse
- STATUS_ACTIVE =
'active'- STATUS_CLOSED =
'closed'- STATUS_PREPARE =
'prepare'- STATUS_PROPOSED =
'proposed'- STATUS_PENDING =
one more, ‘selected’ after proposed?
'pending'- STATUSES =
‘working’
[ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PREPARE, STATUS_PROPOSED, STATUS_PENDING ]
- INTENT_CLOSE =
'close.'- INTENTS =
[ nil, INTENT_CLOSE ]
Class Method Summary collapse
-
.long ⇒ Object
ok.
-
.short ⇒ Object
ok.
Instance Method Summary collapse
-
#autonxt ⇒ Object
2026-02-26 using this one.
- #begin_delta ⇒ Object
- #breakeven ⇒ Object
- #breakeven_covered_call ⇒ Object
-
#breakeven_long_credit_put_spread ⇒ Object
2026-02-23.
- #breakeven_long_debit_call_spread ⇒ Object
-
#breakeven_short_credit_call_spread ⇒ Object
2026-02-23.
- #calc_nxt ⇒ Object
-
#calc_rollp ⇒ Object
should_roll?.
-
#close_price ⇒ Object
credit spread only.
- #current_underlying_strike ⇒ Object
- #end_delta ⇒ Object
-
#inner ⇒ Object
Options.
-
#inner_strike ⇒ Object
2026-02-24 only to make finding easier.
-
#max_gain ⇒ Object
each.
-
#max_loss ⇒ Object
each.
-
#net_amount ⇒ Object
each.
- #net_amount_covered_call ⇒ Object
-
#net_amount_long_credit_put_spread ⇒ Object
2025-10-14 tested.
-
#net_amount_short_credit_call_spread ⇒ Object
2026-02-19 tested.
- #net_percent ⇒ Object
-
#next_expires_on ⇒ Object
ok.
-
#next_reasons ⇒ Object
decisions.
-
#outer_strike ⇒ Object
2026-02-24 only to make finding easier.
-
#place2_price ⇒ Object
place2 = credit-spread.
-
#prev ⇒ Object
there are many of these, for viewing on the ‘roll’ view.
- #put_call ⇒ Object
- #q ⇒ Object
- #refresh ⇒ Object
- #roll_price ⇒ Object
- #status_label(st) ⇒ Object
- #sync ⇒ Object
- #ticker ⇒ Object
- #to_s ⇒ Object
Class Method Details
Instance Method Details
#autonxt ⇒ Object
2026-02-26 using this one.
64 |
# File 'app/models/iro/position.rb', line 64 belongs_to :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev, optional: true |
#begin_delta ⇒ Object
98 99 100 |
# File 'app/models/iro/position.rb', line 98 def begin_delta strategy.send("begin_delta_#{strategy.kind}", self) end |
#breakeven ⇒ Object
105 106 107 |
# File 'app/models/iro/position.rb', line 105 def breakeven send("breakeven_#{strategy.kind}") end |
#breakeven_covered_call ⇒ Object
108 109 110 111 |
# File 'app/models/iro/position.rb', line 108 def breakeven_covered_call p = self p.inner.strike + p.inner.begin_price end |
#breakeven_long_credit_put_spread ⇒ Object
2026-02-23
122 123 124 125 |
# File 'app/models/iro/position.rb', line 122 def breakeven_long_credit_put_spread p = self p.inner.strike - p.max_gain end |
#breakeven_long_debit_call_spread ⇒ Object
112 113 114 115 |
# File 'app/models/iro/position.rb', line 112 def breakeven_long_debit_call_spread p = self p.inner.strike - p.max_gain end |
#breakeven_short_credit_call_spread ⇒ Object
2026-02-23
117 118 119 120 |
# File 'app/models/iro/position.rb', line 117 def breakeven_short_credit_call_spread p = self p.inner.strike + p.max_gain end |
#calc_nxt ⇒ Object
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'app/models/iro/position.rb', line 233 def calc_nxt pos = self puts! pos, '#calc_nxt...' ## 7 days ahead - not configurable params = { contractType: pos.put_call, expirationDate: next_expires_on, ticker: ticker, } puts! params, 'ze params' outs = Tda::Option.get_quotes(params) puts! outs, 'outs' 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 # puts! outs, '#calc_nxt.outs -> 2' ## next_inner_strike if strategy.next_inner_strike.present? 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 'zt3 - @TODO: implement, debit spreads' end end puts! outs[0][:strikePrice], 'after calc next_inner_strike' # puts! outs, 'outs' end ## next_usd_above_mark outs = outs.select do |out| if Iro::Strategy::SHORT == pos.long_or_short out[:strikePrice] > strategy.next_usd_above_mark + strategy.stock.last elsif Iro::Strategy::LONG == pos.long_or_short out[:strikePrice] < strategy.stock.last - strategy.next_usd_above_mark else raise 'zt4 - this cannot happen' end end puts! outs[0][:strikePrice], 'after calc next_usd_above_mark' 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 'zt5 - 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_attrs = 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_attrs = 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], }) autonxt_attrs = { put_call: pos.put_call, status: 'proposed', stock: strategy.stock, inner_strike: inner_attrs[:strike], inner_attributes: inner_attrs, outer_strike: outer_attrs[:strike], outer_attributes: outer_attrs, begin_on: Time.now.to_date, expires_on: next_expires_on, purse: purse, strategy: strategy, quantity: pos.quantity, autoprev: pos, } pos.autonxt ||= Iro::Position.where({ inner_strike: inner_attrs[:strike], outer_strike: outer_attrs[:strike], purse: purse, stock: strategy.stock, strategy: strategy, }).first pos.autonxt ||= Iro::Position.new(autonxt_attrs) pos.autonxt.update(autonxt_attrs) pos.autonxt.inner.update(inner_attrs) pos.autonxt.outer.update(outer_attrs) pos.autonxt.sync pos.autonxt.save! pos.save return pos else throw 'zmq - should not happen' end end |
#calc_rollp ⇒ Object
should_roll?
222 223 224 225 226 227 228 229 230 231 |
# File 'app/models/iro/position.rb', line 222 def calc_rollp pos = self pos.next_reasons = [] out = strategy.send("calc_rollp_#{strategy.kind}", pos ) pos.rollp = out[0] pos.next_reasons.push out[1] save end |
#close_price ⇒ Object
credit spread only
150 151 152 153 154 |
# File 'app/models/iro/position.rb', line 150 def close_price pos = self out = pos.outer.end_price - pos.inner.end_price return out.round(2) end |
#current_underlying_strike ⇒ Object
128 129 130 |
# File 'app/models/iro/position.rb', line 128 def Iro::Stock.find_by( ticker: ticker ).last end |
#end_delta ⇒ Object
101 102 103 |
# File 'app/models/iro/position.rb', line 101 def end_delta strategy.send("end_delta_#{strategy.kind}", self) end |
#inner ⇒ Object
Options
70 |
# File 'app/models/iro/position.rb', line 70 belongs_to :inner, class_name: 'Iro::Option', inverse_of: :pos_of_inner |
#inner_strike ⇒ Object
2026-02-24 only to make finding easier.
82 |
# File 'app/models/iro/position.rb', line 82 validates :inner_strike, presence: true |
#max_gain ⇒ Object
each
188 189 190 |
# File 'app/models/iro/position.rb', line 188 def max_gain # each strategy.send("max_gain_#{strategy.kind}", self) end |
#max_loss ⇒ Object
each
191 192 193 |
# File 'app/models/iro/position.rb', line 191 def max_loss # each strategy.send("max_loss_#{strategy.kind}", self) end |
#net_amount ⇒ Object
each
173 174 175 |
# File 'app/models/iro/position.rb', line 173 def net_amount # each self.send("net_amount_#{strategy.kind}") end |
#net_amount_covered_call ⇒ Object
176 177 178 |
# File 'app/models/iro/position.rb', line 176 def net_amount_covered_call inner.begin_price - inner.end_price end |
#net_amount_long_credit_put_spread ⇒ Object
2025-10-14 tested
180 181 182 |
# File 'app/models/iro/position.rb', line 180 def net_amount_long_credit_put_spread ## each inner.begin_price - outer.begin_price + outer.end_price - inner.end_price end |
#net_amount_short_credit_call_spread ⇒ Object
2026-02-19 tested
184 185 186 |
# File 'app/models/iro/position.rb', line 184 def net_amount_short_credit_call_spread return net_amount_long_credit_put_spread end |
#net_percent ⇒ Object
170 171 172 |
# File 'app/models/iro/position.rb', line 170 def net_percent net_amount / max_gain end |
#next_expires_on ⇒ Object
ok
376 377 378 379 380 381 382 |
# File 'app/models/iro/position.rb', line 376 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.strftime('%Y-%m-%d') end |
#next_reasons ⇒ Object
decisions
218 |
# File 'app/models/iro/position.rb', line 218 field :next_reasons, type: :array, default: [] |
#outer_strike ⇒ Object
2026-02-24 only to make finding easier.
79 |
# File 'app/models/iro/position.rb', line 79 validates :outer_strike, presence: true |
#place2_price ⇒ Object
place2 = credit-spread
157 158 159 160 161 |
# File 'app/models/iro/position.rb', line 157 def place2_price pos = self out = pos.inner.begin_price - pos.outer.begin_price return out.round(2) end |
#prev ⇒ Object
there are many of these, for viewing on the ‘roll’ view
60 |
# File 'app/models/iro/position.rb', line 60 belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxts, optional: true |
#put_call ⇒ Object
51 52 53 |
# File 'app/models/iro/position.rb', line 51 def put_call self[:put_call] || self.strategy.put_call end |
#q ⇒ Object
89 |
# File 'app/models/iro/position.rb', line 89 def q; quantity; end |
#refresh ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'app/models/iro/position.rb', line 132 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 |
#roll_price ⇒ Object
163 164 165 166 167 |
# File 'app/models/iro/position.rb', line 163 def roll_price pos = self out = pos.autoprev.outer.end_price - pos.autoprev.inner.end_price + pos.inner.begin_price - pos.outer.begin_price return out.round(2) end |
#status_label(st) ⇒ Object
22 23 24 25 26 |
# File 'app/models/iro/position.rb', line 22 def status_label st labels = {} labels[STATUS_PROPOSED] = 'Selected.' return labels[st] || st end |
#sync ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'app/models/iro/position.rb', line 196 def sync if schwab_order_id outs = Tda::Order.check_status schwab_order_id update({ schwab_status: outs['status'] }) if [ Tda::Order::STATUS_FILLED, Tda::Order::STATUS_REPLACED ].include?( outs['status'] ) ## update amounts. purse.update({ available_amount: purse.available_amount + next_gain_loss_amount*quantity*100 }) ## make this one active update({ status: Iro::Position::STATUS_ACTIVE, next_gain_loss_amount: nil }) ## make previous one closed autoprev.update({ status: Iro::Position::STATUS_CLOSED }) end end inner.sync outer.sync end |
#ticker ⇒ Object
37 38 39 40 41 42 43 |
# File 'app/models/iro/position.rb', line 37 def ticker if !self[:ticker] self[:ticker] = stock.ticker self.save end self[:ticker] end |
#to_s ⇒ Object
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'app/models/iro/position.rb', line 394 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 |