Class: Iro::Strategy

Inherits:
Object
  • Object
show all
Includes:
Mongoid::Document, Mongoid::Timestamps
Defined in:
app/models/iro/strategy.rb

Constant Summary collapse

LONG =
'long'
SHORT =
'short'
CREDIT =
'credit'
DEBIT =
'debit'
KIND_COVERED_CALL =
'covered_call'
KIND_DIAG_LONG_CALL_SPREAD =
'diag_long_call_spread'
KIND_DIAG_SHORT_PUT_SPREAD =
'diag_short_put_spread'
KIND_IRON_CONDOR =
'iron_condor'
KIND_LONG_CREDIT_PUT_SPREAD =
'long_credit_put_spread'
KIND_LONG_DEBIT_CALL_SPREAD =
'long_debit_call_spread'
KIND_SHORT_CREDIT_CALL_SPREAD =
'short_credit_call_spread'
KIND_SHORT_DEBIT_PUT_SPREAD =
'short_debit_put_spread'
KIND_UNINVESTABLE =
'uninvestable'
KIND_LONG_ONLY =
'long-only'
KIND_SPREAD =

these are too simple and deprecated:

'spread'
KIND_WHEEL =

@deprecated, be specific

'wheel'
KINDS =

@deprecated, use covered_call

[ nil,
  KIND_COVERED_CALL,
  KIND_DIAG_LONG_CALL_SPREAD,
  KIND_DIAG_SHORT_PUT_SPREAD,
  KIND_IRON_CONDOR,
  KIND_LONG_CREDIT_PUT_SPREAD,
  KIND_LONG_DEBIT_CALL_SPREAD,
  KIND_SHORT_CREDIT_CALL_SPREAD,
  KIND_SHORT_DEBIT_PUT_SPREAD,
  KIND_UNINVESTABLE,
  KIND_LONG_ONLY,
]
INTENT_CLOSE =
'try-close'
INTENT_OPEN =
'try-open'
INTENT_ROLL =
'try-roll'
INTENTS =
[ nil, INTENT_CLOSE, INTENT_ROLL ]

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.for_ticker(ticker) ⇒ Object

scopes



392
393
394
# File 'app/models/iro/strategy.rb', line 392

def self.for_ticker ticker
  where( ticker: ticker )
end

.list(long_or_short = nil) ⇒ Object



400
401
402
403
# File 'app/models/iro/strategy.rb', line 400

def self.list long_or_short = nil
  these = long_or_short ? where( long_or_short: long_or_short ) : all
  [[nil,nil]] + these.map { |st| [ st, st.id ] }
end

Instance Method Details

#_begin_delta_spread(p) ⇒ Object



125
126
127
# File 'app/models/iro/strategy.rb', line 125

def _begin_delta_spread p
  p.inner.begin_delta - p.outer.begin_delta
end

#_end_delta_spread(p) ⇒ Object



147
148
149
# File 'app/models/iro/strategy.rb', line 147

def _end_delta_spread p
  p.inner.end_delta - p.outer.end_delta
end

#begin_delta(p) ⇒ Object



119
120
121
# File 'app/models/iro/strategy.rb', line 119

def begin_delta p
  _begin_delta_spread p
end

#begin_delta_covered_call(p) ⇒ Object



122
123
124
# File 'app/models/iro/strategy.rb', line 122

def begin_delta_covered_call p
  p.inner.begin_delta
end

#begin_delta_diag_long_call_spread(p) ⇒ Object



134
135
136
# File 'app/models/iro/strategy.rb', line 134

def begin_delta_diag_long_call_spread p
  _begin_delta_spread p
end

#begin_delta_diag_short_put_spread(p) ⇒ Object



137
138
139
# File 'app/models/iro/strategy.rb', line 137

def begin_delta_diag_short_put_spread p
  _begin_delta_spread p
end

#begin_delta_long_credit_put_spread(p) ⇒ Object



128
129
130
# File 'app/models/iro/strategy.rb', line 128

def begin_delta_long_credit_put_spread p
  _begin_delta_spread p
end

#begin_delta_short_credit_call_spread(p) ⇒ Object



131
132
133
# File 'app/models/iro/strategy.rb', line 131

