wco_models 3.1.0.264 → 3.1.0.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06f4f98ee3c36e42af419ef0778c2b1dcef99b45b6950a0b8e0847e4f122ab84
4
- data.tar.gz: 3156138556995606ce6c338af1fc675b678da32a6c4a2996e3c1c6c6fc8096e8
3
+ metadata.gz: d52ff07afe73ec8c7240fbfb85a78a1cd4bf66c9db7c49fd69d5e7c3c56114aa
4
+ data.tar.gz: 33592a3dd04ab62d82c36d77070323735112bdb5966f8ba3009ff182c593f51e
5
5
  SHA512:
6
- metadata.gz: 3fdab086ab82d058301d86f1b85fac771df0fbedac5418ff503218b04d960cd09ca2a73bebee5aaee4a57f9a910b259b0ea046d5224a62790286108370c863ee
7
- data.tar.gz: 82e604c3c74303c03b7db2a007f1ddb0e6725d77957c86355ca0e749af9b7dadd7d6e6e15d2f2d3528e22d72ddd5f2387e6e9b5a061118801810955613215e2f
6
+ metadata.gz: 88fc66fef4da9de8509ff2a46088229c73ea4041aa2aee67c1cd2771dbec93bafa164b6a6d39b7e312e3f86000b36cfc554fb33f9677b495fde357df60ed1739
7
+ data.tar.gz: 816df1b774e34f02dea1d1f23176a1ba5e0c00f84a1c04afb40084cd79af2671d1e28194b70ce8f90db6c239b672cc352ae8d27a00b9ac49e5f60977d49fad06
@@ -37,9 +37,6 @@ $enable-important-utilities: false;
37
37
  border-bottom: 1px solid gray;
38
38
  border-left: 1px solid gray;
39
39
 
40
- // border-top: 2px solid white;
41
- // border-left: 2px solid white;
42
-
43
40
  display: flex;
44
41
 
45
42
  margin-top: 1em;
@@ -70,16 +70,6 @@ table.bordered {
70
70
  }
71
71
  }
72
72
 
73
- table.sticky thead,
74
- table thead.sticky {
75
- background: #ccc;
76
-
77
- th {
78
- position: sticky;
79
- top: 0;
80
- z-index: 10;
81
- }
82
- }
83
73
 
84
74
  /* C */
85
75
 
@@ -93,6 +83,11 @@ textarea.monospace {
93
83
  padding: 0.6em;
94
84
  }
95
85
 
86
+ .crossed,
87
+ .crossout {
88
+ text-decoration: line-through;
89
+ }
90
+
96
91
  /* D */
97
92
 
