Class: Iro::Strategy

Inherits:
Object
  • Object
show all
Includes:
Mongoid::Document, Mongoid::Paranoia, 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



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

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

.list(long_or_short = nil) ⇒ Object



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

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



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

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

#_end_delta_spread(p) ⇒ Object



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

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

#begin_delta(p) ⇒ Object



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

def begin_delta p
  _begin_delta_spread p
end

#begin_delta_covered_call(p) ⇒ Object



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

def begin_delta_covered_call p
  p.inner.begin_delta
end

#begin_delta_diag_long_call_spread(p) ⇒ Object



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

def begin_delta_diag_long_call_spread p
  _begin_delta_spread p
end

#begin_delta_diag_short_put_spread(p) ⇒ Object



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

def begin_delta_diag_short_put_spread p
  _begin_delta_spread p
end

#begin_delta_long_credit_put_spread(p) ⇒ Object



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

def begin_delta_long_credit_put_spread p
  _begin_delta_spread p
end

#begin_delta_short_credit_call_spread(p) ⇒ Object



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

def begin_delta_short_credit_call_spread p
  _begin_delta_spread p
end

#calc_rollp_covered_call(p) ⇒ Object

do not use!



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

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



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

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!



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

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



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

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!



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

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



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

def end_delta p
  _end_delta_spread p
end

#end_delta_covered_call(p) ⇒ Object



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

def end_delta_covered_call p
  p.inner.end_delta
end

#end_delta_diag_long_call_spread(p) ⇒ Object



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

def end_delta_diag_long_call_spread p
  _end_delta_spread p
end

#end_delta_diag_short_put_spread(p) ⇒ Object



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

def end_delta_diag_short_put_spread p
  _end_delta_spread p
end

#end_delta_long_credit_put_spread(p) ⇒ Object



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

def end_delta_long_credit_put_spread p
  _end_delta_spread p
end

#end_delta_short_credit_call_spread(p) ⇒ Object



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

def end_delta_short_credit_call_spread p
  _end_delta_spread p
end

#max_gain_covered_call(p) ⇒ Object

each



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

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

#max_gain_diag_long_call_spread(p) ⇒ Object

each



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

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



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

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



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

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



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

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



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

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



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

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



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

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



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

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

#max_loss_covered_call(p) ⇒ Object



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

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

#max_loss_diag_long_call_spread(p) ⇒ Object



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

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



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

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



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

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



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

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

#max_loss_short_debit_put_spread(p) ⇒ Object



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

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

#max_loss_spread(p) ⇒ Object



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

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

#max_loss_wheel(p) ⇒ Object



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

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

#net_amount_spread(p) ⇒ Object



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

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?



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

field :next_inner_strike,       type: :float

#next_positionObject

_TODO: makes no sense...



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

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

#put_callObject



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

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



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

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