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 +4 -4
- data/app/controllers/wco/office_actions_controller.rb +8 -0
- data/app/models/iro/option.rb +1 -1
- data/app/models/iro/{option_black_scholes.rb → option_black_scholes.rb-bk} +5 -0
- data/app/models/iro/position.rb +18 -6
- data/app/models/iro/purse.rb +2 -2
- data/app/models/iro/stock.rb +6 -6
- data/app/models/iro/strategy.rb +122 -81
- data/app/models/tda/option.rb +283 -0
- data/app/models/tda/stock.rb +59 -0
- data/app/models/wco/leadset.rb +1 -1
- data/app/models/wco_email/message_stub.rb +0 -1
- data/app/views/wco/office_actions/_index.haml +3 -1
- data/app/views/wco/profiles/_form.haml +1 -1
- data/config/initializers/08_integrations.rb +15 -0
- data/config/routes.rb +2 -0
- data/lib/wco_models.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0771e7abb2c028200fec0881fecde76c2e315226aff2ef2495736d6264018445
|
|
4
|
+
data.tar.gz: bb24df8d57814e1888aad0cc4c17c9a7f60fbf9eb429fcd5e290b6c805621fd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/app/models/iro/option.rb
CHANGED
data/app/models/iro/position.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
252
|
+
put_call: pos.put_call,
|
|
253
|
+
status: 'proposed',
|
|
242
254
|
stock: strategy.stock,
|
|
243
255
|
inner: inner_,
|
|
244
256
|
outer: outer_,
|
data/app/models/iro/purse.rb
CHANGED
data/app/models/iro/stock.rb
CHANGED
|
@@ -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
|
|
data/app/models/iro/strategy.rb
CHANGED
|
@@ -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,
|
|
25
|
-
has_one :next_position,
|
|
26
|
-
belongs_to :stock,
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 '
|
|
69
|
+
throw 'zq5 - should never happen'
|
|
67
70
|
end
|
|
68
71
|
else
|
|
69
|
-
throw '
|
|
72
|
+
throw 'zq6 - debit spreads are not implemented'
|
|
70
73
|
end
|
|
71
|
-
when Iro::Strategy::KIND_WHEEL
|
|
72
|
-
'CALL'
|
|
73
74
|
else
|
|
74
|
-
|
|
75
|
+
# put_call = 'zq9-ERROR'
|
|
76
|
+
throw 'zq9 - this should never happen'
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
78
|
-
field :
|
|
79
|
-
field :
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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 <
|
|
204
|
-
return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{
|
|
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 <
|
|
231
|
-
return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{
|
|
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 <
|
|
255
|
-
return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{
|
|
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
|
data/app/models/wco/leadset.rb
CHANGED
|
@@ -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 :
|
|
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'
|
|
@@ -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
|
+
|
|
6
8
|
= link_to '[~]', edit_office_action_path(oa)
|
|
7
9
|
|
|
8
10
|
= link_to oa.slug, office_action_path(oa)
|
|
9
11
|
|
|
10
|
-
= button_to '
|
|
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
|
|
@@ -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
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.
|
|
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-
|
|
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
|