98
93
  .d-flex {
@@ -250,10 +245,36 @@ label.required {
250
245
 
251
246
 
252
247
  /* S */
248
+
253
249
  .spacer-small {
254
250
  height: 20px;
255
251
  }
256
252
 
253
+ div.sticky,
254
+ h2.sticky,
255
+ h3.sticky,
256
+ h4.sticky,
257
+ h5.sticky {
258
+ position: sticky;
259
+ top: 0.5em;
260
+ z-index: 10;
261
+
262
+ border-radius: 0.5em;
263
+ padding: 0.5em;
264
+ background: rgba(255,255,255, 0.5);
265
+ }
266
+
267
+ table.sticky thead,
268
+ table thead.sticky {
269
+ background: #ccc;
270
+
271
+ th {
272
+ position: sticky;
273
+ top: 0;
274
+ z-index: 10;
275
+ }
276
+ }
277
+
257
278
  /* T */
258
279
 
259
280
  .title {
@@ -34,8 +34,8 @@ module Wco::ApplicationHelper
34
34
  end
35
35
 
36
36
  def pp_amount a, config = { precision: 2 }
37
- return '-' if !a
38
- return '-' if a.class == String
37
+ return '___' if !a
38
+ return '___' if a.class == String
39
39
  return number_to_currency a, precision: config[:precision]
40
40
  # "$#{'%.2f' % a}"
41
41
  end
@@ -10,17 +10,10 @@ class Iro::Option
10
10
 
11
11
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
12
12
  def ticker; stock.ticker; end
13
- # field :ticker
14
- # validates :ticker, presence: true
15
13
 
16
14
  CALL = 'CALL'
17
15
  PUT = 'PUT'
18
16
 
19
- ## for now, recompute every time
20
- # field :symbol
21
- ## each option can be a leg in a position, no uniqueness
22
- # validates :symbol, uniqueness: true, presence: true
23
-
24
17
  field :put_call, type: :string # 'PUT' or 'CALL'
25
18
  validates :put_call, presence: true
26
19
 
@@ -29,11 +22,6 @@ class Iro::Option
29
22
  field :strike, type: :float
30
23
  validates :strike, presence: true
31
24
 
32
- def to_s
33
- "#{symbol} :: #{expires_on.strftime('%Y-%m-%d')} #{put_call} #{strike}"
34
- end
35
-
36
-
37
25
  field :expires_on, type: :date
38
26
  validates :expires_on, presence: true
39
27
  def self.expirations_list full: false, n: 5
@@ -70,38 +58,13 @@ class Iro::Option
70
58
 
71
59
  field :last, type: :float
72
60
 
73
- ## for TDA
74
- ## "COST_030626C1030"
75
- def symbol_old
76
- if !self[:symbol]
77
- p_c_ = put_call == 'PUT' ? 'P' : 'C'
78
- strike_ = strike.to_i == strike ? strike.to_i : strike
79
- sym = "#{stock.ticker}_#{expires_on.strftime("%m%d%y")}#{p_c_}#{strike_}" # XYZ_011819P45
80
- self[:symbol] = sym
81
- save
82
- end
83
- self[:symbol]
84
- end
85
-
86
- ## for schwab
61
+ ## for schwab, eg:
87
62
  ## "COST 260306C01030000"
88
63
  def symbol
89
64
  p_c_ = put_call == 'PUT' ? 'P' : 'C'
90
65
  strike_ = format("%08d", (strike.to_f * 1000).round)
91
66
  sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
92
67
  end
93
- =begin
94
- def symbol_trash ## it persists - which I dont do right now
95
- if !self[:symbol]
96
- p_c_ = put_call == 'PUT' ? 'P' : 'C'
97
- strike_ = format("%08d", (strike.to_f * 1000).round)
98
- sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
99
- self[:symbol] = sym
100
- save
101
- end
102
- self[:symbol]
103
- end
104
- =end
105
68
 
106
69
  # before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
107
70
  def sync
@@ -117,65 +80,7 @@ class Iro::Option
117
80
  self.save! ## 2026-02-19 this must be present.
118
81
  end
119
82
 
120
- def self.max_pain hash
121
- outs = {}
122
-
123
- %w| put call |.each do |contractType|
124
- dates = hash["#{contractType}ExpDateMap"]
125
- dates.each do |_date, strikes| ## _date="2023-02-10:5"
126
- date = _date.split(':')[0].to_date.to_s
127
- outs[date] ||= {
128
- 'all' => {},
129
- 'call' => {},
130
- 'put' => {},
131
- 'summary' => {},
132
- }
133
-
134
- strikes.each do |_strike, _v| ## _strike="18.5"
135
- strike = _strike.to_f
136
-
137
- ## calls
138
- mem_c = 0
139
- strikes.keys.reverse.each do |_key|
140
- if _key == _strike
141
- break
142
- end
143
- key = _key.to_f
144
- tmp = hash["callExpDateMap"][_date][_key][0]['openInterest'] * ( key - strike )
145
- mem_c += tmp
146
- end
147
- outs[date]['call'][_strike] = mem_c
148
-
149
- ## puts
150
- mem_p = 0
151
- strikes.keys.each do |_key|
152
- if _key == _strike
153
- break
154
- end
155
- key = _key.to_f
156
- tmp = hash["putExpDateMap"][_date][_key][0]['openInterest'] * ( strike - key )
157
- mem_p += tmp
158
- end
159
- outs[date]['put'][_strike] = mem_p
160
- outs[date]['all'][_strike] = mem_c + mem_p
161
-
162
- end
163
- end
164
- end
165
-
166
- ## compute summary
167
- outs.each do |date, types|
168
- all = types['all']
169
- outs[date]['summary'] = { 'value' => all.keys[0] }
170
- all.each do |strike, amount|
171
- if amount < all[ outs[date]['summary']['value'] ]
172
- outs[date]['summary']['value'] = strike
173
- end
174
- end
175
- end
176
-
177
- return outs
83
+ def to_s
84
+ "#{symbol} :: #{expires_on.strftime('%Y-%m-%d')} #{put_call} #{strike}"
178
85
  end
179
-
180
-
181
86
  end
@@ -0,0 +1,62 @@
1
+
2
+
3
+ def self.max_pain hash
4
+ outs = {}
5
+
6
+ %w| put call |.each do |contractType|
7
+ dates = hash["#{contractType}ExpDateMap"]
8
+ dates.each do |_date, strikes| ## _date="2023-02-10:5"
9
+ date = _date.split(':')[0].to_date.to_s
10
+ outs[date] ||= {
11
+ 'all' => {},
12
+ 'call' => {},
13
+ 'put' => {},
14
+ 'summary' => {},
15
+ }
16
+
17
+ strikes.each do |_strike, _v| ## _strike="18.5"
18
+ strike = _strike.to_f
19
+
20
+ ## calls
21
+ mem_c = 0
22
+ strikes.keys.reverse.each do |_key|
23
+ if _key == _strike
24
+ break
25
+ end
26
+ key = _key.to_f
27
+ tmp = hash["callExpDateMap"][_date][_key][0]['openInterest'] * ( key - strike )
28
+ mem_c += tmp
29
+ end
30
+ outs[date]['call'][_strike] = mem_c
31
+
32
+ ## puts
33
+ mem_p = 0
34
+ strikes.keys.each do |_key|
35
+ if _key == _strike
36
+ break
37
+ end
38
+ key = _key.to_f
39
+ tmp = hash["putExpDateMap"][_date][_key][0]['openInterest'] * ( strike - key )
40
+ mem_p += tmp
41
+ end
42
+ outs[date]['put'][_strike] = mem_p
43
+ outs[date]['all'][_strike] = mem_c + mem_p
44
+
45
+ end
46
+ end
47
+ end
48
+
49
+ ## compute summary
50
+ outs.each do |date, types|
51
+ all = types['all']
52
+ outs[date]['summary'] = { 'value' => all.keys[0] }
53
+ all.each do |strike, amount|
54
+ if amount < all[ outs[date]['summary']['value'] ]
55
+ outs[date]['summary']['value'] = strike
56
+ end
57
+ end
58
+ end
59
+
60
+ return outs
61
+ end
62
+
@@ -5,12 +5,6 @@ class Iro::Position
5
5
  include Mongoid::Paranoia
6
6
  store_in collection: 'iro_positions'
7
7
 
8
- ## @trash, use next_gain_loss_amount instead
9
- # field :prev_gain_loss_amount, type: :float
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
14
8
  field :next_gain_loss_amount, type: :float
15
9
 
16
10
 
@@ -23,14 +17,22 @@ class Iro::Position
23
17
  STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PREPARE, STATUS_PROPOSED, STATUS_PENDING ]
24
18
  field :status
25
19
  validates :status, presence: true
26
- scope :active, ->{ where( status: 'active' ) }
27
- field :schwab_status
20
+ scope :active, ->{ where( status: 'active' ) }
21
+ scope :proposed, ->{ where( status: 'proposed' ) }
22
+
28
23
 
29
24
  belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
30
25
  index({ purse_id: 1, ticker: 1 })
31
26
 
32
27
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
33
- delegate :ticker, to: :stock
28
+ field :ticker
29
+ def ticker
30
+ if !self[:ticker]
31
+ self[:ticker] = stock.ticker
32
+ self.save
33
+ end
34
+ self[:ticker]
35
+ end
34
36
 
35
37
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
36
38
  delegate :long_or_short, to: :strategy
@@ -83,6 +85,7 @@ class Iro::Position
83
85
  field :end_on
84
86
 
85
87
  field :schwab_order_id, type: :integer
88
+ field :schwab_status
86
89
 
87
90
  def begin_delta
88
91
  strategy.send("begin_delta_#{strategy.kind}", self)
@@ -132,6 +135,16 @@ class Iro::Position
132
135
  print '^'
133
136
  end
134
137
 
138
+
139
+ field :pending_price
140
+
141
+ ## place2 = credit-spread
142
+ def place2_price
143
+ pos = self
144
+ out = pos.inner.begin_price - pos.outer.begin_price
145
+ return out.round(2)
146
+ end
147
+
135
148
  def roll_price
136
149
  pos = self
137
150
  out = pos.autoprev.outer.end_price - pos.autoprev.inner.end_price + pos.inner.begin_price - pos.outer.begin_price
@@ -231,33 +244,35 @@ class Iro::Position
231
244
  # puts! outs, '#calc_nxt.outs -> 2'
232
245
 
233
246
  ## next_inner_strike
234
- outs = outs.select do |out|
235
- if Iro::Strategy::CREDIT == pos.credit_or_debit
236
- if Iro::Strategy::SHORT == pos.long_or_short
237
- ## short credit call
238
- out[:strikePrice] >= strategy.next_inner_strike
239
- elsif Iro::Strategy::LONG == pos.long_or_short
240
- ## long credit put
241
- out[:strikePrice] <= strategy.next_inner_strike
247
+ if strategy.next_inner_strike.present?
248
+ outs = outs.select do |out|
249
+ if Iro::Strategy::CREDIT == pos.credit_or_debit
250
+ if Iro::Strategy::SHORT == pos.long_or_short
251
+ ## short credit call
252
+ out[:strikePrice] >= strategy.next_inner_strike
253
+ elsif Iro::Strategy::LONG == pos.long_or_short
254
+ ## long credit put
255
+ out[:strikePrice] <= strategy.next_inner_strike
256
+ end
257
+ else
258
+ raise 'zt3 - @TODO: implement, debit spreads'
242
259
  end
243
- else
244
- raise 'zt3 - @TODO: implement, debit spreads'
245
260
  end
261
+ puts! outs[0][:strikePrice], 'after calc next_inner_strike'
262
+ # puts! outs, 'outs'
246
263
  end
247
- puts! outs[0][:strikePrice], 'after calc next_inner_strike'
248
- # puts! outs, 'outs'
249
264
 
250
- ## next_buffer_above_water
265
+ ## next_usd_above_mark
251
266
  outs = outs.select do |out|
252
267
  if Iro::Strategy::SHORT == pos.long_or_short
253
- out[:strikePrice] > strategy.next_buffer_above_water + strategy.stock.last
268
+ out[:strikePrice] > strategy.next_usd_above_mark + strategy.stock.last
254
269
  elsif Iro::Strategy::LONG == pos.long_or_short
255
- out[:strikePrice] < strategy.stock.last - strategy.next_buffer_above_water
270
+ out[:strikePrice] < strategy.stock.last - strategy.next_usd_above_mark
256
271
  else
257
272
  raise 'zt4 - this cannot happen'
258
273
  end
259
274
  end
260
- puts! outs[0][:strikePrice], 'after calc next_buffer_above_water'
275
+ puts! outs[0][:strikePrice], 'after calc next_usd_above_mark'
261
276
  puts! outs, 'outs'
262
277
 
263
278
  ## next_inner_delta
@@ -310,7 +325,9 @@ class Iro::Position
310
325
  status: 'proposed',
311
326
  stock: strategy.stock,
312
327
  inner_strike: inner_attrs[:strike],
328
+ inner_attributes: inner_attrs,
313
329
  outer_strike: outer_attrs[:strike],
330
+ outer_attributes: outer_attrs,
314
331
  begin_on: Time.now.to_date,
315
332
  expires_on: next_expires_on,
316
333
  purse: purse,
@@ -28,39 +28,7 @@ class Iro::Purse
28
28
  field :n_next_positions, type: :integer, default: 5
29
29
 
30
30
  field :available_amount, type: :float
31
- validates :available_amount, presence: true
32
- def available
33
- available_amount
34
- end
35
-
36
- # def balance
37
- # 0.01
38
- # end
39
-
40
- def delta_wt_avg( begin_end, long_short, inner_outer )
41
- max_loss_total = 0
42
-
43
- out = positions.send( long_short ).map do |pos|
44
- max_loss_total += pos.max_loss * pos.q
45
- pos.max_loss * pos.q * pos.send( inner_outer ).send( "#{begin_end}_delta" )
46
- end
47
- # puts! out, 'delta_wt_avg 1'
48
- out = out.reduce( &:+ ) / max_loss_total rescue 0
49
- # puts! out, 'delta_wt_avg 2'
50
- return out
51
- end
52
- ## delta to plot percentage
53
- ## convert to normal between 0 and 3 std
54
- def delta_to_plot_p( *args )
55
- x = delta_wt_avg( *args ).abs
56
- if x < 0.5
57
- y = 1
58
- else
59
- y = 2 - 1/( 1.5 - x )
60
- end
61
- y_ = "#{ (y*100) .to_i}%"
62
- return y_
63
- end
31
+ # validates :available_amount, presence: true
64
32
 
65
33
  def to_s
66
34
  slug
@@ -79,7 +79,6 @@ class Iro::Strategy
79
79
 
80
80
  field :threshold_usd_above_mark, type: :float
81
81
  validates :threshold_usd_above_mark, presence: true
82
- def buffer_above_water; threshold_usd_above_mark; end
83
82
 
84
83
  field :threshold_pos_delta, type: :float # offensive: roll b/c markets are going my way
85
84
  field :threshold_neg_delta, type: :float # defensive: roll b/c markets are going against me
@@ -206,6 +205,7 @@ class Iro::Strategy
206
205
  ## decisions
207
206
  ##
208
207
 
208
+ ## do not use!
209
209
  def calc_rollp_covered_call p
210
210
  stock.reload
211
211
 
@@ -213,10 +213,10 @@ class Iro::Strategy
213
213
  return [ 0.99, '0 DTE, must exit' ]
214
214
  end
215
215
 
216
- if ( stock.last - buffer_above_water ) < p.inner.strike
216
+ if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
217
217
  return [ 0.98, "Last #{'%.2f' % stock.last} is " +
218
- "#{'%.2f' % [p.inner.strike + buffer_above_water - stock.last]} " +
219
- "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
218
+ "#{'%.2f' % [p.inner.strike + threshold_usd_above_mark - stock.last]} " +
219
+ "below #{'%.2f' % [p.inner.strike + threshold_usd_above_mark]} water" ]
220
220
  end
221
221
 
222
222
  if p.inner.end_delta < threshold_pos_delta
@@ -230,7 +230,7 @@ class Iro::Strategy
230
230
  return [ 0.33, '-' ]
231
231
  end
232
232
 
233
- ## _TODO
233
+ ## do not use!
234
234
  def calc_rollp_long_debit_call_spread p
235
235
  stock.reload
236
236
 
@@ -241,10 +241,10 @@ class Iro::Strategy
241
241
  return [ 0.99, '1 DTE, must exit' ]
242
242
  end
243
243
 
244
- if ( stock.last - buffer_above_water ) < p.inner.strike
244
+ if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
245
245
  return [ 0.95, "Last #{'%.2f' % stock.last} is " +
246
- "#{'%.2f' % [stock.last - p.inner.strike - buffer_above_water]} " +
247
- "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
246
+ "#{'%.2f' % [stock.last - p.inner.strike - threshold_usd_above_mark]} " +
247
+ "below #{'%.2f' % [p.inner.strike + threshold_usd_above_mark]} water" ]
248
248
  end
249
249
 
250
250
  if p.inner.end_delta < threshold_pos_delta
@@ -258,14 +258,15 @@ class Iro::Strategy
258
258
  return [ 0.33, '-' ]
259
259
  end
260
260
 
261
- ## 2025-10-12 _TODO
261
+ ## 2025-10-12 continue
262
+ ## 2026-04-05 continue
262
263
  def calc_rollp_long_credit_put_spread p
263
264
  stock.reload
264
265
 
265
266
  # puts! p, '#calc_rollp_long_credit_put_spread'
266
267
  # puts! p.inner, 'p.inner'
267
- puts! stock, 'stock'
268
- puts! attributes, 'strategy attributes'
268
+ # puts! stock, 'stock'
269
+ # puts! attributes, 'strategy attributes'
269
270
 
270
271
  if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
271
272
  return [ 0.99, '0 DTE, must exit' ]
@@ -274,24 +275,28 @@ class Iro::Strategy
274
275
  return [ 0.99, '1 DTE, must exit' ]
275
276
  end
276
277
 
277
- if ( stock.last - buffer_above_water ) < p.inner.strike
278
- return [ 0.95, "Last #{'%.2f' % stock.last} is " +
279
- "#{'%.2f' % [stock.last - p.inner.strike - buffer_above_water]} " +
280
- "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
278
+ if ( stock.last - threshold_usd_above_mark ) < p.inner.strike
279
+ return [ 0.95, ":threshold_usd_above_mark <br />Last $#{'%.2f' % stock.last} is " +
280
+ "#{'%.2f' % [stock.last - p.inner.strike]} near #{p.inner.strike} but should be greater than #{threshold_usd_above_mark} ." ]
281
281
  end
282
282
 
283
- if p.inner.end_delta < threshold_pos_delta
284
- return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
283
+ if p.inner.end_delta.abs < threshold_pos_delta
284
+ return [ 0.79, ":threshold_pos_delta <br />Offensive roll: delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} ." ]
285
+ end
286
+ if p.inner.end_delta.abs > threshold_neg_delta
287
+ return [ 0.79, ":threshold_neg_delta <br />Defensive roll: delta #{p.inner.end_delta} is higher than #{threshold_neg_delta} ." ]
285
288
  end
286
289
 
287
- if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
288
- return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
290
+ if threshold_netp.present?
291
+ if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
292
+ return [ 0.51, ":threshold_netp <br />made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
293
+ end
289
294
  end
290
295
 
291
296
  return [ 0.33, '-' ]
292
297
  end
293
298
 
294
- ## _TODO
299
+ ## do not use!
295
300
  def calc_rollp_short_debit_put_spread p
296
301
  stock.reload
297
302
 
@@ -299,10 +304,10 @@ class Iro::Strategy
299
304
  return [ 0.99, "< #{threshold_dte}DTE, must exit" ]
300
305
  end
301
306
 
302
- if stock.last + buffer_above_water > p.inner.strike
307
+ if stock.last + threshold_usd_above_mark > p.inner.strike
303
308
  return [ 0.98, "Last #{'%.2f' % stock.last} is " +
304
- "#{'%.2f' % [stock.last + buffer_above_water - p.inner.strike]} " +
305
- "above #{'%.2f' % [p.inner.strike - buffer_above_water]} water" ]
309
+ "#{'%.2f' % [stock.last + threshold_usd_above_mark - p.inner.strike]} " +
310
+ "above #{'%.2f' % [p.inner.strike - threshold_usd_above_mark]} water" ]
306
311
  end
307
312
 
308
313
  if p.inner.end_delta.abs < threshold_pos_delta.abs
@@ -317,6 +322,7 @@ class Iro::Strategy
317
322
  end
318
323
 
319
324
  ## 2026-02-21 ok
325
+ ## 2026-04-05 ok
320
326
  def calc_rollp_short_credit_call_spread p
321
327
  puts! p, 'calc_rollp_short_credit_call_spread...'
322
328
  stock.reload
@@ -325,10 +331,9 @@ class Iro::Strategy
325
331
  return [ 0.99, "< #{threshold_dte}DTE, must exit" ]
326
332
  end
327
333
 
328
- if stock.last + buffer_above_water > p.inner.strike
329
- return [ 0.95, "Last #{'%.2f' % stock.last} is " +
330
- "#{'%.2f' % [stock.last + buffer_above_water - p.inner.strike]} " +
331
- "above #{'%.2f' % [p.inner.strike - buffer_above_water]} water" ]
334
+ if stock.last + threshold_usd_above_mark > p.inner.strike
335
+ return [ 0.95, ":threshold_usd_above_mark <br />Last $#{'%.2f' % stock.last} is " +
336
+ "#{'%.2f' % [p.inner.strike - stock.last]} near #{p.inner.strike} but should be greater than #{threshold_usd_above_mark} ." ]
332
337
  end
333
338
 
334
339
  ## defensive
@@ -342,8 +347,10 @@ class Iro::Strategy
342
347
  return [ 0.69, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} offensive threshold." ]
343
348
  end
344
349
 
345
- if p.net_percent > threshold_netp
346
- return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ]
350
+ if threshold_netp.present?
351
+ if p.net_percent > threshold_netp
352
+ return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ]
353
+ end
347
354
  end
348
355
 
349
356
  return [ 0.33, '-' ]
@@ -357,9 +364,6 @@ class Iro::Strategy
357
364
  end
358
365
 
359
366
 
360
- # def slug
361
- # "#{kind} #{stock}"
362
- # end
363
367
  def to_s
364
368
  "#{kind} #{stock} #{descr}"
365
369
  end
@@ -18,7 +18,7 @@ class Tda::Order
18
18
  },