def begin_delta_short_credit_call_spread p
  _begin_delta_spread p
end

#calc_rollp_covered_call(p) ⇒ Object

do not use!



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/models/iro/strategy.rb', line 244

def calc_rollp_covered_call p
  stock.reload

  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
    return [ 0.99, '0 DTE, must exit' ]
  end

  if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
    return [ 0.98, "Last #{'%.2f' % stock.last} is " +
      "#{'%.2f' % [p.inner.strike + threshold_usd_above_mark - stock.last]} " +
      "below #{'%.2f' % [p.inner.strike + threshold_usd_above_mark]} water" ]
  end

  if p.inner.end_delta < threshold_pos_delta
    return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
  end

  if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
    return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit." ]
  end

  return [ 0.33, '-' ]
end

#calc_rollp_long_credit_put_spread(p) ⇒ Object

2025-10-12 continue 2026-04-05 continue



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
# File 'app/models/iro/strategy.rb', line 298

def calc_rollp_long_credit_put_spread p
  stock.reload

  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
    return [ 0.99, '0 DTE, must exit' ]
  end
  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 2
    return [ 0.99, '1 DTE, must exit' ]
  end

  if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
    return [ 0.95, ":threshold_usd_above_mark <br />Last $#{'%.2f' % stock.last} is " +
      "#{'%.2f' % [stock.last - p.inner.strike]} near #{p.inner.strike} but should be greater than #{threshold_usd_above_mark} ." ]
  end

  if p.inner.end_delta.abs < threshold_pos_delta
    return [ 0.79, ":threshold_pos_delta <br />Offensive roll: delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} ." ]
  end
  if p.inner.end_delta.abs > threshold_neg_delta
    return [ 0.79, ":threshold_neg_delta <br />Defensive roll: delta #{p.inner.end_delta} is higher than #{threshold_neg_delta} ." ]
  end

  if threshold_netp.present?
    if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
      return [ 0.51, ":threshold_netp <br />made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
    end
  end

  return [ 0.33, '-' ]
end

#calc_rollp_long_debit_call_spread(p) ⇒ Object

do not use!



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
# File 'app/models/iro/strategy.rb', line 269

def calc_rollp_long_debit_call_spread p
  stock.reload

  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
    return [ 0.99, '0 DTE, must exit' ]
  end
  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 2
    return [ 0.99, '1 DTE, must exit' ]
  end

  if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
    return [ 0.95, "Last #{'%.2f' % stock.last} is " +
        "#{'%.2f' % [stock.last - p.inner.strike - threshold_usd_above_mark]} " +
        "below #{'%.2f' % [p.inner.strike + threshold_usd_above_mark]} water" ]
  end

  if p.inner.end_delta < threshold_pos_delta
    return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
  end

  if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
    return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
  end

  return [ 0.33, '-' ]
end

#calc_rollp_short_credit_call_spread(p) ⇒ Object

2026-02-21 ok 2026-04-05 ok



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'app/models/iro/strategy.rb', line 356

def calc_rollp_short_credit_call_spread p
  # puts! p, 'calc_rollp_short_credit_call_spread...'
  stock.reload

  if ( p.expires_on.to_date - Time.now.to_date ).to_i <= threshold_dte
    return [ 0.99, "< #{threshold_dte}DTE, must exit" ]
  end

  if stock.last + threshold_usd_above_mark > p.inner.strike
    return [ 0.95, ":threshold_usd_above_mark <br />Last $#{'%.2f' % stock.last} is " +
        "#{'%.2f' % [p.inner.strike - stock.last]} near #{p.inner.strike} but should be greater than #{threshold_usd_above_mark} ." ]
  end

  ## defensive
  ## inner short call is negative delta, but I'll deal with absolutes anyway.
  if p.inner.end_delta.abs > threshold_neg_delta.abs
    return [ 0.88, "Delta #{p.inner.end_delta} is larger than #{threshold_neg_delta} defensive threshold." ]
  end

  ## offensive
  if p.inner.end_delta.abs < threshold_pos_delta.abs
    return [ 0.69, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} offensive threshold." ]
  end

  if threshold_netp.present?
    if p.net_percent > threshold_netp
      return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ]
    end
  end

  return [ 0.33, '-' ]
