iron_warbler 2.0.7.24 → 2.0.7.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iron_warbler/Card.scss +6 -0
  3. data/app/assets/stylesheets/iron_warbler/positions.scss +1 -1
  4. data/app/assets/stylesheets/iron_warbler/positions_gameui.scss +8 -0
  5. data/app/assets/stylesheets/iron_warbler/purses_gameui.scss +2 -4
  6. data/app/assets/stylesheets/iron_warbler/purses_summary.scss +31 -19
  7. data/app/controllers/iro/application_controller.rb +6 -0
  8. data/app/controllers/iro/positions_controller.rb +186 -137
  9. data/app/controllers/iro/purses_controller.rb +3 -1
  10. data/app/controllers/iro/stocks_controller.rb +2 -2
  11. data/app/models/iro/option.rb +41 -148
  12. data/app/models/iro/option_black_scholes.rb +149 -0
  13. data/app/models/iro/position.rb +156 -205
  14. data/app/models/iro/purse.rb +34 -4
  15. data/app/models/iro/strategy.rb +49 -47
  16. data/app/views/iro/_main_header.haml +4 -2
  17. data/app/views/iro/options/_show_mini.haml +8 -0
  18. data/app/views/iro/positions/_form.haml +8 -3
  19. data/app/views/iro/positions/_formpart_4data.haml +41 -38
  20. data/app/views/iro/positions/_gameui_covered_call.haml +1 -1
  21. data/app/views/iro/positions/_gameui_long_debit_call_spread.haml +5 -5
  22. data/app/views/iro/positions/_gameui_long_debit_call_spread.haml-trash +42 -0
  23. data/app/views/iro/positions/_gameui_short_debit_put_spread.haml +1 -0
  24. data/app/views/iro/positions/_gameui_short_debit_put_spread.haml-trash +40 -0
  25. data/app/views/iro/positions/_header.haml +2 -0
  26. data/app/views/iro/positions/_header_long_debit_call_spread.haml +43 -25
  27. data/app/views/iro/positions/_prepare_long_debit_call_spread.haml +6 -5
  28. data/app/views/iro/positions/_prepare_short_debit_put_spread.haml +2 -1
  29. data/app/views/iro/positions/_table.haml +25 -26
  30. data/app/views/iro/positions/prepare.haml +6 -4
  31. data/app/views/iro/positions/prepare2.haml +22 -0
  32. data/app/views/iro/purses/_form_extra_fields.haml +8 -4
  33. data/app/views/iro/purses/_header.haml +19 -5
  34. data/app/views/iro/purses/_summary.haml +69 -62
  35. data/app/views/iro/purses/show.haml +1 -1
  36. data/app/views/iro/strategies/_form.haml +26 -19
  37. data/app/views/iro/strategies/_show.haml +6 -4
  38. data/config/routes.rb +10 -7
  39. metadata +8 -2
  40. data/app/views/iro/positions/_gameui_short_debit_put_spread.haml +0 -40
@@ -5,11 +5,18 @@ class Iro::Position
5
5
  include Mongoid::Paranoia
6
6
  store_in collection: 'iro_positions'
7
7
 
8
+ field :prev_gain_loss_amount, type: :float
8
9
  attr_accessor :next_gain_loss_amount
10
+ def prev_gain_loss_amount
11
+ out = autoprev.outer.end_price - autoprev.inner.end_price
12
+ out += inner.begin_price - outer.begin_price
13
+ end
9
14
 
10
15
  STATUS_ACTIVE = 'active'
11
16
  STATUS_PROPOSED = 'proposed'
12
- STATUSES = [ nil, 'active', 'inactive', 'proposed' ]
17
+ STATUS_CLOSED = 'closed'
18
+ STATUS_PENDING = 'pending'
19
+ STATUSES = [ nil, STATUS_ACTIVE, STATUS_PROPOSED, STATUS_CLOSED, STATUS_PENDING ]
13
20
  field :status
14
21
  validates :status, presence: true
15
22
  scope :active, ->{ where( status: 'active' ) }
@@ -18,20 +25,39 @@ class Iro::Position
18
25
  index({ purse_id: 1, ticker: 1 })
19
26
 
20
27
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
21
- def ticker
22
- stock&.ticker || '-'
23
- end
28
+ delegate :ticker, to: :stock
24
29
 
25
30
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
31
+ field :long_or_short
32
+
33
+ def put_call
34
+ case strategy.kind
35
+ when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
36
+ put_call = 'CALL'
37
+ when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
38
+ put_call = 'PUT'
39
+ when Iro::Strategy::KIND_COVERED_CALL
40
+ put_call = 'CALL'
41
+ end
42
+ end
26
43
 