19
19
  })
20
20
  puts! results, 'results'
21
- return results
21
+ return results.deep_symbolize_keys
22
22
  end
23
23
 
24
24
  ## not used - the hash is stored
@@ -33,6 +33,70 @@ class Tda::Order
33
33
  puts! results, 'results'
34
34
  end
35
35
 
36
+ def self.get_orders
37
+ profile = Wco::Profile.pi
38
+ today = Time.now.strftime("%Y-%m-%d")
39
+ results = self.get("/accounts/#{profile.schwab_account_hash}/orders", {
40
+ headers: {
41
+ accept: 'application/json',
42
+ Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
43
+ },
44
+ query: {
45
+ fromEnteredTime: "#{today}T00:00:00Z",
46
+ toEnteredTime: "#{today}T23:59:59Z",
47
+ },
48
+ })
49
+ puts! results, 'get_orders() results'
50
+ puts! results.code, 'results.code'
51
+ return results
52
+ end
53
+
54
+ def self.cancel_order!( id )
55
+ profile = Wco::Profile.pi
56
+ results = self.delete("/accounts/#{profile.schwab_account_hash}/orders/#{id}", {
57
+ headers: {
58
+ accept: 'application/json',
59
+ Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
60
+ },
61
+ })
62
+ puts! results, 'cancel_order!() results'
63
+ puts! results.code, 'results.code'
64
+ # if !results.code == '200'
65
+ # throw 'could not cancel order'
66
+ # end
67
+ end
68
+
69
+ def self.credit_spread_q pos
70
+ query = {
71
+ orderType: pos.place2_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
72
+ session: "NORMAL",
73
+ duration: "DAY",
74
+ price: pos.pending_price,
75
+ orderStrategyType: "SINGLE",
76
+ orderLegCollection: [
77
+ ## open
78
+ {
79
+ instruction: "BUY_TO_OPEN",
80
+ quantity: pos.q,
81
+ instrument: {
82
+ symbol: pos.outer.symbol,
83
+ assetType: "OPTION",
84
+ },
85
+ },
86
+ {
87
+ instruction: "SELL_TO_OPEN",
88
+ quantity: pos.q,
89
+ instrument: {
90
+ symbol: pos.inner.symbol,
91
+ assetType: "OPTION",
92
+ },
93
+ },
94
+ ],
95
+ }
96
+ return query
97
+ end
98
+
99
+ ## obsolete, I don't do covered calls anymore?
36
100
  def self.roll_covered_call_q pos