end

#calc_rollp_short_debit_put_spread(p) ⇒ Object

do not use!



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'app/models/iro/strategy.rb', line 330

def calc_rollp_short_debit_put_spread p
  stock.reload

  if ( p.expires_on.to_date - Time.now.to_date ).to_i <= threshold_dte
    return [ 0.99, "< #{threshold_dte}DTE, must exit" ]
  end

  if stock.last + threshold_usd_above_mark > p.inner.strike
    return [ 0.98, "Last #{'%.2f' % stock.last} is " +
        "#{'%.2f' % [stock.last + threshold_usd_above_mark - p.inner.strike]} " +
        "above #{'%.2f' % [p.inner.strike - threshold_usd_above_mark]} water" ]
  end

  if p.inner.end_delta.abs < threshold_pos_delta.abs
    return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
  end

  if p.net_percent > threshold_netp
    return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ]
  end

  return [ 0.33, '-' ]
end

#end_delta(p) ⇒ Object



141
142
143
# File 'app/models/iro/strategy.rb', line 141

def end_delta p
  _end_delta_spread p
end

#end_delta_covered_call(p) ⇒ Object



144
145
146
# File 'app/models/iro/strategy.rb', line 144

def end_delta_covered_call p
  p.inner.end_delta
end

#end_delta_diag_long_call_spread(p) ⇒ Object



156
157
158
# File 'app/models/iro/strategy.rb', line 156

def end_delta_diag_long_call_spread p
  _end_delta_spread p
end

#end_delta_diag_short_put_spread(p) ⇒ Object



159
160
161
# File 'app/models/iro/strategy.rb', line 159

def end_delta_diag_short_put_spread p
  _end_delta_spread p
end

#end_delta_long_credit_put_spread(p) ⇒ Object



150
151
152
# File 'app/models/iro/strategy.rb', line 150

def end_delta_long_credit_put_spread p
  _end_delta_spread p
end

#end_delta_short_credit_call_spread(p) ⇒ Object



153
154
155
# File 'app/models/iro/strategy.rb', line 153

def end_delta_short_credit_call_spread p
  _end_delta_spread p
end

#max_gain_covered_call(p) ⇒ Object

each



172
173
174
# File 'app/models/iro/strategy.rb', line 172

def max_gain_covered_call p ## each
  p.inner.begin_price # - 0.66
end

#max_gain_diag_long_call_spread(p) ⇒ Object

each



164
165
166
167
# File 'app/models/iro/strategy.rb', line 164

def max_gain_diag_long_call_spread p ## each
  # p.outer.strike - p.inner.strike + p.outer.begin_price - p.inner.begin_price
  p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price
end

#max_gain_diag_short_put_spread(p) ⇒ Object

each



168
169
170
# File 'app/models/iro/strategy.rb', line 168

def max_gain_diag_short_put_spread p ## each
  p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price
end

#max_gain_long_credit_put_spread(p) ⇒ Object



175
176
177
178
# File 'app/models/iro/strategy.rb', line 175

def max_gain_long_credit_put_spread p
  ## 100 * disallowed for gameui
  p.inner.begin_price - p.outer.begin_price
end

#max_gain_long_debit_call_spread(p) ⇒ Object



179
180
181
182
# File 'app/models/iro/strategy.rb', line 179