27
- # field :ticker
28
- # validates :ticker, presence: true
44
+ belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxt, optional: true
45
+ belongs_to :autoprev, class_name: 'Iro::Position', inverse_of: :autonxt, optional: true
46
+ ## there are many of these, for viewing on the 'roll' view
47
+ has_many :nxt, class_name: 'Iro::Position', inverse_of: :prev
48
+ has_one :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev
49
+
50
+ ## Options
51
+
52
+ belongs_to :inner, class_name: 'Iro::Option', inverse_of: :inner
53
+ belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer
54
+ accepts_nested_attributes_for :inner, :outer
29
55
 
30
56
  field :outer_strike, type: :float
31
57
  # validates :outer_strike, presence: true
32
58
 
33
59
  field :inner_strike, type: :float
34
- validates :inner_strike, presence: true
60
+ # validates :inner_strike, presence: true
35
61
 
36
62
  field :expires_on
37
63
  validates :expires_on, presence: true
@@ -41,18 +67,8 @@ class Iro::Position
41
67
  def q; quantity; end
42
68
 
43
69
  field :begin_on
44
- field :begin_outer_price, type: :float
45
- field :begin_outer_delta, type: :float
46
-
47
- field :begin_inner_price, type: :float
48
- field :begin_inner_delta, type: :float
49
70
 
50
71
  field :end_on
51
- field :end_outer_price, type: :float
52
- field :end_outer_delta, type: :float
53
-
54
- field :end_inner_price, type: :float
55
- field :end_inner_delta, type: :float
56
72
 
57
73
  def begin_delta
58
74
  strategy.send("begin_delta_#{strategy.kind}", self)
@@ -80,7 +96,7 @@ class Iro::Position
80
96
  end_delta: out[:delta],
81
97
  end_price: out[:last],
82
98
  })
83
- print '_'
99
+ print '^'
84
100
  end
85
101
 
86
102
  def net_percent
@@ -95,237 +111,172 @@ class Iro::Position
95
111
  def max_loss # each
96
112
  strategy.send("max_loss_#{strategy.kind}", self)
97
113
  end
98
- # def gain_loss_amount
99
- # strategy.send("gain_loss_amount_#{strategy.kind}", self)
100
- # end
101
114
 
102
115
 
103
- field :next_delta, type: :float
104
- field :next_outcome, type: :float
105
- field :next_symbol
106
- field :next_mark
107
- field :next_reasons, type: :array, default: []
108
- field :rollp, type: :float
109
-
110
-
111
- ## covered call
112
- # def sync
113
- # puts! [ inner_strike, expires_on, stock.ticker ], 'init sync'
114
- # out = Tda::Option.get_quote({
115
- # contractType: 'CALL',
116
- # strike: inner_strike,
117
- # expirationDate: expires_on,
118
- # ticker: stock.ticker,
119
- # })
120
- # puts! out, 'sync'
121
- # self.end_inner_price = ( out.bid + out.ask ) / 2
122
- # self.end_inner_delta = out.delta
123
- # end
124
-
125
- ## long call spread
126
- # def sync
127
- # # puts! [
128
- # # [ inner_strike, expires_on, stock.ticker ],
129
- # # [ outer_strike, expires_on, stock.ticker ],
130
- # # ], 'init sync inner, outer'
131
- # inner = Tda::Option.get_quote({
132
- # contractType: 'CALL',
133
- # strike: inner_strike,
134
- # expirationDate: expires_on,
135
- # ticker: stock.ticker,
136
- # })
137
- # outer = Tda::Option.get_quote({
138
- # contractType: 'CALL',
139
- # strike: outer_strike,
140
- # expirationDate: expires_on,
141
- # ticker: stock.ticker,
142
- # })
143
- # puts! [inner, outer], 'sync inner, outer'
144
- # self.end_outer_price = ( outer.bid + outer.ask ) / 2
145
- # self.end_outer_delta = outer.delta
146
-
147
- # self.end_inner_price = ( inner.bid + inner.ask ) / 2
148
- # self.end_inner_delta = inner.delta
149
- # end
150
-
151
116
  def sync
