iron_warbler 2.0.7.23 → 2.0.7.25
Sign up to get free protection for your applications and to get access to all the features.
- 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
|