def max_gain_long_debit_call_spread p
  ## 100 * disallowed for gameui
  ( p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
end

#max_gain_short_credit_call_spread(p) ⇒ Object



183
184
185
# File 'app/models/iro/strategy.rb', line 183

def max_gain_short_credit_call_spread p
  p.inner.begin_price - p.outer.begin_price
end

#max_gain_short_debit_put_spread(p) ⇒ Object



186
187
188
189
# File 'app/models/iro/strategy.rb', line 186

def max_gain_short_debit_put_spread p
  ## 100 * disallowed for gameui
  ( p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
end

#max_gain_spread(p) ⇒ Object



190
191
192
193
# File 'app/models/iro/strategy.rb', line 190

def max_gain_spread p
  ## 100 * disallowed for gameui
  ( p.outer.strike - p.inner.strike ).abs - p.outer.begin_price + p.inner.begin_price # - 2*0.66
end

#max_gain_wheel(p) ⇒ Object



194
195
196
# File 'app/models/iro/strategy.rb', line 194

def max_gain_wheel p
  p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really?
end

#max_loss_covered_call(p) ⇒ Object



206
207
208
# File 'app/models/iro/strategy.rb', line 206

def max_loss_covered_call p
  p.inner.begin_price*10 # just suppose 10,000%
end

#max_loss_diag_long_call_spread(p) ⇒ Object



199
200
201
202
# File 'app/models/iro/strategy.rb', line 199

def max_loss_diag_long_call_spread p
  # p.inner.strike - p.outer.strike + p.inner.begin_price + p.outer.begin_price
  p.outer.begin_price - p.inner.begin_price - p.realized_gl
end

#max_loss_diag_short_put_spread(p) ⇒ Object



203
204
205
# File 'app/models/iro/strategy.rb', line 203

def max_loss_diag_short_put_spread p
  p.outer.begin_price - p.inner.begin_price - p.realized_gl ## same as above, max_loss_diag_long_call_spread
end

#max_loss_long_credit_put_spread(p) ⇒ Object



209
210
211
# File 'app/models/iro/strategy.rb', line 209

def max_loss_long_credit_put_spread p
  out = p.inner.strike - p.outer.strike
end

#max_loss_short_credit_call_spread(p) ⇒ Object

def max_loss_long_debit_call_spread p out = p.outer.strike - p.inner.strike end def max_loss_short_debit_put_spread p # different out = p.inner.strike - p.outer.strike end



218
219
220
# File 'app/models/iro/strategy.rb', line 218

def max_loss_short_credit_call_spread p
  out = p.outer.strike - p.inner.strike
end

#max_loss_short_debit_put_spread(p) ⇒ Object



221
222
223
# File 'app/models/iro/strategy.rb', line 221

def max_loss_short_debit_put_spread p
  p.inner.begin_price - p.outer.begin_price
end

#max_loss_spread(p) ⇒ Object



224
225
226
# File 'app/models/iro/strategy.rb', line 224

def max_loss_spread p
  ( p.outer.strike - p.inner.strike ).abs
end

#max_loss_wheel(p) ⇒ Object



227
228
229
# File 'app/models/iro/strategy.rb', line 227

def max_loss_wheel p
  p.inner.begin_price*10 # just suppose 10,000%
end

#net_amount_spread(p) ⇒ Object



233
234
235
# File 'app/models/iro/strategy.rb', line 233

def net_amount_spread p
  p.inner.begin_price - p.inner.end_price - p.outer.begin_price + p.outer.end_price
end

#next_inner_strikeObject

can be nil, poor design?



97
# File 'app/models/iro/strategy.rb', line 97

field :next_inner_strike,       type: :float

#next_positionObject

_TODO: makes no sense...



21
# File 'app/models/iro/strategy.rb', line 21

has_one  :next_position, class_name: 'Iro::Position', inverse_of: :next_strategy

#put_callObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'app/models/iro/strategy.rb', line 53

def put_call
  case kind
  when Iro::Strategy::KIND_COVERED_CALL
    put_call = 'CALL'
  when Iro::Strategy::KIND_DIAG_LONG_CALL_SPREAD
    put_call = 'CALL'
  when Iro::Strategy::KIND_DIAG_SHORT_PUT_SPREAD
    put_call = 'PUT'
  when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD
    put_call = 'PUT'
  when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
    put_call = 'CALL'
  when Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD
    put_call = 'CALL'
  when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
    put_call = 'PUT'

  when Iro::Strategy::KIND_SPREAD
    if credit_or_debit == CREDIT
      if long_or_short == LONG
        'PUT'
      elsif long_or_short == SHORT
        'CALL'
      else
        throw 'zq5 - should never happen'
      end
    else
      throw 'zq6 - debit spreads are not implemented'
    end
  else
    throw 'zq9 - this should never happen'
  end
end

#to_sObject



397
398
399
# File 'app/models/iro/strategy.rb', line 397

def to_s
  "#{kind} #{stock} #{next_spread_amount}- #{intent} #{descr}"
end