152
- put_call = Iro::Strategy::LONG == strategy.long_or_short ? 'CALL' : 'PUT'
153
- puts! [
154
- [ inner_strike, expires_on, stock.ticker ],
155
- [ outer_strike, expires_on, stock.ticker ],
156
- ], 'init sync inner, outer'
157
- inner = Tda::Option.get_quote({
158
- contractType: put_call,
159
- strike: inner_strike,
160
- expirationDate: expires_on,
161
- ticker: stock.ticker,
162
- })
163
- outer = Tda::Option.get_quote({
164
- contractType: put_call,
165
- strike: outer_strike,
166
- expirationDate: expires_on,
167
- ticker: stock.ticker,
168
- })
169
- puts! [inner, outer], 'sync inner, outer'
170
- self.end_outer_price = ( outer.bid + outer.ask ) / 2
171
- self.end_outer_delta = outer.delta
172
-
173
- self.end_inner_price = ( inner.bid + inner.ask ) / 2
174
- self.end_inner_delta = inner.delta
117
+ inner.sync
118
+ outer.sync
175
119
  end
176
- def sync_short_debit_put_spread
177
- puts! [
178
- [ inner_strike, expires_on, stock.ticker ],
179
- [ outer_strike, expires_on, stock.ticker ],
180
- ], 'init sync inner, outer'
181
- inner = Tda::Option.get_quote({
182
- contractType: 'PUT',
183
- strike: inner_strike,
184
- expirationDate: expires_on,
185
- ticker: stock.ticker,
186
- })
187
- outer = Tda::Option.get_quote({
188
- contractType: 'PUT',
189
- strike: outer_strike,
190
- expirationDate: expires_on,
191
- ticker: stock.ticker,
192
- })
193
- puts! [inner, outer], 'sync inner, outer'
194
- self.end_outer_price = ( outer.bid + outer.ask ) / 2
195
- self.end_outer_delta = outer.delta
196
120
 
197
- self.end_inner_price = ( inner.bid + inner.ask ) / 2
198
- self.end_inner_delta = inner.delta
199
- end
200
121
 
201
122
  ##
202
123
  ## decisions
203
124
  ##
204
125
 
126
+ field :next_reasons, type: :array, default: []
127
+ field :rollp, type: :float
128
+
129
+ ## should_roll?
205
130
  def calc_rollp
206
131
  self.next_reasons = []
207
- self.next_symbol = nil
208
- self.next_delta = nil
132
+ # self.next_symbol = nil
133
+ # self.next_delta = nil
209
134
 
210
135
  out = strategy.send( "calc_rollp_#{strategy.kind}", self )
211
136
 
212
137
  self.rollp = out[0]
213
138
  self.next_reasons.push out[1]
214
139
  save
215
-
216
- # update({
217
- # next_delta: next_position[:delta],
218
- # next_outcome: next_position[:mark] - end_price,
219
- # next_symbol: next_position[:symbol],
220
- # next_mark: next_position[:mark],
221
- # should_rollp: out,
222
- # # status: Iro::Position::STATE_PROPOSED,
223
- # })
224
- end
225
-
226
-
227
- ## expires_on = cc.expires_on ; nil
228
- def can_roll?
229
- ## only if less than 7 days left
230
- ( expires_on.to_date - Time.now.to_date ).to_i < 7
231
- end
232
-
233
- ## strike = cc.strike ; strategy = cc.strategy ; nil
234
- def near_below_water?
235
- strike < current_underlying_strike + strategy.buffer_above_water
236
140
  end
237
141
 
238
-
239
-
240
- ## 2023-03-18 _vp_ Continue.
241
- ## 2023-03-19 _vp_ Continue.
242
- ## 2023-08-05 _vp_ an Important method
243
- ##
244
- ## expires_on = cc.expires_on ; strategy = cc.strategy ; ticker = cc.ticker ; end_price = cc.end_price ; next_expires_on = cc.next_expires_on ; nil
245
- ##
246
- ## out.map { |p| [ p[:strikePrice], p[:delta] ] }
247
- ##
248
- def next_position
249
- return @next_position if @next_position
250
- return {} if ![ STATUS_ACTIVE, STATUS_PROPOSED ].include?( status )
142
+ def calc_nxt
143
+ pos = self
251
144
 
252
145
  ## 7 days ahead - not configurable so far