37
101
  roll_price = pos.inner.begin_price - pos.autoprev.inner.end_price
38
102
  query = {
@@ -67,12 +131,12 @@ class Tda::Order
67
131
  return query
68
132
  end
69
133
 
70
- def self.roll_short_credit_call_spread_q pos
134
+ def self.roll_credit_call_spread_q pos
71
135
  query = {
72
- orderType: "NET_CREDIT", ## pos.roll_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
136
+ orderType: pos.roll_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
73
137
  session: "NORMAL",
74
138
  duration: "DAY",
75
- price: ( pos.roll_price + 100 ).to_s, ## _TODO this order will never fill (net credit only)
139
+ price: pos.roll_price.abs.to_s,
76
140
  orderStrategyType: "SINGLE",
77
141
  orderLegCollection: [
78
142
  ## close
@@ -116,8 +180,8 @@ class Tda::Order
116
180
  return query
117
181
  end
118
182
 
119
- def self.place_order query
120
- puts! query, '#place_order'
183
+ def self.place_order! query
184
+ # puts! query, '#place_order'
121
185
 
122
186
  profile = Wco::Profile.pi
123
187
  results = self.post("/accounts/#{profile.schwab_account_hash}/orders", {
@@ -128,8 +192,14 @@ class Tda::Order
128
192
  },
129
193
  body: query.to_json,
130
194
  })
195
+ puts! results, 'place_order!() results'
196
+ puts! results.code, 'results.code'
197
+ # if 201 != results.code
198
+ # throw results
199
+ # end
131
200
  order_id = results.headers['location'].split('/').last
132
- return order_id
201
+ # response = JSON.parse results.body
202
+ return { schwab_order_id: order_id, schwab_status: 'WORKING' }
133
203
  end
134
204
 
135
205
  end
@@ -0,0 +1,15 @@
1
+
2
+ class WcoEmail::Config
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'wco_email_config'
6
+
7
+ field :key
8
+ validates :key, { presence: true, uniqueness: true }
9
+
10
+ field :value
11
+ validates :value, { presence: true }
12
+
13
+ field :descr
14
+
15
+ end
@@ -23,6 +23,9 @@
23
23
  %hr
24
24
  .row
25
25
  .col-6
26
+ .field
27
+ %label schwab_account_hash
28
+ = f.text_field :schwab_account_hash
26
29
  .field
27
30
  %label schwab_access_token
28
31
  = f.text_field :schwab_access_token
@@ -33,6 +36,8 @@
33
36
  %label schwab_id_token
34
37
  = f.text_field :schwab_id_token
35
38
  .col-6
39
+ .field
40
+ %label &nbsp;
36
41
  .field
37
42
  %label schwab_exec_access_token
38
43
  = f.text_field :schwab_exec_access_token
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wco_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.264
4
+ version: 3.1.0.266
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Pudeyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-05 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ahoy_matey
@@ -477,6 +477,7 @@ files:
477
477
  - app/models/iro/datapoint.rb
478
478
  - app/models/iro/date.rb
479
479
  - app/models/iro/option.rb
480
+ - app/models/iro/option.rb-bk
480
481
  - app/models/iro/option_black_scholes.rb-bk
481
482
  - app/models/iro/position.rb
482
483
  - app/models/iro/priceitem.rb
@@ -516,6 +517,7 @@ files:
516
517
  - app/models/wco/utils.rb
517
518
  - app/models/wco/video.rb
518
519
  - app/models/wco_email/campaign.rb
520
+ - app/models/wco_email/config.rb
519
521
  - app/models/wco_email/context.rb
520
522
  - app/models/wco_email/conversation.rb
521
523
  - app/models/wco_email/email_action.rb