wco_models 3.1.0.217 → 3.1.0.219

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: 9e4c1bcf7eeafe1bda8551e1c7c1c0483618c786ea5850c5f04538394f366693
4
- data.tar.gz: 06f8dc9e84dacad4bd86eaa29defc48fc0afe3c7870cfe29485dca5b1e0a85b5
3
+ metadata.gz: 0771e7abb2c028200fec0881fecde76c2e315226aff2ef2495736d6264018445
4
+ data.tar.gz: bb24df8d57814e1888aad0cc4c17c9a7f60fbf9eb429fcd5e290b6c805621fd4
5
5
  SHA512:
6
- metadata.gz: 7e76234796b08f1a79d3eb8422794b3aeec20265b5bd11b44e461473bae3b4290be84eb7c3300b4c0c3968563bccbbcec55cf8b207efb493d0492fc7f972c2de
7
- data.tar.gz: 7c942a638cda199228832c5af05792acea077ca6a9049af39280c6410caf1a1dc3713f61ceba473e1bde8ad9ef32a4ceee1693d12207e065b467a00e59a13526
6
+ metadata.gz: a76367c163c8f41036b8d62dfb3eae45bf2f6070c90c74825eaba8e073c0143e5a51a83c7f928060f245fd1b7acc44d03ab5196dfae5807224aa57fa83fa6f2b
7
+ data.tar.gz: efcb62dd0cce5526aeeb4d682808728bd27acd2b460525f308959e166b44c53875c6e6bacd17678a2c78d227d0fa0a70584ad245027d2ea8ff399ae354aff0c4
@@ -26,6 +26,14 @@ class Wco::OfficeActionsController < Wco::ApplicationController
26
26
  redirect_to action: :index
27
27
  end
28
28
 
29
+ def do_run
30
+ @oa = OA.find params[:id]
31
+ authorize! :do_run, @oa
32
+ out = @oa.do_run
33
+ puts! out, 'do_run office action'
34
+ redirect_to request.referrer
35
+ end
36
+
29
37
  def edit
30
38
  @oa = OA.find params[:id]
31
39
  authorize! :edit, @oa
@@ -3,7 +3,7 @@ class Iro::Option
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
5
  include Mongoid::Paranoia
6
- include Iro::OptionBlackScholes
6
+ # include Iro::OptionBlackScholes
7
7
  store_in collection: 'iro_options'
8
8
 
9
9
  attr_accessor :recompute
@@ -1,4 +1,9 @@
1
1
 
2
+ ##
3
+ ## This doesn't work because gems gsl, rb-gsl are messing up
4
+ ## I'd prefer to write this in python, and restfully integrate.
5
+ ##
6
+
2
7
  require 'distribution'
3
8
  N = Distribution::Normal
4
9
 
@@ -30,7 +30,12 @@ class Iro::Position
30
30
  delegate :ticker, to: :stock
31
31
 
32
32
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
33
- delegate :put_call, to: :strategy
33
+
34
+ ## no: the strategy can be wheel, and position is put-spread.
35
+ # delegate :put_call, to: :strategy
36
+ field :put_call, type: :string
37
+ validates :put_call, presence: true
38
+
34
39
  delegate :long_or_short, to: :strategy
35
40
  delegate :credit_or_debit, to: :strategy
36
41
 
@@ -103,8 +108,13 @@ class Iro::Position
103
108
  net_amount / max_gain
104
109
  end
105
110
  def net_amount # each
106
- strategy.send("net_amount_#{strategy.kind}", self)
111
+ self.send("net_amount_#{strategy.kind}")
107
112
  end
113
+ ## 2025-10-14 tested
114
+ def net_amount_long_credit_put_spread ## each
115
+ inner.begin_price - outer.begin_price + outer.end_price - inner.end_price
116
+ end
117
+
108
118
  def max_gain # each
109
119
  strategy.send("max_gain_#{strategy.kind}", self)
110
120
  end
@@ -159,6 +169,7 @@ class Iro::Position
159
169
  elsif 'PUT' == pos.put_call
160
170
  outs = outs.reverse
161
171
  end
172
+ puts! outs, '#calc_nxt.outs -> 2'
162
173
 
163
174
  ## next_inner_strike
164
175
  outs = outs.select do |out|
@@ -171,7 +182,7 @@ class Iro::Position
171
182
  out[:strikePrice] <= strategy.next_inner_strike
172
183
  end
173
184
  else
174
- raise 'zz3 - @TODO: implement, debit spreads'
185
+ raise 'zt3 - @TODO: implement, debit spreads'
175
186
  end
176
187
  end
177
188
  puts! outs[0][:strikePrice], 'after calc next_inner_strike'