253
- out = Tda::Option.get_quotes({
254
- ticker: ticker,
146
+ outs = Tda::Option.get_quotes({
147
+ contractType: pos.put_call,
255
148
  expirationDate: next_expires_on,
256
- contractType: 'CALL',
149
+ ticker: ticker,
257
150
  })
151
+ outs_bk = outs.dup
258
152
 
259
- ## above_water
260
- if strategy.buffer_above_water.present?
261
- out = out.select do |i|
262
- i[:strikePrice] > current_underlying_strike + strategy.buffer_above_water
153
+ # byebug
154
+
155
+ ## strike price
156
+ outs = outs.select do |out|
157
+ out[:bidSize]+out[:askSize] > 0
158
+ end
159
+ outs = outs.select do |out|
160
+ if Iro::Strategy::SHORT == pos.long_or_short
161
+ out[:strikePrice] > strategy.next_buffer_above_water + strategy.stock.last
162
+ elsif Iro::Strategy::LONG == pos.long_or_short
163
+ out[:strikePrice] < strategy.stock.last - strategy.next_buffer_above_water
164
+ else
165
+ throw 'zz4 - this cannot happen'
166
+ end
167
+ end
168
+ puts! outs[0][:strikePrice], 'after calc next_buffer_above_water'
169
+
170
+ outs = outs.select do |out|
171
+ if Iro::Strategy::SHORT == pos.long_or_short
172
+ out[:strikePrice] > strategy.next_inner_strike
173
+ elsif Iro::Strategy::LONG == pos.long_or_short
174
+ out[:strikePrice] < strategy.next_inner_strike
175
+ else
176
+ throw 'zz3 - this cannot happen'
263
177
  end
264
- # next_reasons.push "buffer_above_water above #{current_underlying_strike + strategy.buffer_above_water}"
265
178
  end
179
+ puts! outs[0][:strikePrice], 'after calc next_inner_strike'
266
180
 
267
- if near_below_water?
268
- msg = "Panic! climb at a loss. Skip the rest of the calculation."
269
- next_reasons.push msg
270
- ## @TODO: if not enough money in the purse, cannot roll? 2023-03-19
181
+ ## delta
182
+ outs = outs.select do |out|
183
+ out_delta = out[:delta].abs rescue 0
184
+ out_delta >= strategy.next_inner_delta.abs
185
+ end
186
+ puts! outs[0][:strikePrice], 'after calc next_inner_delta'
187
+
188
+ inner = outs[0]
189
+ outs = outs.select do |out|
190
+ out[:strikePrice] >= inner[:strikePrice].to_f + strategy.next_spread_amount
191
+ end
192
+ outer = outs[0]
193
+
194
+ # byebug
195
+
196
+ if inner && outer
197
+ o_attrs = {
198
+ expires_on: next_expires_on,
199
+ put_call: pos.put_call,
200
+ stock_id: pos.stock_id,
201
+ }
202
+ inner_ = Iro::Option.new(o_attrs.merge({
203
+ strike: inner[:strikePrice],
204
+ begin_price: ( inner[:bid] + inner[:ask] )/2,
205
+ begin_delta: inner[:delta],
206
+ end_price: ( inner[:bid] + inner[:ask] )/2,
207
+ end_delta: inner[:delta],
208
+ }))
209
+ outer_ = Iro::Option.new(o_attrs.merge({
210
+ strike: outer[:strikePrice],
211
+ begin_price: ( outer[:bid] + outer[:ask] )/2,
212
+ begin_delta: outer[:delta],
213
+ end_price: ( outer[:bid] + outer[:ask] )/2,
214
+ end_delta: outer[:delta],
215
+ }))
216
+ pos.autonxt ||= Iro::Position.new
217
+ pos.autonxt.update({
218
+ prev_gain_loss_amount: 'a',
219
+ status: 'proposed',
220
+ stock: strategy.stock,
221
+ inner: inner_,
222
+ outer: outer_,
223
+ inner_strike: inner_.strike,
224
+ outer_strike: outer_.strike,
225
+ begin_on: Time.now.to_date,
226
+ expires_on: next_expires_on,
227
+ purse: purse,
228
+ strategy: strategy,
229
+ quantity: 1,
230
+ autoprev: pos,
231
+ })
271
232
 
272
233
  # byebug
273
234
 
274
- ## Take a small loss here.
275
- prev = nil
276
- out.each_with_index do |i, idx|
277
- next if idx == 0
278
- if i[:last] < end_price
279
- prev ||= i
280
- end
281
- end
282
- out = [ prev ]
235
+ autonxt.sync
236
+ autonxt.save!
283
237
 
284
238
  else
285
- ## Normal flow, making money.
286
- ## @TODO: test! _vp_ 2023-03-19
287
-
288
- ## next_min_strike
289
- if strategy.next_min_strike.present?
290
- out = out.select do |i|
291
- i[:strikePrice] >= strategy.next_min_strike
292
- end
293
- # next_reasons.push "next_min_strike above #{strategy.next_min_strike}"
294
- end
295
- # json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_min_strike'
296
-
297
- ## max_delta
298
- if strategy.next_max_delta.present?
299
- out = out.select do |i|
300
- i[:delta] = 0.0 if i[:delta] == "NaN"
301
- i[:delta] <= strategy.next_max_delta
302
- end
303
- # next_reasons.push "next_max_delta below #{strategy.next_max_delta}"
304
- end
305
- # json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_max_delta'
239
+ throw 'zmq - should not happen'
306
240
  end
307
-
308
- @next_position = out[0] || {}
309
241
  end
310
242
 
311
- ## @TODO: Test this. _vp_ 2023-04-01
243
+
244
+
245
+ ## ok
312
246
  def next_expires_on
313
- out = expires_on.to_time + 7.days
314
- while !out.friday?
315
- out = out + 1.day
316
- end
317
- while !out.workday?
318
- out = out - 1.day
247
+ out = expires_on.to_datetime.next_occurring(:monday).next_occurring(:friday)
248
+ if !out.workday?
249
+ out = Time.previous_business_day(out )
319
250
  end
320
251
  return out
321
252
  end
322
253
 
254
+ ## ok
255
+ def self.long
256
+ where( long_or_short: Iro::Strategy::LONG )
257
+ end
258
+
259
+ ## ok
260
+ def self.short
261
+ where( long_or_short: Iro::Strategy::SHORT )
262
+ end
263
+
323
264
  def to_s
324
265
  out = "#{stock} (#{q}) #{expires_on.to_datetime.strftime('%b %d')} #{strategy.kind_short} ["
325
- if outer_strike
326
- out = out + "$#{outer_strike}->"
266
+ if Iro::Strategy::LONG == long_or_short
267
+ if outer.strike
268
+ out = out + "$#{outer.strike}->"
269
+ end
270
+ out = out + "$#{inner.strike}"
271
+ else
272
+ out = out + "$#{inner.strike}"
273
+ if outer.strike
274
+ out = out + "<-$#{outer.strike}"
275
+ end
327
276
  end
328
- out = out + "$#{inner_strike}] "
277
+ out += "] "
329
278
  return out
330
279
  end
331
280
  end
281
+
282
+
@@ -1,4 +1,7 @@
1
1
 
2
+ require 'distribution'
3
+ N = Distribution::Normal
4
+
2
5
  class Iro::Purse
3
6
  include Mongoid::Document
4
7
  include Mongoid::Timestamps
@@ -9,23 +12,50 @@ class Iro::Purse
9
12
  validates :slug, presence: true, uniqueness: true
10
13
  index({ slug: -1 }, { unique: true })
11
14
 
12
- has_many :positions, class_name: 'Iro::Position', inverse_of: :purse
15
+ has_many :positions, class_name: 'Iro::Position', inverse_of: :purse
16
+
17
+ has_and_belongs_to_many :strategies, class_name: 'Iro::Strategy', inverse_of: :purses
13
18
 
14
19
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
15
20
 
16
21
  field :unit, type: :integer, default: 10
22
+ ## with unit 10, .001
23
+ ## with unit 100, .0001
24
+ field :summary_unit, type: :float, default: 0.001
17
25
 
18
26
  ## for rolling only:
19
27
  field :height, type: :integer, default: 100
20
28
 
21
29
  field :mark_every_n_usd, type: :float, default: 1
22
30
  field :n_next_positions, type: :integer, default: 5
23
- ## with unit 10, sum_scale .001
24
- ## with unit 100, sum_scale .0001
25
- field :summary_scale, type: :float, default: 0.001
26
31
 
27
32
  field :available_amount, type: :float
28
33
 
34
+ def delta_wt_avg( begin_end, long_short, inner_outer )
35
+ max_loss_total = 0
36
+
37
+ out = positions.send( long_short ).map do |pos|
38
+ max_loss_total += pos.max_loss * pos.q
39
+ pos.max_loss * pos.q * pos.send( inner_outer ).send( "#{begin_end}_delta" )
40
+ end
41
+ puts! out, 'delta_wt_avg 1'
42
+ out = out.reduce( &:+ ) / max_loss_total rescue 0
43
+ puts! out, 'delta_wt_avg 2'
44
+ return out
45
+ end
46
+ ## delta to plot percentage
47
+ ## convert to normal between 0 and 3 std
48
+ def delta_to_plot_p( *args )
49
+ x = delta_wt_avg( *args ).abs
50
+ if x < 0.5
51
+ y = 1
52
+ else
53
+ y = 2 - 1/( 1.5 - x )
54
+ end
55
+ y_ = "#{ (y*100) .to_i}%"
56
+ return y_
57
+ end
58
+
29
59
  def to_s
30
60
  slug
31
61
  end