iron_warbler 2.0.7.23 → 2.0.7.25
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/README.txt +19 -0
- data/app/assets/stylesheets/iron_warbler/positions_gameui.scss +8 -0
- data/app/assets/stylesheets/iron_warbler/purses_gameui.scss +7 -34
- data/app/controllers/iro/positions_controller.rb +67 -1
- data/app/models/iro/datapoint.rb +7 -7
- data/app/models/iro/option.rb +178 -3
- data/app/models/iro/position.rb +6 -0
- data/app/models/iro/purse.rb +4 -1
- data/app/models/iro/stock.rb +81 -0
- data/app/models/tda/option.rb +101 -5
- data/app/views/iro/positions/_gameui_covered_call.haml +1 -1
- data/app/views/iro/positions/_gameui_long_debit_call_spread.haml +1 -1
- data/app/views/iro/positions/_prepare_long_debit_call_spread.haml +6 -4
- data/app/views/iro/positions/prepare2.haml +11 -0
- data/app/views/iro/purses/_form_extra_fields.haml +5 -5
- data/app/views/iro/purses/gameui.haml +2 -1
- data/config/routes.rb +6 -5
- data/lib/tasks/db_tasks.rake +1 -1
- data/lib/tasks/test_tasks.rake +54 -0
- metadata +19 -4
- data/app/models/iro/trash/position_covered_call.rb +0 -4
- data/app/models/iro/trash/position_debit_spread.rb +0 -251
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8184e7abf0df5faad6e8d99504be22235e0fa846801be7bd2869e7f81631349b
|
4
|
+
data.tar.gz: ac0dab40337f109eadccd06b943203e19c507abc9a05cc3e66f0f453b8275d82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e892adf76bb1fecaf319f614bb4249689bd5b761e46b234f5e27b89d3640b0f6fcfb7f9cfba5ecd25873b8c9af40ab7d9e8e706293d09879447c7188ae47303d
|
7
|
+
data.tar.gz: e037e728a1a2beb3f4f07ec46f5c40c188514960bd961570fb603e5a73842377a01268c9e1ff8538e8c7bc1afc213dc4df5a73fd826be9209838318f2aaee65b
|
data/README.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
= Install =
|
3
|
+
|
4
|
+
From: https://docs.galpy.org/en/latest/installation.html
|
5
|
+
|
6
|
+
brew install gsl
|
7
|
+
|
8
|
+
= Use =
|
9
|
+
|
10
|
+
calculator: https://www.omnicalculator.com/finance/black-scholes
|
11
|
+
|
12
|
+
From: https://pythoninoffice.com/calculate-black-scholes-option-price-in-python/
|
13
|
+
|
14
|
+
-=---
|
15
|
+
|
16
|
+
|
17
|
+
https://api.tdameritrade.com/v1/accounts/232718838/orders/13597943085
|
18
|
+
|
19
|
+
|
@@ -2,7 +2,8 @@
|
|
2
2
|
.purses-gameuiW {
|
3
3
|
border: 1px solid red;
|
4
4
|
width: 100%;
|
5
|
-
overflow: scroll;
|
5
|
+
// overflow-x: scroll;
|
6
|
+
overflow-y: hidden;
|
6
7
|
}
|
7
8
|
|
8
9
|
.purses-gameui {
|
@@ -51,7 +52,7 @@
|
|
51
52
|
.Position {
|
52
53
|
position: absolute;
|
53
54
|
width: 100%;
|
54
|
-
height:
|
55
|
+
height: 100%;
|
55
56
|
|
56
57
|
left: 0;
|
57
58
|
top: 0;
|
@@ -63,8 +64,6 @@
|
|
63
64
|
background: rgba(255,153,51, 0.5);
|
64
65
|
position: absolute;
|
65
66
|
|
66
|
-
/* HEREHERE: either left, or right 0, this orange strip flips. */
|
67
|
-
// left: 0;
|
68
67
|
right: 0;
|
69
68
|
|
70
69
|
width: 10000px;
|
@@ -75,10 +74,10 @@
|
|
75
74
|
.Breakeven {
|
76
75
|
position: absolute;
|
77
76
|
right: 0;
|
78
|
-
bottom:
|
77
|
+
bottom: 0;
|
79
78
|
border: 2px dotted green;
|
80
79
|
background: rgba(0,255,0, 0.5);
|
81
|
-
height:
|
80
|
+
height: 0.5em;
|
82
81
|
}
|
83
82
|
|
84
83
|
.sprite {
|
@@ -142,32 +141,6 @@
|
|
142
141
|
background: rgba(255,0,0, 0.75);
|
143
142
|
}
|
144
143
|
|
145
|
-
|
146
|
-
// .grid-mark:nth-child(4n + 1) {
|
147
|
-
// border: 1px solid red;
|
148
|
-
// display: none;
|
149
|
-
// }
|
150
|
-
// .grid-mark:nth-child(4n) {
|
151
|
-
// border: 1px solid red;
|
152
|
-
// display: none;
|
153
|
-
// }
|
154
|
-
// .grid-mark:nth-child(6n + 1) {
|
155
|
-
// border: 1px solid red;
|
156
|
-
// display: none;
|
157
|
-
// }
|
158
|
-
// .grid-mark:nth-child(6n) {
|
159
|
-
// border: 1px solid red;
|
160
|
-
// display: none;
|
161
|
-
// }
|
162
|
-
// .grid-mark:nth-child(3n + 1) {
|
163
|
-
// border: 1px solid red;
|
164
|
-
// display: none;
|
165
|
-
// }
|
166
|
-
// .grid-mark:nth-child(3n) {
|
167
|
-
// border: 1px solid red;
|
168
|
-
// display: none;
|
169
|
-
// }
|
170
|
-
|
171
144
|
.grid-mark {
|
172
145
|
// border: 1px solid red;
|
173
146
|
|
@@ -206,8 +179,8 @@
|
|
206
179
|
.labelC {
|
207
180
|
background: white;
|
208
181
|
position: absolute;
|
209
|
-
|
210
|
-
|
182
|
+
bottom: -1em;
|
183
|
+
right: 0;
|
211
184
|
}
|
212
185
|
}
|
213
186
|
}
|
@@ -47,6 +47,7 @@ class Iro::PositionsController < Iro::ApplicationController
|
|
47
47
|
authorize! :edit, @position
|
48
48
|
end
|
49
49
|
|
50
|
+
## this is auto-driven
|
50
51
|
def propose
|
51
52
|
@strategy = Iro::Strategy.find params[:strategy_id]
|
52
53
|
authorize! :show, @strategy
|
@@ -112,6 +113,68 @@ class Iro::PositionsController < Iro::ApplicationController
|
|
112
113
|
redirect_to request.referrer || purse_path( @position.purse )
|
113
114
|
end
|
114
115
|
|
116
|
+
-## long debit call spread
|
117
|
+
def prepare2
|
118
|
+
@position = Iro::Position.find params[:id]
|
119
|
+
authorize! :roll, @position
|
120
|
+
|
121
|
+
pos = @position
|
122
|
+
stock = @position.stock
|
123
|
+
|
124
|
+
next_outer = @position.outer || Iro::Option.create({
|
125
|
+
stock: stock,
|
126
|
+
strike: pos.outer_strike,
|
127
|
+
expires_on: pos.expires_on,
|
128
|
+
position: pos,
|
129
|
+
last: pos.begin_outer_price,
|
130
|
+
})
|
131
|
+
|
132
|
+
next_inner = @position.inner || Iro::Option.create({
|
133
|
+
stock: stock,
|
134
|
+
strike: pos.inner_strike,
|
135
|
+
expires_on: pos.expires_on,
|
136
|
+
position: pos,
|
137
|
+
last: pos.begin_inner_price,
|
138
|
+
})
|
139
|
+
|
140
|
+
prev_outer = pos.prev.outer
|
141
|
+
prev_inner = pos.prev.inner
|
142
|
+
|
143
|
+
price = pos.prev.outer.last - pos.prev.inner.last + pos.nxt.inner.last - pos.nxt.outer.last
|
144
|
+
|
145
|
+
@query = {
|
146
|
+
orderType: "NET_DEBIT",
|
147
|
+
session: "NORMAL",
|
148
|
+
price: price,
|
149
|
+
duration: "DAY",
|
150
|
+
orderStrategyType: "SINGLE",
|
151
|
+
orderLegCollection: [
|
152
|
+
## @TODO: this is only entering the next position, need to also close out the previous.
|
153
|
+
{
|
154
|
+
instruction: "BUY_TO_OPEN",
|
155
|
+
quantity: q,
|
156
|
+
instrument: {
|
157
|
+
symbol: outer.symbol,
|
158
|
+
assetType: "OPTION",
|
159
|
+
},
|
160
|
+
},
|
161
|
+
{
|
162
|
+
instruction: "SELL_TO_OPEN",
|
163
|
+
quantity: q,
|
164
|
+
instrument: {
|
165
|
+
symbol: inner.symbol,
|
166
|
+
assetType: "OPTION",
|
167
|
+
},
|
168
|
+
},
|
169
|
+
],
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
-## long debit call spread
|
174
|
+
def prepare3
|
175
|
+
out = Tda::Option.roll_long_debit_call_spread( position )
|
176
|
+
end
|
177
|
+
|
115
178
|
def prepare
|
116
179
|
@position = Iro::Position.find params[:id]
|
117
180
|
authorize! :roll, @position
|
@@ -188,7 +251,8 @@ class Iro::PositionsController < Iro::ApplicationController
|
|
188
251
|
def _prepare_long_debit_call_spread
|
189
252
|
@positions = []
|
190
253
|
(-@nn..@nn).each do |idx|
|
191
|
-
next_ = Iro::Position.
|
254
|
+
next_ = Iro::Position.find_or_create_by({
|
255
|
+
status: 'prepare',
|
192
256
|
stock: @stock,
|
193
257
|
inner_strike: @prev.inner_strike - idx*@stock.options_price_increment,
|
194
258
|
outer_strike: @prev.outer_strike - idx*@stock.options_price_increment,
|
@@ -196,6 +260,7 @@ class Iro::PositionsController < Iro::ApplicationController
|
|
196
260
|
purse: @position.purse,
|
197
261
|
strategy: @position.strategy,
|
198
262
|
quantity: @position.quantity,
|
263
|
+
prev_id: @prev.id,
|
199
264
|
})
|
200
265
|
next_.sync
|
201
266
|
next_.begin_inner_price = next_.end_inner_price
|
@@ -205,6 +270,7 @@ class Iro::PositionsController < Iro::ApplicationController
|
|
205
270
|
next_.begin_outer_delta = next_.end_outer_delta
|
206
271
|
next_.next_gain_loss_amount = @prev.end_outer_price - @prev.end_inner_price
|
207
272
|
next_.next_gain_loss_amount += next_.begin_inner_price - next_.begin_outer_price
|
273
|
+
next_.save
|
208
274
|
puts! next_, 'next_'
|
209
275
|
puts! next_.next_gain_loss_amount, 'next_gain_loss_amount'
|
210
276
|
@positions.push next_
|
data/app/models/iro/datapoint.rb
CHANGED
@@ -138,15 +138,15 @@ class Iro::Datapoint
|
|
138
138
|
flag = create({
|
139
139
|
kind: 'STOCK',
|
140
140
|
symbol: symbol,
|
141
|
-
date: row['
|
142
|
-
quote_at: row['
|
141
|
+
date: row['Date'],
|
142
|
+
quote_at: row['Date'],
|
143
143
|
|
144
|
-
volume: row['
|
144
|
+
volume: row['Volume'],
|
145
145
|
|
146
|
-
open: row['
|
147
|
-
high: row['
|
148
|
-
low: row['
|
149
|
-
value: row['
|
146
|
+
open: row['Open'],
|
147
|
+
high: row['High'],
|
148
|
+
low: row['Low'],
|
149
|
+
value: row['Close'],
|
150
150
|
})
|
151
151
|
print '.' if flag.persisted?
|
152
152
|
end
|
data/app/models/iro/option.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
require 'distribution'
|
2
|
+
N = Distribution::Normal
|
3
|
+
# include Math
|
4
|
+
# require 'business_time'
|
2
5
|
|
3
6
|
class Iro::Option
|
4
7
|
include Mongoid::Document
|
@@ -6,11 +9,183 @@ class Iro::Option
|
|
6
9
|
include Mongoid::Paranoia
|
7
10
|
store_in collection: 'iro_options'
|
8
11
|
|
9
|
-
|
10
|
-
|
12
|
+
attr_accessor :recompute
|
13
|
+
|
14
|
+
field :ticker
|
15
|
+
validates :ticker, presence: true
|
11
16
|
|
12
17
|
field :symbol
|
13
18
|
validates :symbol, uniqueness: true, presence: true
|
14
19
|
|
20
|
+
field :put_call, type: :string
|
21
|
+
validates :put_call, presence: true
|
22
|
+
|
23
|
+
field :delta, type: :float
|
24
|
+
|
25
|
+
field :strike, type: :float
|
26
|
+
validates :strike, presence: true
|
27
|
+
|
28
|
+
field :expires_on, type: :date
|
29
|
+
validates :expires_on, presence: true
|
30
|
+
|
31
|
+
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
|
32
|
+
|
33
|
+
has_one :outer, class_name: 'Iro::Position', invese_of: :outer
|
34
|
+
has_one :inner, class_name: 'Iro::Position', invese_of: :inner
|
35
|
+
|
36
|
+
field :last, type: :float
|
37
|
+
|
38
|
+
def symbol
|
39
|
+
if !self[:symbol]
|
40
|
+
p_c_ = put_call == 'PUT' ? 'P' : 'C'
|
41
|
+
strike_ = strike.to_i == strike ? strike.to_i : strike
|
42
|
+
sym = "#{stock.ticker}_#{expires_on.strftime("%m%d%y")}#{p_c_}#{strike_}" # XYZ_011819P45
|
43
|
+
self[:symbol] = sym
|
44
|
+
save
|
45
|
+
end
|
46
|
+
self[:symbol]
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
## black-scholes pricing
|
51
|
+
##
|
52
|
+
|
53
|
+
=begin
|
54
|
+
##
|
55
|
+
##
|
56
|
+
##
|
57
|
+
annual to daily:
|
58
|
+
|
59
|
+
AR = ((DR + 1)^365 – 1) x 100
|
60
|
+
|
61
|
+
##
|
62
|
+
##
|
63
|
+
##
|
64
|
+
From: https://www.investopedia.com/articles/optioninvestor/07/options_beat_market.asp
|
65
|
+
|
66
|
+
K :: strike price
|
67
|
+
S_t :: last
|
68
|
+
r :: risk-free rate
|
69
|
+
t :: time to maturity
|
70
|
+
|
71
|
+
C = S_t N( d1 ) - K e^-rt N( d2 )
|
72
|
+
|
73
|
+
d1 = ln( St / K ) + (r + theta**2 / 2 )t
|
74
|
+
/{ theta_s * sqrt( t ) }
|
75
|
+
|
76
|
+
d2 = d1 - theta_s sqrt( t )
|
77
|
+
|
78
|
+
##
|
79
|
+
## From: https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model
|
80
|
+
##
|
81
|
+
|
82
|
+
D :: e^(rt) # discount factor
|
83
|
+
F :: e^(rt) S # forward price of underlying
|
84
|
+
|
85
|
+
C(F,t) = D[ N(d1)F - N(d2)K ]
|
86
|
+
|
87
|
+
d1 = ln(F/K) + stdev**2 t / 2
|
88
|
+
/{ stdev sqrt(t) }
|
89
|
+
d2 = d1 - stdev sqrt(t)
|
90
|
+
|
91
|
+
##
|
92
|
+
## From: https://www.daytrading.com/options-pricing-models
|
93
|
+
##
|
94
|
+
C0 = S0N(d1) – Xe-rtN(d2)
|
95
|
+
|
96
|
+
C0 = current call premium
|
97
|
+
S0 = current stock price
|
98
|
+
N(d1) = the probability that a value in a normal distribution will be less than d
|
99
|
+
N(d2) = the probability that the option will be in the money by expiration
|
100
|
+
X = strike price of the option
|
101
|
+
T = time until expiration (expressed in years)
|
102
|
+
r = risk-free interest rate
|
103
|
+
e = 2.71828, the base of the natural logarithm
|
104
|
+
ln = natural logarithm function
|
105
|
+
σ = standard deviation of the stock’s annualized rate of return (compounded continuously)
|
106
|
+
d1 = ln(S0/X) + (r + σ2/2)Tσ√T
|
107
|
+
|
108
|
+
d2 = d1 – σ√T
|
109
|
+
|
110
|
+
Note that:
|
111
|
+
|
112
|
+
Xe-rt = X/ert = the present value of the strike price using a continuously compounded interest rate
|
113
|
+
|
114
|
+
##
|
115
|
+
## From: https://www.wallstreetmojo.com/black-scholes-model/
|
116
|
+
##
|
117
|
+
|
118
|
+
|
119
|
+
## init
|
120
|
+
require 'distribution'
|
121
|
+
N = Distribution::Normal
|
122
|
+
stock = Iro::Stock.find_by ticker: 'NVDA'
|
123
|
+
strike = 910.0
|
124
|
+
r = Iro::Option.rate_daily
|
125
|
+
stdev = 91.0
|
126
|
+
t = 7.0
|
127
|
+
expires_on = '2024-03-22'
|
128
|
+
|
129
|
+
=end
|
130
|
+
def d1
|
131
|
+
last = stock.last
|
132
|
+
r = self.class.rate_annual
|
133
|
+
|
134
|
+
out = Math.log( last / strike ) + ( r + stdev**2 / 2 ) * t
|
135
|
+
out = out /( stdev * Math.sqrt(t) )
|
136
|
+
return out
|
137
|
+
end
|
138
|
+
def d2
|
139
|
+
last = stock.last
|
140
|
+
r = self.class.rate_annual
|
141
|
+
|
142
|
+
out = d1 - stdev * Math.sqrt( t )
|
143
|
+
return out
|
144
|
+
end
|
145
|
+
def t
|
146
|
+
# t = 1.0 / 365 * Date.today.business_days_until( expires_on )
|
147
|
+
t = 1.0 / 365 * (expires_on - Date.today).to_i
|
148
|
+
end
|
149
|
+
def stdev
|
150
|
+
recompute = nil
|
151
|
+
stock.stdev( recompute: recompute )
|
152
|
+
end
|
153
|
+
def call_price
|
154
|
+
last = stock.last
|
155
|
+
r = self.class.rate_annual
|
156
|
+
|
157
|
+
out = N.cdf( d1 ) * last - N.cdf( d2 ) * strike * Math::E**( -1 * r * t )
|
158
|
+
return out
|
159
|
+
end
|
160
|
+
|
161
|
+
def put_price
|
162
|
+
last = stock.last
|
163
|
+
r = self.class.rate_annual
|
164
|
+
|
165
|
+
out = N.cdf(-d2) * strike * exp(-r*t) - N.cdf(-d1) * last
|
166
|
+
return out
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def self.rate_annual
|
171
|
+
0.05
|
172
|
+
end
|
173
|
+
|
174
|
+
=begin
|
175
|
+
# test
|
176
|
+
|
177
|
+
inn = 100
|
178
|
+
n.times { inn = inn*(1.0+out) }
|
179
|
+
inn
|
180
|
+
|
181
|
+
=end
|
182
|
+
def self.rate_daily
|
183
|
+
n = 250.0 # days
|
184
|
+
# n = 12 # months
|
185
|
+
|
186
|
+
out = (1.0+self.rate_annual)**(1.0/n) - 1.0
|
187
|
+
puts! out, 'rate_daily'
|
188
|
+
return out
|
189
|
+
end
|
15
190
|
|
16
191
|
end
|
data/app/models/iro/position.rb
CHANGED
@@ -17,6 +17,9 @@ class Iro::Position
|
|
17
17
|
belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
|
18
18
|
index({ purse_id: 1, ticker: 1 })
|
19
19
|
|
20
|
+
belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxt
|
21
|
+
has_one :nxt, class_name: 'Iro::Position', inverse_of: :prev
|
22
|
+
|
20
23
|
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
|
21
24
|
def ticker
|
22
25
|
stock&.ticker || '-'
|
@@ -27,6 +30,9 @@ class Iro::Position
|
|
27
30
|
# field :ticker
|
28
31
|
# validates :ticker, presence: true
|
29
32
|
|
33
|
+
belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer
|
34
|
+
belongs_to :inner, class_name: 'Iro::Option', inverse_of: :inner
|
35
|
+
|
30
36
|
field :outer_strike, type: :float
|
31
37
|
# validates :outer_strike, presence: true
|
32
38
|
|
data/app/models/iro/purse.rb
CHANGED
@@ -14,7 +14,10 @@ class Iro::Purse
|
|
14
14
|
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
|
15
15
|
|
16
16
|
field :unit, type: :integer, default: 10
|
17
|
-
|
17
|
+
|
18
|
+
## for rolling only:
|
19
|
+
field :height, type: :integer, default: 100
|
20
|
+
|
18
21
|
field :mark_every_n_usd, type: :float, default: 1
|
19
22
|
field :n_next_positions, type: :integer, default: 5
|
20
23
|
## with unit 10, sum_scale .001
|
data/app/models/iro/stock.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
include Math
|
2
|
+
require 'business_time'
|
1
3
|
|
2
4
|
class Iro::Stock
|
3
5
|
include Mongoid::Document
|
@@ -20,9 +22,16 @@ class Iro::Stock
|
|
20
22
|
field :last, type: :float
|
21
23
|
field :options_price_increment, type: :float
|
22
24
|
|
25
|
+
field :stdev, type: :float
|
26
|
+
|
23
27
|
has_many :positions, class_name: 'Iro::Position', inverse_of: :stock
|
24
28
|
has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :stock
|
25
29
|
has_many :purses, class_name: 'Iro::Purse', inverse_of: :stock
|
30
|
+
has_many :options, class_name: 'Iro::Option', inverse_of: :stock
|
31
|
+
|
32
|
+
def self.f ticker
|
33
|
+
self.find_by ticker: ticker
|
34
|
+
end
|
26
35
|
|
27
36
|
def to_s
|
28
37
|
ticker
|
@@ -33,4 +42,76 @@ class Iro::Stock
|
|
33
42
|
def self.tickers_list
|
34
43
|
[[nil,nil]] + all.map { |sss| [ sss.ticker, sss.ticker ] }
|
35
44
|
end
|
45
|
+
|
46
|
+
=begin
|
47
|
+
stock = Iro::Stock.find_by( ticker: 'NVDA' )
|
48
|
+
|
49
|
+
duration = 1.month
|
50
|
+
stock.volatility_from_mo
|
51
|
+
|
52
|
+
duration = 1.year
|
53
|
+
stock.volatility_from_yr
|
54
|
+
|
55
|
+
=end
|
56
|
+
def volatility duration:
|
57
|
+
stock = self
|
58
|
+
begin_on = Time.now - duration - 1.day
|
59
|
+
points = Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
|
60
|
+
:date.gte => begin_on,
|
61
|
+
).order_by( date: :asc )
|
62
|
+
|
63
|
+
puts! [points.first.date, points.last.date], "from,to"
|
64
|
+
|
65
|
+
points_p = []
|
66
|
+
points.each_with_index do |p, idx|
|
67
|
+
next if idx == 0
|
68
|
+
prev = points[idx-1]
|
69
|
+
|
70
|
+
out = p.value / prev.value - 1
|
71
|
+
points_p.push out
|
72
|
+
end
|
73
|
+
n = points_p.length
|
74
|
+
|
75
|
+
avg = points_p.reduce(&:+) / n
|
76
|
+
_sum_of_sq = []
|
77
|
+
points_p.map do |p|
|
78
|
+
_sum_of_sq.push( ( p - avg )*( p - avg ) )
|
79
|
+
end
|
80
|
+
sum_of_sq = _sum_of_sq.reduce( &:+ ) / n
|
81
|
+
|
82
|
+
# n_periods = begin_on.to_date.business_days_until( Date.today )
|
83
|
+
out = Math.sqrt( sum_of_sq )*sqrt( n )
|
84
|
+
adjustment = 2.0
|
85
|
+
out = out * adjustment
|
86
|
+
puts! out, 'volatility (adjusted)'
|
87
|
+
return out
|
88
|
+
end
|
89
|
+
|
90
|
+
def volatility_from_mo
|
91
|
+
volatility( duration: 1.month )
|
92
|
+
end
|
93
|
+
def volatility_from_yr
|
94
|
+
volatility( duration: 1.year )
|
95
|
+
end
|
96
|
+
def stdev recompute: nil
|
97
|
+
if !self[:stdev] || recompute
|
98
|
+
out = volatility_from_yr
|
99
|
+
self[:stdev] = out
|
100
|
+
save( validate: false )
|
101
|
+
return out
|
102
|
+
else
|
103
|
+
self[:stdev]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
## stdev
|
108
|
+
## From: https://stackoverflow.com/questions/19484891/how-do-i-find-the-standard-deviation-in-ruby
|
109
|
+
# contents = [1,2,3,4,5,6,7,8,9]
|
110
|
+
# n = contents.size # => 9
|
111
|
+
# contents.map!(&:to_f) # => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
112
|
+
# mean = contents.reduce(&:+)/n # => 5.0
|
113
|
+
# sum_sqr = contents.map {|x| x * x}.reduce(&:+) # => 285.0
|
114
|
+
# std_dev = Math.sqrt((sum_sqr - n * mean * mean)/(n-1)) # => 2.7386127875258306
|
115
|
+
|
116
|
+
|
36
117
|
end
|
data/app/models/tda/option.rb
CHANGED
@@ -125,13 +125,109 @@ class Tda::Option
|
|
125
125
|
end
|
126
126
|
|
127
127
|
|
128
|
+
def self.close_credit_call
|
129
|
+
end
|
130
|
+
def self.close_long_debit_call_spread
|
131
|
+
end
|
132
|
+
def self.close_short_debit_put_spread
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.get_token
|
136
|
+
opts = {
|
137
|
+
grant_type: 'authorization_code',
|
138
|
+
access_type: 'offline',
|
139
|
+
code: ::TD_AMERITRADE[:code],
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.create_credit_call outer:, inner:, q:, price:
|
144
|
+
query = {
|
145
|
+
orderType: "NET_DEBIT",
|
146
|
+
session: "NORMAL",
|
147
|
+
price: price,
|
148
|
+
duration: "DAY",
|
149
|
+
orderStrategyType: "SINGLE",
|
150
|
+
orderLegCollection: [
|
151
|
+
{
|
152
|
+
instruction: "BUY_TO_OPEN",
|
153
|
+
quantity: q,
|
154
|
+
instrument: {
|
155
|
+
symbol: outer.symbol,
|
156
|
+
assetType: "OPTION",
|
157
|
+
},
|
158
|
+
},
|
159
|
+
{
|
160
|
+
instruction: "SELL_TO_OPEN",
|
161
|
+
quantity: q,
|
162
|
+
instrument: {
|
163
|
+
symbol: inner.symbol,
|
164
|
+
assetType: "OPTION",
|
165
|
+
},
|
166
|
+
},
|
167
|
+
],
|
168
|
+
}
|
169
|
+
File.write('tmp/query.json', JSON.pretty_generate( query ))
|
170
|
+
puts! query, 'query'
|
171
|
+
|
172
|
+
return
|
173
|
+
|
174
|
+
headers = {
|
175
|
+
Authorize: "Bearer #{::TD_AMERITRADE[:access_token]}",
|
176
|
+
}
|
177
|
+
|
178
|
+
path = "/v1/accounts/#{::TD_AMERITRADE[:accountId]}/orders"
|
179
|
+
puts! path, 'path'
|
180
|
+
out = self.post path, { query: query, headers: headers }
|
181
|
+
timestamp = DateTime.parse out.headers['date']
|
182
|
+
out = out.parsed_response.deep_symbolize_keys
|
183
|
+
puts! out, 'created credit call?'
|
184
|
+
end
|
185
|
+
def self.create_long_debit_call_spread
|
186
|
+
end
|
187
|
+
def self.create_short_debit_put_spread
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.roll_credit_call
|
191
|
+
end
|
192
|
+
def self.roll_long_debit_call_spread
|
193
|
+
end
|
194
|
+
def self.roll_short_debit_put_spread
|
195
|
+
end
|
196
|
+
|
197
|
+
|
128
198
|
end
|
129
199
|
|
200
|
+
##
|
201
|
+
## From: https://developer.tdameritrade.com/content/place-order-samples
|
202
|
+
## Buy Limit: Vertical Call Spread
|
203
|
+
##
|
130
204
|
=begin
|
205
|
+
{
|
206
|
+
"orderType": "NET_DEBIT",
|
207
|
+
"session": "NORMAL",
|
208
|
+
"price": "1.20",
|
209
|
+
"duration": "DAY",
|
210
|
+
"orderStrategyType": "SINGLE",
|
211
|
+
"orderLegCollection": [
|
212
|
+
{
|
213
|
+
"instruction": "BUY_TO_OPEN",
|
214
|
+
"quantity": 10,
|
215
|
+
"instrument": {
|
216
|
+
"symbol": "XYZ_011516C40",
|
217
|
+
"assetType": "OPTION"
|
218
|
+
}
|
219
|
+
},
|
220
|
+
{
|
221
|
+
"instruction": "SELL_TO_OPEN",
|
222
|
+
"quantity": 10,
|
223
|
+
"instrument": {
|
224
|
+
"symbol": "XYZ_011516C42.5",
|
225
|
+
"assetType": "OPTION"
|
226
|
+
}
|
227
|
+
}
|
228
|
+
]
|
229
|
+
}
|
230
|
+
=end
|
231
|
+
|
131
232
|
|
132
|
-
outs = Tda::Option.get_quotes({
|
133
|
-
contractType: 'CALL', strike: 20.0, expirationDate: '2024-01-12',
|
134
|
-
ticker: 'GME',
|
135
|
-
})
|
136
233
|
|
137
|
-
=end
|
@@ -10,7 +10,7 @@
|
|
10
10
|
.a
|
11
11
|
= render "/iro/positions/header_#{pos.strategy.kind}", pos: pos
|
12
12
|
.StockCoordinatesW
|
13
|
-
.StockCoordinates{ style: "height: #{pos.q() *
|
13
|
+
.StockCoordinates{ style: "height: #{pos.q() * pos.purse.height}px " }
|
14
14
|
.grid= render "/iro/stocks/grid_#{pos.strategy.long_or_short}", stock: stock, position: pos
|
15
15
|
|
16
16
|
.Origin{ style: "left: #{ ( nearest_strike - stock.last )* u}px" }
|
@@ -10,7 +10,7 @@
|
|
10
10
|
.a
|
11
11
|
= render "/iro/positions/header_#{pos.strategy.kind}", pos: pos
|
12
12
|
.StockCoordinatesW
|
13
|
-
.StockCoordinates{ style: "height: #{pos.q() *
|
13
|
+
.StockCoordinates{ style: "height: #{pos.q() *pos.purse.height}px " }
|
14
14
|
.grid= render "/iro/stocks/grid_#{pos.strategy.long_or_short}", stock: stock, position: pos
|
15
15
|
|
16
16
|
.Origin{ style: "left: #{ ( nearest_strike - stock.last )* u}px" }
|
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
.StockCoordinatesW
|
9
|
-
.StockCoordinates{ style: "height: #{@position.purse.height}px " }
|
9
|
+
.StockCoordinates{ style: "height: #{@position.q * @position.purse.height}px " }
|
10
10
|
|
11
11
|
.grid= render "/iro/stocks/grid_#{pos.strategy.long_or_short}", stock: stock, position: pos
|
12
12
|
|
@@ -17,7 +17,9 @@
|
|
17
17
|
- border = amnt >= 0 ? "#{amnt * u}px solid green" : "#{amnt * -1 * u}px solid red"
|
18
18
|
- width = "#{ ( pos.inner_strike - pos.outer_strike ) * u}px"
|
19
19
|
- left = "#{ ( pos.outer_strike - pos.stock.last ) * u}px"
|
20
|
-
.PositionW{ style: "width: #{width}; left: #{left};
|
21
|
-
.Position
|
22
|
-
|
20
|
+
.PositionW{ style: "width: #{width}; left: #{left}; " }
|
21
|
+
.Position{ style: " border-right: #{border}; " }
|
22
|
+
.MaxGain{ style: "width: #{pos.max_gain * u}px" }
|
23
|
+
|
24
|
+
.select= button_to 'select', prepare2_position_path(pos.id)
|
23
25
|
.c
|
@@ -5,10 +5,10 @@
|
|
5
5
|
px/usd
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
%label height
|
9
|
+
= f.number_field :height
|
10
|
+
px/q
|
11
|
+
|
12
12
|
|
13
13
|
%label mark_every_n_usd
|
14
14
|
= f.number_field :mark_every_n_usd, step: 0.01
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
|
21
21
|
%label summary_scale
|
22
|
-
= f.number_field :summary_scale, step: 0.
|
22
|
+
= f.number_field :summary_scale, step: 0.000001, style: "width: 120px"
|
23
23
|
|
24
24
|
|
25
25
|
|
data/config/routes.rb
CHANGED
@@ -9,11 +9,12 @@ Iro::Engine.routes.draw do
|
|
9
9
|
|
10
10
|
resources :option_watches
|
11
11
|
|
12
|
-
get 'positions/duplicate/:id', to: 'positions#new',
|
13
|
-
post 'positions/propose', to: 'positions#propose',
|
14
|
-
get 'positions/:id/prepare', to: 'positions#prepare',
|
15
|
-
|
16
|
-
|
12
|
+
get 'positions/duplicate/:id', to: 'positions#new', as: :duplicate_position
|
13
|
+
post 'positions/propose', to: 'positions#propose', as: :propose_position
|
14
|
+
get 'positions/:id/prepare', to: 'positions#prepare', as: :prepare_to_roll_position, defaults: { template: 'gameui' }
|
15
|
+
match 'positions/:id/prepare2', to: 'positions#prepare2', as: :prepare2_position, defaults: { template: 'gameui' }, via: [ :get, :post ]
|
16
|
+
post 'positions/:id/roll', to: 'positions#do_roll', as: :roll_position
|
17
|
+
get 'positions/:id/refresh', to: 'positions#refresh', as: :refresh_position
|
17
18
|
resources :positions
|
18
19
|
resources :profiles
|
19
20
|
|
data/lib/tasks/db_tasks.rake
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
namespace :db do
|
3
3
|
|
4
|
-
##
|
4
|
+
## Date, Volume, Open, High, Low, Close
|
5
5
|
desc 'import_stock symbol=GME path=./data/GME-test.csv'
|
6
6
|
task import_stock: :environment do
|
7
7
|
Iro::Datapoint.import_stock( symbol: ENV['symbol'], path: ENV['path'] )
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
namespace :test do
|
3
|
+
|
4
|
+
desc 'stock#volatility_mo'
|
5
|
+
task stock_vol_mo: :environment do
|
6
|
+
out = Iro::Stock.find_by( ticker: 'NVDA' ).volatility_from_mo
|
7
|
+
puts! out, 'out'
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'stock#volatility_yr'
|
11
|
+
task stock_vol_yr: :environment do
|
12
|
+
out = Iro::Stock.find_by( ticker: 'NVDA' ).volatility_from_yr
|
13
|
+
puts! out, 'out'
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'option#call_price'
|
17
|
+
task option_call_price: :environment do
|
18
|
+
stock = Iro::Stock.find_by( ticker: 'NVDA' )
|
19
|
+
stock.last = 884
|
20
|
+
|
21
|
+
option = Iro::Option.new({
|
22
|
+
stock: stock,
|
23
|
+
strike: 910,
|
24
|
+
expires_on: '2024-03-22',
|
25
|
+
# delta: 0.41,
|
26
|
+
})
|
27
|
+
out = option.put_price
|
28
|
+
puts! out, 'out'
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'place order'
|
32
|
+
task place_order: :environment do
|
33
|
+
stock = Iro::Stock.f 'GME'
|
34
|
+
outer = Iro::Option.new({
|
35
|
+
stock: stock,
|
36
|
+
put_call: 'CALL',
|
37
|
+
strike: 15.5,
|
38
|
+
expires_on: '2024-03-22',
|
39
|
+
})
|
40
|
+
inner = Iro::Option.new({
|
41
|
+
stock: stock,
|
42
|
+
put_call: 'CALL',
|
43
|
+
strike: 17.0,
|
44
|
+
expires_on: '2024-03-22',
|
45
|
+
})
|
46
|
+
out = Tda::Option.create_credit_call( inner: inner, outer: outer, q: 1, price: 0.01 )
|
47
|
+
puts! out, 'out'
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iron_warbler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.7.
|
4
|
+
version: 2.0.7.25
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Pudeyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: business_time
|
@@ -192,12 +192,27 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: 1.0.1
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: distribution
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
195
209
|
description: A stocks and Options Trading Bot.
|
196
210
|
email: victor@wasya.co
|
197
211
|
executables: []
|
198
212
|
extensions: []
|
199
213
|
extra_rdoc_files: []
|
200
214
|
files:
|
215
|
+
- README.txt
|
201
216
|
- Rakefile
|
202
217
|
- app/assets/config/iron_warbler_manifest.js
|
203
218
|
- app/assets/javascript/iron_warbler/application.js
|
@@ -232,8 +247,6 @@ files:
|
|
232
247
|
- app/models/iro/purse.rb
|
233
248
|
- app/models/iro/stock.rb
|
234
249
|
- app/models/iro/strategy.rb
|
235
|
-
- app/models/iro/trash/position_covered_call.rb
|
236
|
-
- app/models/iro/trash/position_debit_spread.rb
|
237
250
|
- app/models/tda/option.rb
|
238
251
|
- app/models/tda/stock.rb
|
239
252
|
- app/views/iro/_analytics.erb
|
@@ -261,6 +274,7 @@ files:
|
|
261
274
|
- app/views/iro/positions/edit.haml
|
262
275
|
- app/views/iro/positions/new.haml
|
263
276
|
- app/views/iro/positions/prepare.haml
|
277
|
+
- app/views/iro/positions/prepare2.haml
|
264
278
|
- app/views/iro/positions/roll-cc.haml-bk
|
265
279
|
- app/views/iro/positions/roll.haml-trash
|
266
280
|
- app/views/iro/purses/_form.haml
|
@@ -293,6 +307,7 @@ files:
|
|
293
307
|
- lib/tasks/db_tasks.rake
|
294
308
|
- lib/tasks/iro_tasks.rake
|
295
309
|
- lib/tasks/iro_tasks.rake-old
|
310
|
+
- lib/tasks/test_tasks.rake
|
296
311
|
homepage: https://wasya.co
|
297
312
|
licenses:
|
298
313
|
- Proprietary
|
@@ -1,251 +0,0 @@
|
|
1
|
-
|
2
|
-
class Iro::PositionDebitSpread < Iro::Positin
|
3
|
-
include Mongoid::Document
|
4
|
-
include Mongoid::Timestamps
|
5
|
-
store_in collection: 'iro_positions'
|
6
|
-
|
7
|
-
STATUS_ACTIVE = 'active'
|
8
|
-
STATUS_PROPOSED = 'proposed'
|
9
|
-
STATUSES = [ nil, 'active', 'inactive', 'proposed' ]
|
10
|
-
field :status
|
11
|
-
validates :status, presence: true
|
12
|
-
scope :active, ->{ where( status: 'active' ) }
|
13
|
-
|
14
|
-
belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
|
15
|
-
index({ purse_id: 1, ticker: 1 })
|
16
|
-
|
17
|
-
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
|
18
|
-
def ticker
|
19
|
-
stock&.ticker || '-'
|
20
|
-
end
|
21
|
-
|
22
|
-
belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
|
23
|
-
|
24
|
-
field :outer_strike, type: :float
|
25
|
-
validates :outer_strike, presence: true
|
26
|
-
|
27
|
-
field :inner_strike, type: :float
|
28
|
-
validates :inner_strike, presence: true
|
29
|
-
|
30
|
-
field :expires_on
|
31
|
-
validates :expires_on, presence: true
|
32
|
-
|
33
|
-
field :quantity, type: :integer
|
34
|
-
validates :quantity, presence: true
|
35
|
-
|
36
|
-
field :begin_on
|
37
|
-
field :begin_outer_price, type: :float
|
38
|
-
field :begin_outer_delta, type: :float
|
39
|
-
|
40
|
-
field :begin_inner_price, type: :float
|
41
|
-
field :begin_inner_delta, type: :float
|
42
|
-
|
43
|
-
field :end_on
|
44
|
-
field :end_outer_price, type: :float
|
45
|
-
field :end_outer_delta, type: :float
|
46
|
-
|
47
|
-
field :end_inner_price, type: :float
|
48
|
-
field :end_inner_delta, type: :float
|
49
|
-
|
50
|
-
field :net_amount
|
51
|
-
field :net_percent
|
52
|
-
|
53
|
-
def current_underlying_strike
|
54
|
-
Iro::Stock.find_by( ticker: ticker ).last
|
55
|
-
end
|
56
|
-
|
57
|
-
def refresh
|
58
|
-
out = Tda::Option.get_quote({
|
59
|
-
contractType: 'CALL',
|
60
|
-
strike: strike,
|
61
|
-
expirationDate: expires_on,
|
62
|
-
ticker: ticker,
|
63
|
-
})
|
64
|
-
update({
|
65
|
-
end_delta: out[:delta],
|
66
|
-
end_price: out[:last],
|
67
|
-
})
|
68
|
-
print '_'
|
69
|
-
end
|
70
|
-
|
71
|
-
def net_amount # total
|
72
|
-
outer = 0 - begin_outer_price + end_outer_price
|
73
|
-
inner = begin_inner_price - end_inner_price
|
74
|
-
out = ( outer + inner ) * 100 * quantity
|
75
|
-
end
|
76
|
-
def max_gain # total
|
77
|
-
100 * ( begin_outer_price - begin_inner_price ) * quantity
|
78
|
-
end
|
79
|
-
def max_loss
|
80
|
-
out = 100 * ( outer_strike - inner_strike ) * quantity
|
81
|
-
if strategy.long_or_short == Iro::Strategy::SHORT
|
82
|
-
out = out * -1
|
83
|
-
end
|
84
|
-
return out
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
field :next_delta, type: :float
|
89
|
-
field :next_outcome, type: :float
|
90
|
-
field :next_symbol
|
91
|
-
field :next_mark
|
92
|
-
field :next_reasons, type: :array, default: []
|
93
|
-
field :should_rollp, type: :float
|
94
|
-
|
95
|
-
##
|
96
|
-
## decisions
|
97
|
-
##
|
98
|
-
|
99
|
-
def should_roll?
|
100
|
-
puts! 'shold_roll?'
|
101
|
-
|
102
|
-
update({
|
103
|
-
next_reasons: [],
|
104
|
-
next_symbol: nil,
|
105
|
-
next_delta: nil,
|
106
|
-
})
|
107
|
-
|
108
|
-
if must_roll?
|
109
|
-
out = 1.0
|
110
|
-
elsif can_roll?
|
111
|
-
|
112
|
-
if end_delta < strategy.threshold_delta
|
113
|
-
next_reasons.push "delta is lower than threshold"
|
114
|
-
out = 0.91
|
115
|
-
elsif 1 - end_outer_price/begin_outer_price > strategy.threshold_netp
|
116
|
-
next_reasons.push "made enough percent profit (dubious)"
|
117
|
-
out = 0.61
|
118
|
-
else
|
119
|
-
next_reasons.push "neutral"
|
120
|
-
out = 0.33
|
121
|
-
end
|
122
|
-
|
123
|
-
else
|
124
|
-
out = 0.0
|
125
|
-
end
|
126
|
-
|
127
|
-
update({
|
128
|
-
next_delta: next_position[:delta],
|
129
|
-
next_outcome: next_position[:mark] - end_price,
|
130
|
-
next_symbol: next_position[:symbol],
|
131
|
-
next_mark: next_position[:mark],
|
132
|
-
should_rollp: out,
|
133
|
-
# status: Iro::Position::STATE_PROPOSED,
|
134
|
-
})
|
135
|
-
|
136
|
-
puts! next_reasons, 'next_reasons'
|
137
|
-
puts! out, 'out'
|
138
|
-
return out > 0.5
|
139
|
-
end
|
140
|
-
|
141
|
-
|
142
|
-
## expires_on = cc.expires_on ; nil
|
143
|
-
def can_roll?
|
144
|
-
## only if less than 7 days left
|
145
|
-
( expires_on.to_date - Time.now.to_date ).to_i < 7
|
146
|
-
end
|
147
|
-
|
148
|
-
## If I'm near below water
|
149
|
-
##
|
150
|
-
## expires_on = cc.expires_on ; strategy = cc.strategy ; strike = cc.strike ; nil
|
151
|
-
def must_roll?
|
152
|
-
if ( current_underlying_strike + strategy.buffer_above_water ) > strike
|
153
|
-
return true
|
154
|
-
end
|
155
|
-
## @TODO: This one should not happen, I should log appropriately. _vp_ 2023-03-19
|
156
|
-
if ( expires_on.to_date - Time.now.to_date ).to_i < 1
|
157
|
-
return true
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
## strike = cc.strike ; strategy = cc.strategy ; nil
|
162
|
-
def near_below_water?
|
163
|
-
strike < current_underlying_strike + strategy.buffer_above_water
|
164
|
-
end
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
## 2023-03-18 _vp_ Continue.
|
169
|
-
## 2023-03-19 _vp_ Continue.
|
170
|
-
## 2023-08-05 _vp_ an Important method
|
171
|
-
##
|
172
|
-
## expires_on = cc.expires_on ; strategy = cc.strategy ; ticker = cc.ticker ; end_price = cc.end_price ; next_expires_on = cc.next_expires_on ; nil
|
173
|
-
##
|
174
|
-
## out.map { |p| [ p[:strikePrice], p[:delta] ] }
|
175
|
-
##
|
176
|
-
def next_position
|
177
|
-
return @next_position if @next_position
|
178
|
-
return {} if ![ STATUS_ACTIVE, STATUS_PROPOSED ].include?( status )
|
179
|
-
|
180
|
-
## 7 days ahead - not configurable so far
|
181
|
-
out = Tda::Option.get_quotes({
|
182
|
-
ticker: ticker,
|
183
|
-
expirationDate: next_expires_on,
|
184
|
-
contractType: 'CALL',
|
185
|
-
})
|
186
|
-
|
187
|
-
## above_water
|
188
|
-
if strategy.buffer_above_water.present?
|
189
|
-
out = out.select do |i|
|
190
|
-
i[:strikePrice] > current_underlying_strike + strategy.buffer_above_water
|
191
|
-
end
|
192
|
-
# next_reasons.push "buffer_above_water above #{current_underlying_strike + strategy.buffer_above_water}"
|
193
|
-
end
|
194
|
-
|
195
|
-
if near_below_water?
|
196
|
-
msg = "Panic! climb at a loss. Skip the rest of the calculation."
|
197
|
-
next_reasons.push msg
|
198
|
-
## @TODO: if not enough money in the purse, cannot roll? 2023-03-19
|
199
|
-
|
200
|
-
# byebug
|
201
|
-
|
202
|
-
## Take a small loss here.
|
203
|
-
prev = nil
|
204
|
-
out.each_with_index do |i, idx|
|
205
|
-
next if idx == 0
|
206
|
-
if i[:last] < end_price
|
207
|
-
prev ||= i
|
208
|
-
end
|
209
|
-
end
|
210
|
-
out = [ prev ]
|
211
|
-
|
212
|
-
else
|
213
|
-
## Normal flow, making money.
|
214
|
-
## @TODO: test! _vp_ 2023-03-19
|
215
|
-
|
216
|
-
## next_min_strike
|
217
|
-
if strategy.next_min_strike.present?
|
218
|
-
out = out.select do |i|
|
219
|
-
i[:strikePrice] >= strategy.next_min_strike
|
220
|
-
end
|
221
|
-
# next_reasons.push "next_min_strike above #{strategy.next_min_strike}"
|
222
|
-
end
|
223
|
-
# json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_min_strike'
|
224
|
-
|
225
|
-
## max_delta
|
226
|
-
if strategy.next_max_delta.present?
|
227
|
-
out = out.select do |i|
|
228
|
-
i[:delta] = 0.0 if i[:delta] == "NaN"
|
229
|
-
i[:delta] <= strategy.next_max_delta
|
230
|
-
end
|
231
|
-
# next_reasons.push "next_max_delta below #{strategy.next_max_delta}"
|
232
|
-
end
|
233
|
-
# json_puts! out.map { |p| [p[:delta], p[:symbol]] }, 'next_max_delta'
|
234
|
-
end
|
235
|
-
|
236
|
-
@next_position = out[0] || {}
|
237
|
-
end
|
238
|
-
|
239
|
-
## @TODO: Test this. _vp_ 2023-04-01
|
240
|
-
def next_expires_on
|
241
|
-
out = expires_on.to_time + 7.days
|
242
|
-
while !out.friday?
|
243
|
-
out = out + 1.day
|
244
|
-
end
|
245
|
-
while !out.workday?
|
246
|
-
out = out - 1.day
|
247
|
-
end
|
248
|
-
return out
|
249
|
-
end
|
250
|
-
|
251
|
-
end
|