@@ -184,7 +195,7 @@ class Iro::Position
184
195
  elsif Iro::Strategy::LONG == pos.long_or_short
185
196
  out[:strikePrice] < strategy.stock.last - strategy.next_buffer_above_water
186
197
  else
187
- raise 'zz4 - this cannot happen'
198
+ raise 'zt4 - this cannot happen'
188
199
  end
189
200
  end
190
201
  puts! outs[0][:strikePrice], 'after calc next_buffer_above_water'
@@ -199,7 +210,7 @@ class Iro::Position
199
210
  out_delta = out[:delta] rescue 0
200
211
  out_delta <= strategy.next_inner_delta
201
212
  else
202
- raise 'zz5 - this cannot happen'
213
+ raise 'zt5 - this cannot happen'
203
214
  end
204
215
  end
205
216
  puts! outs[0][:strikePrice], 'after calc next_inner_delta'
@@ -238,7 +249,8 @@ class Iro::Position
238
249
  pos.autonxt ||= Iro::Position.new
239
250
  pos.autonxt.update({
240
251
  prev_gain_loss_amount: 'a',
241
- status: 'proposed',
252
+ put_call: pos.put_call,
253
+ status: 'proposed',
242
254
  stock: strategy.stock,
243
255
  inner: inner_,
244
256
  outer: outer_,
@@ -1,6 +1,6 @@
1
1
 
2
- require 'distribution'
3
- N = Distribution::Normal
2
+ # require 'distribution'
3
+ # N = Distribution::Normal
4
4
 
5
5
  class Iro::Purse
6
6
  include Mongoid::Document
@@ -4,7 +4,7 @@ require 'business_time'
4
4
  ##
5
5
  ## https://www.macrotrends.net/stocks/charts/META/meta-platforms/stock-price-history
6
6
  ##
7
- class Iro::Stock
7
+ class ::Iro::Stock
8
8
  include Mongoid::Document
9
9
  include Mongoid::Timestamps
10
10
  include Mongoid::Paranoia
@@ -29,10 +29,10 @@ class Iro::Stock
29
29
 
30
30
  field :stdev, type: :float
31
31
 
32
- has_many :positions, class_name: 'Iro::Position', inverse_of: :stock
33
- has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :stock
34
- # has_many :purses, class_name: 'Iro::Purse', inverse_of: :stock
35
- has_many :options, class_name: 'Iro::Option', inverse_of: :stock
32
+ has_many :positions, class_name: '::Iro::Position', inverse_of: :stock
33
+ has_many :strategies, class_name: '::Iro::Strategy', inverse_of: :stock
34
+ # has_many :purses, class_name: '::Iro::Purse', inverse_of: :stock
35
+ has_many :options, class_name: '::Iro::Option', inverse_of: :stock
36
36
  has_many :priceitems, inverse_of: :stock
37
37
 
38
38
  belongs_to :profile, class_name: 'Wco::Profile', optional: true
@@ -79,7 +79,7 @@ class Iro::Stock
79
79
 
80
80
  stock = self
81
81
  begin_on = Time.now - duration - 1.day
82
- points = Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
82
+ points = ::Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
83
83
  :date.gte => begin_on,
84
84
  ).order_by( date: :asc )
85
85
 
@@ -17,45 +17,48 @@ class Iro::Strategy
17
17
 
18
18
  CREDIT = 'credit'
19
19
  DEBIT = 'debit'
20
- field :credit_or_debit, type: :string
20
+ field :credit_or_debit, type: :string, default: 'credit'
21
21
  validates :credit_or_debit, presence: true
22
22
 
23
23
 
24
- has_many :positions, class_name: 'Iro::Position', inverse_of: :strategy
25
- has_one :next_position, class_name: 'Iro::Position', inverse_of: :next_strategy
26
- belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
24
+ has_many :positions, class_name: 'Iro::Position', inverse_of: :strategy, dependent: :destroy
25
+ has_one :next_position, class_name: 'Iro::Position', inverse_of: :next_strategy ## _TODO: makes no sense...
26
+ belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
27
27
  # has_and_belongs_to_many :purses, class_name: 'Iro::Purse', inverse_of: :strategies
28
28
 
29
- # KIND_COVERED_CALL = 'covered_call'
30
- # KIND_IRON_CONDOR = 'iron_condor'
31
- # KIND_LONG_CREDIT_PUT_SPREAD = 'long_credit_put_spread'
32
- # KIND_LONG_DEBIT_CALL_SPREAD = 'long_debit_call_spread'
33
- # KIND_SHORT_CREDIT_CALL_SPREAD = 'short_credit_call_spread'
34
- # KIND_SHORT_DEBIT_PUT_SPREAD = 'short_debit_put_spread'
35
- # KINDS = [ nil,
36
- # KIND_COVERED_CALL,
37
- # KIND_IRON_CONDOR,
38
- # KIND_LONG_CREDIT_PUT_SPREAD,
39
- # KIND_LONG_DEBIT_CALL_SPREAD,
40
- # KIND_SHORT_CREDIT_CALL_SPREAD,
41
- # KIND_SHORT_DEBIT_PUT_SPREAD,
42
- # ];
43
- KIND_SPREAD = 'spread'
44
- KIND_WHEEL = 'wheel'
29
+ KIND_COVERED_CALL = 'covered_call'
30
+ KIND_IRON_CONDOR = 'iron_condor'
31
+ KIND_LONG_CREDIT_PUT_SPREAD = 'long_credit_put_spread'
32
+ KIND_LONG_DEBIT_CALL_SPREAD = 'long_debit_call_spread'
33
+ KIND_SHORT_CREDIT_CALL_SPREAD = 'short_credit_call_spread'
34
+ KIND_SHORT_DEBIT_PUT_SPREAD = 'short_debit_put_spread'
35
+ ## these are too simple and deprecated:
36
+ KIND_SPREAD = 'spread' ## @deprecated, be specific
37
+ KIND_WHEEL = 'wheel' ## @deprecated, be specific
38
+ KINDS = [ nil,
39
+ KIND_COVERED_CALL,
40
+ KIND_IRON_CONDOR,
41
+ KIND_LONG_CREDIT_PUT_SPREAD,
42
+ KIND_LONG_DEBIT_CALL_SPREAD,
43
+ KIND_SHORT_CREDIT_CALL_SPREAD,
44
+ KIND_SHORT_DEBIT_PUT_SPREAD,
45
+ KIND_SPREAD,
46
+ KIND_WHEEL,
47
+ ];
45
48
  field :kind
46
49
 
47
50
  def put_call
48
51
  case kind
49
- # when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD
50
- # put_call = 'PUT'
51
- # when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
52
- # put_call = 'CALL'
53
- # when Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD
54
- # put_call = 'CALL'
55
- # when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
56
- # put_call = 'PUT'
57
- # when Iro::Strategy::KIND_COVERED_CALL
58
- # put_call = 'CALL'
52
+ when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD
53
+ put_call = 'PUT'
54
+ when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
55
+ put_call = 'CALL'
56
+ when Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD
57
+ put_call = 'CALL'
58
+ when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
59
+ put_call = 'PUT'
60
+ when Iro::Strategy::KIND_COVERED_CALL
61
+ put_call = 'CALL'
59
62
  when Iro::Strategy::KIND_SPREAD
60
63
  if credit_or_debit == CREDIT
61
64
  if long_or_short == LONG
@@ -63,20 +66,20 @@ class Iro::Strategy
63
66
  elsif long_or_short == SHORT
64
67
  'CALL'
65
68
  else
66
- throw 'zz5 - should never happen'
69
+ throw 'zq5 - should never happen'
67
70
  end
68
71
  else
69
- throw 'zz6 - debit spreads are not implemented'
72
+ throw 'zq6 - debit spreads are not implemented'
70
73
  end
71
- when Iro::Strategy::KIND_WHEEL
72
- 'CALL'
73
74
  else
74
- throw 'zz9 - this should never happen'
75
+ # put_call = 'zq9-ERROR'
76
+ throw 'zq9 - this should never happen'
75
77
  end
76
78
  end
77
79
 
78
- field :threshold_buffer_above_water, type: :float
79
- field :threshold_delta, type: :float
80
+ field :buffer_above_water, type: :float
81
+ field :threshold_pos_delta, type: :float # best-case scenario: roll b/c markets are going my way
82
+ field :threshold_neg_delta, type: :float # nightmare scenario: defensively rolling
80
83
  field :threshold_netp, type: :float
81
84
  field :threshold_dte, type: :integer, default: 1
82
85
 
@@ -88,16 +91,15 @@ class Iro::Strategy
88
91
  field :next_buffer_above_water, type: :float
89
92
 
90
93
 
91
-
92
-
93
-
94
-
95
94
  def begin_delta_wheel p
96
95
  p.inner.begin_delta
97
96
  end
98
97
  def begin_delta_spread p
99
98
  p.inner.begin_delta - p.outer.begin_delta
100
99
  end
100
+ def begin_delta_long_credit_put_spread p
101
+ begin_delta_spread p
102
+ end
101
103
 
102
104
 
103
105
  def breakeven_covered_call p
@@ -115,26 +117,29 @@ class Iro::Strategy
115
117
  def end_delta_spread p
116
118
  p.inner.end_delta - p.outer.end_delta
117
119
  end
120
+ def end_delta_long_credit_put_spread p
121
+ end_delta_spread p
122
+ end
118
123
 
119
124
 
120
- # def max_gain_covered_call p
121
- # p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really?
122
- # end
123
- # # def max_gain_long_credit_put_spread p
124
- # ## 100 * disallowed for gameui
125
- # p.inner.begin_price - p.outer.begin_price
126
- # end
127
- # def max_gain_long_debit_call_spread p
128
- # ## 100 * disallowed for gameui
129
- # ( p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
130
- # end
131
- # def max_gain_short_credit_call_spread p
132
- # p.inner.begin_price - p.outer.begin_price
133
- # end
134
- # def max_gain_short_debit_put_spread p
135
- # ## 100 * disallowed for gameui
136
- # ( p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
137
- # end
125
+ def max_gain_covered_call p
126
+ p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really?
127
+ end
128
+ def max_gain_long_credit_put_spread p
129
+ ## 100 * disallowed for gameui
130
+ p.inner.begin_price - p.outer.begin_price
131
+ end
132
+ def max_gain_long_debit_call_spread p
133
+ ## 100 * disallowed for gameui
134
+ ( p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
135
+ end
136
+ def max_gain_short_credit_call_spread p
137
+ p.inner.begin_price - p.outer.begin_price
138
+ end
139
+ def max_gain_short_debit_put_spread p
140
+ ## 100 * disallowed for gameui
141
+ ( p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
142
+ end
138
143
  def max_gain_spread p
139
144
  ## 100 * disallowed for gameui
140
145
  ( p.outer.strike - p.inner.strike ).abs - p.outer.begin_price + p.inner.begin_price # - 2*0.66
@@ -144,21 +149,21 @@ class Iro::Strategy
144
149
  end
145
150
 
146
151
 
147
- # def max_loss_covered_call p
148
- # p.inner.begin_price*10 # just suppose 10,000%
149
- # end
150
- # def max_loss_long_credit_put_spread p
151
- # out = p.inner.strike - p.outer.strike
152
- # end
153
- # def max_loss_long_debit_call_spread p
154
- # out = p.outer.strike - p.inner.strike
155
- # end
156
- # def max_loss_short_debit_put_spread p # different
157
- # out = p.inner.strike - p.outer.strike
158
- # end
159
- # def max_loss_short_credit_call_spread p
160
- # out = p.outer.strike - p.inner.strike
161
- # end
152
+ def max_loss_covered_call p
153
+ p.inner.begin_price*10 # just suppose 10,000%
154
+ end
155
+ def max_loss_long_credit_put_spread p
156
+ out = p.inner.strike - p.outer.strike
157
+ end
158
+ def max_loss_long_debit_call_spread p
159
+ out = p.outer.strike - p.inner.strike
160
+ end
161
+ def max_loss_short_debit_put_spread p # different
162
+ out = p.inner.strike - p.outer.strike
163
+ end
164
+ def max_loss_short_credit_call_spread p
165
+ out = p.outer.strike - p.inner.strike
166
+ end
162
167
  def max_loss_spread p
163
168
  ( p.outer.strike - p.inner.strike ).abs
164
169
  end
@@ -171,15 +176,20 @@ class Iro::Strategy
171
176
  def net_amount_spread p
172
177
  p.inner.begin_price - p.inner.end_price
173
178
  end
179
+ def net_amount_long_credit_put_spread p
180
+ p.inner.begin_price - p.inner.end_price
181
+ end
174
182
 
175
183
 
176
184
  ## 2024-05-09 @TODO
185
+ ## 2025-10-11 _TODO
177
186
  def next_inner_strike_on expires_on
178
- outs = Tda::Option.get_quotes({
187
+ outs = ::Tda::Option.get_quotes({
179
188
  contractType: put_call,
180
189
  expirationDate: expires_on,
181
190
  ticker: stock.ticker,
182
191
  })
192
+ puts! outs, 'next_inner_strike_on -> outs'
183
193
  end
184
194
 
185
195
 
@@ -200,8 +210,8 @@ class Iro::Strategy
200
210
  "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
201
211
  end
202
212
 
203
- if p.inner.end_delta < threshold_delta
204
- return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
213
+ if p.inner.end_delta < threshold_pos_delta
214
+ return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
205
215
  end
206
216
 
207
217
  if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
@@ -227,8 +237,39 @@ class Iro::Strategy
227
237
  "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
228
238
  end
229
239
 
230
- if p.inner.end_delta < threshold_delta
231
- return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
240
+ if p.inner.end_delta < threshold_pos_delta
241
+ return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
242
+ end
243
+
244
+ if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
245
+ return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
246
+ end
247
+
248
+ return [ 0.33, '-' ]
249
+ end
250
+
251
+ ## 2025-10-12 _TODO
252
+ def calc_rollp_long_credit_put_spread p
253
+ # puts! p, '#calc_rollp_long_credit_put_spread'
254
+ # puts! p.inner, 'p.inner'
255
+ puts! stock, 'stock'
256
+ puts! attributes, 'strategy attributes'
257
+
258
+ if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
259
+ return [ 0.99, '0 DTE, must exit' ]
260
+ end
261
+ if ( p.expires_on.to_date - Time.now.to_date ).to_i < 2
262
+ return [ 0.99, '1 DTE, must exit' ]
263
+ end
264
+
265
+ if ( stock.last - buffer_above_water ) < p.inner.strike
266
+ return [ 0.95, "Last #{'%.2f' % stock.last} is " +
267
+ "#{'%.2f' % [stock.last - p.inner.strike - buffer_above_water]} " +
268
+ "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
269
+ end
270
+
271
+ if p.inner.end_delta < threshold_pos_delta
272
+ return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
232
273
  end
233
274
 
234
275
  if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
@@ -251,8 +292,8 @@ class Iro::Strategy
251
292
  "above #{'%.2f' % [p.inner.strike - buffer_above_water]} water" ]
252
293
  end
253
294
 
254
- if p.inner.end_delta.abs < threshold_delta.abs
255
- return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
295
+ if p.inner.end_delta.abs < threshold_pos_delta.abs
296
+ return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_pos_delta} threshold." ]
256
297
  end
257
298
 
258
299
  if p.net_percent > threshold_netp
@@ -0,0 +1,283 @@
1
+
2
+ require 'httparty'
3
+
4
+ =begin
5
+ class Schwab
6
+ include HTTParty
7
+ debug_output $stdout
8
+ base_uri 'https://api.schwabapi.com/marketdata/v1'
9
+ end
10
+ =end
11
+
12
+ class Tda::Option
13
+
14
+ include ::HTTParty
15
+ debug_output $stdout
16
+ # base_uri 'https://api.tdameritrade.com'
17
+ base_uri 'https://api.schwabapi.com/marketdata/v1'
18
+
19
+
20
+ ##
21
+ ## Get entire chains for a ticker
22
+ ## params: { ticker, force }
23
+ ##
24
+ ## 2024-08-09 :: Continue
25
+ ## 2024-08-21 :: Continue : )
26
+ ##
27
+ def self.get_chains params
28
+ filename = "./data/schwab/#{Time.now.to_date.to_s}-#{params[:ticker]}-chains.json"
29
+ if !params[:force] && File.exists?(filename)
30
+ return JSON.parse File.read filename
31
+
32
+ else
33
+ profile = Wco::Profile.find_by email: 'piousbox@gmail.com'
34
+ query = { symbol: params[:ticker] } ## use 'GME' as symbol here even though a symbol is eg 'GME_021023P2.5'
35
+ # puts! query, 'query'
36
+
37
+ headers = {
38
+ accept: 'application/json',
39
+ Authorization: "Bearer #{profile[:schwab_access_token]}",
40
+ }
41
+ path = "/chains"
42
+ out = self.get path, {
43
+ headers: headers,
44
+ query: query }
45
+ timestamp = DateTime.parse out.headers['date']
46
+ out = out.parsed_response
47
+ puts! out, 'outs'
48
+
49
+ outs = []
50
+ %w| put call |.each do |contractType|
51
+ _out = out["#{contractType}ExpDateMap"]
52
+ _out.each do |date, vs| ## date="2023-02-10:5"
53
+ vs.each do |strike, _v| ## strike="18.5"
54
+ _v = _v[0] ## weird, keep
55
+ # puts! _v, '_v'
56
+
57
+ v = {
58
+ putCall: _v['putCall'],
59
+ symbol: _v['symbol'],
60
+ bid: _v['bid'],
61
+ ask: _v['ask'],
62
+ last: _v['last'],
63
+ totalVolume: _v['totalVolume'],
64
+ openInterest: _v['openInterest'],
65
+ strikePrice: _v['strikePrice'],
66
+ expirationDate: _v['expirationDate'],
67
+ }
68
+ v.each do |k, i|
69
+ if i == 'NaN'
70
+ v[k] = nil
71
+ end
72
+ end
73
+
74
+ v[:timestamp] = timestamp
75
+ v[:ticker] = params[:ticker]
76
+ outs.push( v )
77
+ end
78
+ end
79
+ end
80
+
81
+ outs.each do |out|
82
+ opi = ::Iro::Priceitem.create( out )
83
+ if !opi.persisted?
84
+ puts! opi.errors.full_messages, "Cannot create PriceItem"
85
+ end
86
+ end
87
+
88
+ File.write filename, out.to_json
89
+ return out
90
+ end
91
+ end
92
+
93
+ ##
94
+ ## 2023-03-18 _vp_ This is what I should be using to check if a position should be rolled.
95
+ ##
96
+ def self.get_quote params
97
+ OpenStruct.new ::Tda::Option.get_quotes(params)[0]
98
+ end
99
+
100
+ ##
101
+ ## params: contractType, strike, expirationDate, ticker
102
+ ##
103
+ ## ow = { contractType: 'PUT', ticker: 'GME', date: '2022-12-09' }
104
+ ## query = {:apikey=>"<>", :toDate=>"2022-12-09", :fromDate=>"2022-12-09", :symbol=>"GME"}
105
+ ##
106
+ ## 2023-02-04 _vp_ :: Too specific, but I want the entire chain, every 1-min
107
+ ## 2023-02-06 _vp_ :: Continue.
108
+ ##
109
+ def self.get_quotes params
110
+ puts! params, 'Tda::Option#get_quotes'
111
+
112
+ profile = Wco::Profile.find_by email: 'piousbox@gmail.com'
113
+ opts = {}
114
+
115
+ #
116
+ # Validate input ???
117
+ #
118
+ validOpts = %i| contractType |
119
+ validOpts.each do |s|
120
+ if params[s]
121
+ opts[s] = params[s]
122
+ else
123
+ raise Iro::InputError.new("Invalid input, missing '#{s}'.")
124
+ end
125
+ end
126
+ if params[:expirationDate]
127
+ opts[:fromDate] = opts[:toDate] = params[:expirationDate].to_s[0...10]
128
+ else
129
+ raise Iro::InputError.new("Invalid input, missing 'expirationDate'.")
130
+ end
131
+ if params[:ticker]
132
+ opts[:symbol] = params[:ticker].upcase
133
+ else
134
+ raise Iro::InputError.new("Invalid input, missing 'ticker'.")
135
+ end
136
+
137
+ if params[:strike]
138
+ opts[:strike] = params[:strike]
139
+ end
140
+
141
+ query = { }.merge opts
142
+ puts! query, 'input opts'
143
+
144
+ headers = {
145
+ accept: 'application/json',
146
+ Authorization: "Bearer #{profile[:schwab_access_token]}",
147
+ }
148
+
149
+ path = "/chains"
150
+ out = self.get path, {
151
+ # basic_auth: { username: SCHWAB_DATA[:key], password: SCHWAB_DATA[:secret] },
152
+ headers: headers,
153
+ query: query,
154
+ }
155
+ puts! out, 'out'
156
+ timestamp = DateTime.parse out.headers['date']
157
+ out = out.parsed_response.deep_symbolize_keys
158
+
159
+
160
+ tmp_sym = "#{opts[:contractType].to_s.downcase}ExpDateMap".to_sym
161
+ outs = []
162
+ out = out[tmp_sym]
163
+ out.each do |date, vs|
164
+ vs.each do |strike, _v|
165
+ v = _v[0]
166
+ v = v.except( :lastSize, :optionDeliverablesList, :settlementType,
167
+ :deliverableNote, :pennyPilot, :mini )
168
+ v[:timestamp] = timestamp
169
+ outs.push( v )
170
+ end
171
+ end
172
+
173
+ # puts! outs, 'outs'
174
+ return outs
175
+ end
176
+
177
+
178
+ def self.close_credit_call
179
+ end
180
+ def self.close_long_debit_call_spread
181
+ end
182
+ def self.close_short_debit_put_spread
183
+ end
184
+
185
+ def self.get_token
186
+ opts = {
187
+ grant_type: 'authorization_code',
188
+ access_type: 'offline',
189
+ code: ::TD_AMERITRADE[:code],
190
+ }
191
+ end
192
+
193
+ def self.create_credit_call outer:, inner:, q:, price:
194
+ query = {
195
+ orderType: "NET_DEBIT",
196
+ session: "NORMAL",
197
+ price: price,
198
+ duration: "DAY",
199
+ orderStrategyType: "SINGLE",
200
+ orderLegCollection: [
201
+ {
202
+ instruction: "BUY_TO_OPEN",
203
+ quantity: q,
204
+ instrument: {
205
+ symbol: outer.symbol,
206
+ assetType: "OPTION",
207
+ },
208
+ },
209
+ {
210
+ instruction: "SELL_TO_OPEN",
211
+ quantity: q,
212
+ instrument: {
213
+ symbol: inner.symbol,
214
+ assetType: "OPTION",
215
+ },
216
+ },
217
+ ],
218
+ }
219
+ File.write('tmp/query.json', JSON.pretty_generate( query ))
220
+ puts! query, 'query'
221
+
222
+ return
223
+
224
+ headers = {
225
+ Authorize: "Bearer #{::TD_AMERITRADE[:access_token]}",
226
+ }
227
+
228
+ path = "/v1/accounts/#{::TD_AMERITRADE[:accountId]}/orders"
229
+ puts! path, 'path'
230
+ out = self.post path, { query: query, headers: headers }
231
+ timestamp = DateTime.parse out.headers['date']
232
+ out = out.parsed_response.deep_symbolize_keys
233
+ puts! out, 'created credit call?'
234
+ end
235
+ def self.create_long_debit_call_spread
236
+ end
237
+ def self.create_short_debit_put_spread
238
+ end
239
+
240
+ def self.roll_credit_call
241
+ end
242
+ def self.roll_long_debit_call_spread
243
+ end
244
+ def self.roll_short_debit_put_spread
245
+ end
246
+
247
+
248
+ end
249
+
250
+ ##
251
+ ## From: https://developer.tdameritrade.com/content/place-order-samples
252
+ ## Buy Limit: Vertical Call Spread
253
+ ##
254
+ =begin
255
+ {
256
+ "orderType": "NET_DEBIT",
257
+ "session": "NORMAL",
258
+ "price": "1.20",
259
+ "duration": "DAY",
260
+ "orderStrategyType": "SINGLE",
261
+ "orderLegCollection": [
262
+ {
263
+ "instruction": "BUY_TO_OPEN",
264
+ "quantity": 10,
265
+ "instrument": {
266
+ "symbol": "XYZ_011516C40",
267
+ "assetType": "OPTION"
268
+ }
269
+ },
270
+ {
271
+ "instruction": "SELL_TO_OPEN",
272
+ "quantity": 10,
273
+ "instrument": {
274
+ "symbol": "XYZ_011516C42.5",
275
+ "assetType": "OPTION"
276
+ }
277
+ }
278
+ ]
279
+ }
280
+ =end
281
+
282
+
283
+
@@ -0,0 +1,59 @@
1
+
2
+ require 'httparty'
3
+
4
+ class Tda::Stock
5
+ include ::HTTParty
6
+ base_uri 'https://api.schwabapi.com/marketdata/v1'
7
+
8
+ ## alias
9
+ def self.get_quote which
10
+ self.get_quotes( which )[0]
11
+ end
12
+
13
+ ## tickers = "GME"
14
+ ## tickers = "NVDA,GME"
15
+ def self.get_quotes tickers
16
+ profile = Wco::Profile.find_by email: 'piousbox@gmail.com'
17
+
18
+ path = "/quotes"
19
+ headers = {
20
+ accept: 'application/json',
21
+ Authorization: "Bearer #{profile.schwab_access_token}",
22
+ }
23
+ inns = self.get path, { headers: headers, query: { symbols: tickers } }
24
+ inns = inns.parsed_response
25
+ puts! inns, 'parsed response'
26
+
27
+ if [ NilClass, String ].include?( inns.class )
28
+ return []
29
+ end
30
+ inns.each do |k, v|
31
+ inns[k] = v.deep_symbolize_keys
32
+ end
33
+ outs = []
34
+ inns.each do |symbol, _obj|
35
+ obj = _obj[:quote]
36
+ outs.push ::Iro::Priceitem.create!({
37
+ putCall: 'STOCK',
38
+ symbol: symbol,
39
+ ticker: symbol,
40
+ bid: obj[:bidPrice],
41
+ bidSize: obj[:bidSize],
42
+ ask: obj[:askPrice],
43
+ askSize: obj[:askSize],
44
+ last: obj[:lastPrice],
45
+ openPrice: obj[:openPrice],
46
+ closePrice: obj[:closePrice],
47
+ highPrice: obj[:highPrice],
48
+ lowPrice: obj[:lowPrice],
49
+ timestamp: Time.at( obj[:quoteTime]/1000 ),
50
+ totalVolume: obj[:totalVolume],
51
+ mark: obj[:mark],
52
+ exchangeName: obj[:exchangeName],
53
+ volatility: obj[:volatility],
54
+ })
55
+ end
56
+ return outs
57
+ end
58
+
59
+ end
@@ -35,7 +35,7 @@ class Wco::Leadset
35
35
 
36
36
 
37
37
  has_many :appliances, class_name: '::WcoHosting::Appliance', inverse_of: :leadset
38
- has_many :appliance_tmpls, class_name: 'Wco::Price', inverse_of: :leadset
38
+ has_many :appliance_tmpl_prices, class_name: 'Wco::Price', inverse_of: :leadset
39
39
 
40
40
  has_many :environments, class_name: '::WcoHosting::Environment', inverse_of: :leadset
41
41
  has_many :invoices, class_name: 'Wco::Invoice'
@@ -227,7 +227,6 @@ class WcoEmail::MessageStub
227
227
  else
228
228
  @message.apply_filter( filter )
229
229
  end
230
-
231
230
  end
232
231
  end
233
232
 
@@ -3,11 +3,13 @@
3
3
  %ul
4
4
  - oas.each do |oa|
5
5
  %li.d-flex
6
+ = button_to 'x', office_action_path(oa), method: :delete, data: { confirm: 'Are you sure?' }
7
+ &nbsp;
6
8
  = link_to '[~]', edit_office_action_path(oa)
7
9
  &nbsp;
8
10
  = link_to oa.slug, office_action_path(oa)
9
11
  &nbsp;
10
- = button_to 'x', office_action_path(oa), method: :delete, data: { confirm: 'Are you sure?' }
12
+ = button_to 'do run', run_office_action_path(oa), data: { confirm: 'Are you sure?' }
11
13
  %ul
12
14
  %li
13
15
  .gray.mini= oa.id
@@ -23,7 +23,7 @@
23
23
 
24
24
  .field
25
25
  %label leadset
26
- = f.select :leadset, options_for_select(Wco::Leadset.list, selected: profile.leadset )
26
+ = f.select :leadset, options_for_select(Wco::Leadset.list, selected: profile.leadset_id )
27
27
 
28
28
  .actions
29
29
  = f.submit 'Go'
@@ -7,3 +7,18 @@ Stripe.api_version = '2020-08-27'
7
7
 
8
8
  PI_DRUP_PROD_USERNAME ||= 'test-1@piousbox.com'
9
9
  PI_DRUP_PROD_PASSWD ||= 'KSUisl321,'
10
+
11
+ SCHWAB_DATA = {
12
+ # key: 'epK2snmDATP8Rt8Z61drW7pp7bbA0Jxd', ## exec
13
+ # secret: 'GaIGXzwxsEc69S4w', ## exec
14
+
15
+ key: 'AytEXpTMmzmAHaKDlcvu5uYjqQvdHinz', ## market data
16
+ secret: '9zYIcBxPLzDFqEhN', ## market data
17
+
18
+ access_token: 'I0.b2F1dGgyLmNkYy5zY2h3YWIuY29t.ZOtBvya0gEzxBQAjbaFoABoYhRsrvEEWPF3yswUVuZg@',
19
+ # redirect_url: 'https://email.wasya.co/trading/api/oauth2-redirect.html',
20
+ redirect_url: 'http://email.local:3002/trading/api/oauth2-redirect.html',
21
+ }
22
+
23
+ TEST_SCHWAB_ACCESS_TOKEN = "I0.b2F1dGgyLmJkYy5zY2h3YWIuY29t.mf4P-9r_E8x6sm4-lkoWRfBB8n0hXYv7O-O08t21f2s@"
24
+
data/config/routes.rb CHANGED
@@ -48,6 +48,8 @@ Wco::Engine.routes.draw do
48
48
  post 'office_action_templates/:id/perform', to: 'office_action_templates#perform', as: :oat_perform
49
49
  get 'office_action_templates/:id/perform', to: 'office_action_templates#perform'
50
50
  resources :office_action_templates
51
+
52
+ post 'office_actions/:id/run', to: 'office_actions#do_run', as: :run_office_action
51
53
  resources :office_actions
52
54
 
53
55
  resources :prices
data/lib/wco_models.rb CHANGED
@@ -30,6 +30,8 @@ ACTIVE = 'active'
30
30
  INACTIVE = 'inactive'
31
31
  STATUSES = [ nil, ACTIVE, INACTIVE ]
32
32
 
33
+ module Iro; end
34
+ module Tda; end
33
35
  module Wco; end
34
36
 
35
37
  module WcoEmail
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.217
4
+ version: 3.1.0.219
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Pudeyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-24 00:00:00.000000000 Z
11
+ date: 2025-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ahoy_matey
@@ -464,12 +464,14 @@ files:
464
464
  - app/models/iro/datapoint.rb
465
465
  - app/models/iro/date.rb
466
466
  - app/models/iro/option.rb
467
- - app/models/iro/option_black_scholes.rb
467
+ - app/models/iro/option_black_scholes.rb-bk
468
468
  - app/models/iro/position.rb
469
469
  - app/models/iro/priceitem.rb
470
470
  - app/models/iro/purse.rb
471
471
  - app/models/iro/stock.rb
472
472
  - app/models/iro/strategy.rb
473
+ - app/models/tda/option.rb
474
+ - app/models/tda/stock.rb
473
475
  - app/models/wco/asset.rb
474
476
  - app/models/wco/gallery.rb
475
477
  - app/models/wco/headline.rb