iron_warbler 2.0.7.22 → 2.0.7.24
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/application.css +12 -0
- data/app/assets/stylesheets/iron_warbler/positions.scss +2 -0
- data/app/assets/stylesheets/iron_warbler/positions_gameui.scss +11 -0
- data/app/assets/stylesheets/iron_warbler/purses_gameui.scss +11 -32
- data/app/assets/stylesheets/iron_warbler/purses_summary.scss +98 -0
- data/app/assets/stylesheets/iron_warbler/utils.scss +4 -0
- data/app/controllers/iro/alerts_controller.rb +2 -0
- data/app/controllers/iro/application_controller.rb +13 -0
- data/app/controllers/iro/positions_controller.rb +197 -23
- data/app/controllers/iro/purses_controller.rb +60 -6
- data/app/controllers/iro/stocks_controller.rb +30 -18
- data/app/controllers/iro/strategies_controller.rb +12 -1
- data/app/models/iro/alert.rb +1 -0
- data/app/models/iro/datapoint.rb +7 -7
- data/app/models/iro/option.rb +174 -3
- data/app/models/iro/position.rb +124 -56
- data/app/models/iro/purse.rb +18 -4
- data/app/models/iro/stock.rb +84 -0
- data/app/models/iro/strategy.rb +122 -27
- data/app/models/tda/option.rb +104 -8
- data/app/views/iro/_main_header.haml +28 -17
- data/app/views/iro/application/home.haml +6 -1
- data/app/views/iro/positions/_form.haml +1 -2
- data/app/views/iro/positions/_gameui_covered_call.haml +10 -13
- data/app/views/iro/positions/_gameui_covered_call.haml-bk +8 -0
- data/app/views/iro/positions/_gameui_long_debit_call_spread.haml +5 -4
- data/app/views/iro/positions/_gameui_short_debit_put_spread.haml +5 -4
- data/app/views/iro/positions/_header.haml +2 -2
- data/app/views/iro/positions/_header_covered_call.haml +7 -1
- data/app/views/iro/positions/_header_long_debit_call_spread.haml +19 -1
- data/app/views/iro/positions/_prepare_covered_call.haml +25 -0
- data/app/views/iro/positions/_prepare_long_debit_call_spread.haml +23 -0
- data/app/views/iro/positions/_prepare_short_debit_put_spread.haml +23 -0
- data/app/views/iro/positions/_table.haml +33 -24
- data/app/views/iro/positions/prepare.haml +24 -0
- data/app/views/iro/positions/roll-cc.haml-bk +40 -0
- data/app/views/iro/positions/roll.haml-trash +42 -0
- data/app/views/iro/purses/_form.haml +9 -0
- data/app/views/iro/purses/_form_extra_fields.haml +25 -13
- data/app/views/iro/purses/_header.haml +15 -16
- data/app/views/iro/purses/_summary.haml +69 -0
- data/app/views/iro/purses/gameui.haml +13 -7
- data/app/views/iro/purses/index.haml +3 -1
- data/app/views/iro/purses/show.haml +5 -1
- data/app/views/iro/stocks/_form.haml +5 -0
- data/app/views/iro/stocks/_grid_is_long.haml +2 -2
- data/app/views/iro/stocks/_grid_is_short.haml +2 -2
- data/app/views/iro/stocks/index.haml +3 -2
- data/app/views/iro/strategies/_form.haml +12 -6
- data/app/views/iro/strategies/_show.haml +3 -3
- data/app/views/iro/strategies/_table.haml +15 -6
- data/app/views/iro/strategies/show.haml +14 -0
- data/app/views/layouts/iro/application.haml +4 -0
- data/config/routes.rb +6 -2
- data/lib/tasks/db_tasks.rake +1 -1
- data/lib/tasks/test_tasks.rake +54 -0
- metadata +28 -6
- data/app/models/iro/trash/position_covered_call.rb +0 -4
- data/app/models/iro/trash/position_debit_spread.rb +0 -251
- data/app/views/iro/positions/roll.haml +0 -83
- data/app/views/iro/positions/trash/_header_short_debit_put_spread.haml +0 -9
@@ -2,32 +2,48 @@
|
|
2
2
|
class Iro::StocksController < Iro::ApplicationController
|
3
3
|
before_action :set_stock, only: [:show, :edit, :update, :destroy]
|
4
4
|
|
5
|
+
def create
|
6
|
+
@stock = Iro::Stock.new(stock_params)
|
7
|
+
authorize! :create, @stock
|
8
|
+
|
9
|
+
if @stock.save
|
10
|
+
flash_notice @stock
|
11
|
+
else
|
12
|
+
flash_alert @stock
|
13
|
+
end
|
14
|
+
redirect_to action: :index
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
@stock.destroy
|
19
|
+
redirect_to stocks_url, notice: 'Stock was successfully destroyed.'
|
20
|
+
end
|
21
|
+
|
22
|
+
def edit
|
23
|
+
end
|
24
|
+
|
5
25
|
def index
|
6
26
|
@stocks = Iro::Stock.all
|
7
27
|
authorize! :index, Iro::Stock
|
8
28
|
end
|
9
29
|
|
10
|
-
def show
|
11
|
-
end
|
12
|
-
|
13
30
|
def new
|
14
31
|
@stock = Iro::Stock.new
|
15
32
|
authorize! :new, @stock
|
16
33
|
end
|
17
34
|
|
18
|
-
def
|
35
|
+
def refresh
|
36
|
+
authorize! :refresh, Iro::Stock
|
37
|
+
tickers = Iro::Stock.all.map { |s| s.ticker }.join(',')
|
38
|
+
outs = Tda::Stock.get_quotes tickers
|
39
|
+
outs.map do |out|
|
40
|
+
Iro::Stock.where( ticker: out[:symbol] ).update( last: out[:last] )
|
41
|
+
end
|
42
|
+
flash_notice 'ok'
|
43
|
+
redirect_to request.referrer
|
19
44
|
end
|
20
45
|
|
21
|
-
def
|
22
|
-
@stock = Iro::Stock.new(stock_params)
|
23
|
-
authorize! :create, @stock
|
24
|
-
|
25
|
-
if @stock.save
|
26
|
-
flash_notice @stock
|
27
|
-
else
|
28
|
-
flash_alert @stock
|
29
|
-
end
|
30
|
-
redirect_to action: :index
|
46
|
+
def show
|
31
47
|
end
|
32
48
|
|
33
49
|
def update
|
@@ -41,10 +57,6 @@ class Iro::StocksController < Iro::ApplicationController
|
|
41
57
|
redirect_to request.referrer
|
42
58
|
end
|
43
59
|
|
44
|
-
def destroy
|
45
|
-
@stock.destroy
|
46
|
-
redirect_to stocks_url, notice: 'Stock was successfully destroyed.'
|
47
|
-
end
|
48
60
|
|
49
61
|
##
|
50
62
|
## private
|
@@ -32,6 +32,7 @@ class Iro::StrategiesController < Iro::ApplicationController
|
|
32
32
|
def index
|
33
33
|
authorize! :index, Iro::Strategy
|
34
34
|
@strategies = Iro::Strategy.all
|
35
|
+
|
35
36
|
# render '_table'
|
36
37
|
end
|
37
38
|
|
@@ -40,6 +41,13 @@ class Iro::StrategiesController < Iro::ApplicationController
|
|
40
41
|
authorize! :new, @posision
|
41
42
|
end
|
42
43
|
|
44
|
+
def show
|
45
|
+
@strategy = Iro::Strategy.find params[:id]
|
46
|
+
authorize! :show, @strategy
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
43
51
|
def update
|
44
52
|
@strategy = Iro::Strategy.find params[:id]
|
45
53
|
authorize! :update, @strategy
|
@@ -59,8 +67,11 @@ class Iro::StrategiesController < Iro::ApplicationController
|
|
59
67
|
private
|
60
68
|
|
61
69
|
def set_lists
|
70
|
+
super
|
71
|
+
|
72
|
+
@purses_list = Iro::Purse.list
|
62
73
|
@strategies_list = Iro::Strategy.list
|
63
|
-
@
|
74
|
+
@stocks_list = Iro::Stock.list
|
64
75
|
end
|
65
76
|
|
66
77
|
end
|
data/app/models/iro/alert.rb
CHANGED
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,15 +1,186 @@
|
|
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
|
5
8
|
include Mongoid::Timestamps
|
9
|
+
include Mongoid::Paranoia
|
6
10
|
store_in collection: 'iro_options'
|
7
11
|
|
8
|
-
|
9
|
-
|
12
|
+
attr_accessor :recompute
|
13
|
+
|
14
|
+
field :ticker
|
15
|
+
validates :ticker, presence: true
|
10
16
|
|
11
17
|
field :symbol
|
12
18
|
validates :symbol, uniqueness: true, presence: true
|
13
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
|
+
def symbol
|
34
|
+
if !self[:symbol]
|
35
|
+
p_c_ = put_call == 'PUT' ? 'P' : 'C'
|
36
|
+
strike_ = strike.to_i == strike ? strike.to_i : strike
|
37
|
+
sym = "#{stock.ticker}_#{expires_on.strftime("%m%d%y")}#{p_c_}#{strike_}" # XYZ_011819P45
|
38
|
+
self[:symbol] = sym
|
39
|
+
save
|
40
|
+
end
|
41
|
+
self[:symbol]
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
## black-scholes pricing
|
46
|
+
##
|
47
|
+
|
48
|
+
=begin
|
49
|
+
##
|
50
|
+
##
|
51
|
+
##
|
52
|
+
annual to daily:
|
53
|
+
|
54
|
+
AR = ((DR + 1)^365 – 1) x 100
|
55
|
+
|
56
|
+
##
|
57
|
+
##
|
58
|
+
##
|
59
|
+
From: https://www.investopedia.com/articles/optioninvestor/07/options_beat_market.asp
|
60
|
+
|
61
|
+
K :: strike price
|
62
|
+
S_t :: last
|
63
|
+
r :: risk-free rate
|
64
|
+
t :: time to maturity
|
65
|
+
|
66
|
+
C = S_t N( d1 ) - K e^-rt N( d2 )
|
67
|
+
|
68
|
+
d1 = ln( St / K ) + (r + theta**2 / 2 )t
|
69
|
+
/{ theta_s * sqrt( t ) }
|
70
|
+
|
71
|
+
d2 = d1 - theta_s sqrt( t )
|
72
|
+
|
73
|
+
##
|
74
|
+
## From: https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model
|
75
|
+
##
|
76
|
+
|
77
|
+
D :: e^(rt) # discount factor
|
78
|
+
F :: e^(rt) S # forward price of underlying
|
79
|
+
|
80
|
+
C(F,t) = D[ N(d1)F - N(d2)K ]
|
81
|
+
|
82
|
+
d1 = ln(F/K) + stdev**2 t / 2
|
83
|
+
/{ stdev sqrt(t) }
|
84
|
+
d2 = d1 - stdev sqrt(t)
|
85
|
+
|
86
|
+
##
|
87
|
+
## From: https://www.daytrading.com/options-pricing-models
|
88
|
+
##
|
89
|
+
C0 = S0N(d1) – Xe-rtN(d2)
|
90
|
+
|
91
|
+
C0 = current call premium
|
92
|
+
S0 = current stock price
|
93
|
+
N(d1) = the probability that a value in a normal distribution will be less than d
|
94
|
+
N(d2) = the probability that the option will be in the money by expiration
|
95
|
+
X = strike price of the option
|
96
|
+
T = time until expiration (expressed in years)
|
97
|
+
r = risk-free interest rate
|
98
|
+
e = 2.71828, the base of the natural logarithm
|
99
|
+
ln = natural logarithm function
|
100
|
+
σ = standard deviation of the stock’s annualized rate of return (compounded continuously)
|
101
|
+
d1 = ln(S0/X) + (r + σ2/2)Tσ√T
|
102
|
+
|
103
|
+
d2 = d1 – σ√T
|
104
|
+
|
105
|
+
Note that:
|
106
|
+
|
107
|
+
Xe-rt = X/ert = the present value of the strike price using a continuously compounded interest rate
|
108
|
+
|
109
|
+
##
|
110
|
+
## From: https://www.wallstreetmojo.com/black-scholes-model/
|
111
|
+
##
|
112
|
+
|
113
|
+
|
114
|
+
## init
|
115
|
+
require 'distribution'
|
116
|
+
N = Distribution::Normal
|
117
|
+
stock = Iro::Stock.find_by ticker: 'NVDA'
|
118
|
+
strike = 910.0
|
119
|
+
r = Iro::Option.rate_daily
|
120
|
+
stdev = 91.0
|
121
|
+
t = 7.0
|
122
|
+
expires_on = '2024-03-22'
|
123
|
+
|
124
|
+
=end
|
125
|
+
def d1
|
126
|
+
last = stock.last
|
127
|
+
r = self.class.rate_annual
|
128
|
+
|
129
|
+
out = Math.log( last / strike ) + ( r + stdev**2 / 2 ) * t
|
130
|
+
out = out /( stdev * Math.sqrt(t) )
|
131
|
+
return out
|
132
|
+
end
|
133
|
+
def d2
|
134
|
+
last = stock.last
|
135
|
+
r = self.class.rate_annual
|
136
|
+
|
137
|
+
out = d1 - stdev * Math.sqrt( t )
|
138
|
+
return out
|
139
|
+
end
|
140
|
+
def t
|
141
|
+
# t = 1.0 / 365 * Date.today.business_days_until( expires_on )
|
142
|
+
t = 1.0 / 365 * (expires_on - Date.today).to_i
|
143
|
+
end
|
144
|
+
def stdev
|
145
|
+
recompute = nil
|
146
|
+
stock.stdev( recompute: recompute )
|
147
|
+
end
|
148
|
+
def call_price
|
149
|
+
last = stock.last
|
150
|
+
r = self.class.rate_annual
|
151
|
+
|
152
|
+
out = N.cdf( d1 ) * last - N.cdf( d2 ) * strike * Math::E**( -1 * r * t )
|
153
|
+
return out
|
154
|
+
end
|
155
|
+
|
156
|
+
def put_price
|
157
|
+
last = stock.last
|
158
|
+
r = self.class.rate_annual
|
159
|
+
|
160
|
+
out = N.cdf(-d2) * strike * exp(-r*t) - N.cdf(-d1) * last
|
161
|
+
return out
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def self.rate_annual
|
166
|
+
0.05
|
167
|
+
end
|
168
|
+
|
169
|
+
=begin
|
170
|
+
# test
|
171
|
+
|
172
|
+
inn = 100
|
173
|
+
n.times { inn = inn*(1.0+out) }
|
174
|
+
inn
|
175
|
+
|
176
|
+
=end
|
177
|
+
def self.rate_daily
|
178
|
+
n = 250.0 # days
|
179
|
+
# n = 12 # months
|
180
|
+
|
181
|
+
out = (1.0+self.rate_annual)**(1.0/n) - 1.0
|
182
|
+
puts! out, 'rate_daily'
|
183
|
+
return out
|
184
|
+
end
|
14
185
|
|
15
186
|
end
|
data/app/models/iro/position.rb
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
class Iro::Position
|
3
3
|
include Mongoid::Document
|
4
4
|
include Mongoid::Timestamps
|
5
|
+
include Mongoid::Paranoia
|
5
6
|
store_in collection: 'iro_positions'
|
6
7
|
|
7
|
-
attr_accessor :
|
8
|
+
attr_accessor :next_gain_loss_amount
|
8
9
|
|
9
10
|
STATUS_ACTIVE = 'active'
|
10
11
|
STATUS_PROPOSED = 'proposed'
|
@@ -53,8 +54,15 @@ class Iro::Position
|
|
53
54
|
field :end_inner_price, type: :float
|
54
55
|
field :end_inner_delta, type: :float
|
55
56
|
|
57
|
+
def begin_delta
|
58
|
+
strategy.send("begin_delta_#{strategy.kind}", self)
|
59
|
+
end
|
60
|
+
def end_delta
|
61
|
+
strategy.send("end_delta_#{strategy.kind}", self)
|
62
|
+
end
|
63
|
+
|
56
64
|
def breakeven
|
57
|
-
strategy.
|
65
|
+
strategy.send("breakeven_#{strategy.kind}", self)
|
58
66
|
end
|
59
67
|
|
60
68
|
def current_underlying_strike
|
@@ -87,6 +95,9 @@ class Iro::Position
|
|
87
95
|
def max_loss # each
|
88
96
|
strategy.send("max_loss_#{strategy.kind}", self)
|
89
97
|
end
|
98
|
+
# def gain_loss_amount
|
99
|
+
# strategy.send("gain_loss_amount_#{strategy.kind}", self)
|
100
|
+
# end
|
90
101
|
|
91
102
|
|
92
103
|
field :next_delta, type: :float
|
@@ -94,52 +105,122 @@ class Iro::Position
|
|
94
105
|
field :next_symbol
|
95
106
|
field :next_mark
|
96
107
|
field :next_reasons, type: :array, default: []
|
97
|
-
field :
|
98
|
-
|
99
|
-
|
100
|
-
##
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
field :rollp, type: :float
|
109
|
+
|
110
|
+
|
111
|
+
## covered call
|
112
|
+
# def sync
|
113
|
+
# puts! [ inner_strike, expires_on, stock.ticker ], 'init sync'
|
114
|
+
# out = Tda::Option.get_quote({
|
115
|
+
# contractType: 'CALL',
|
116
|
+
# strike: inner_strike,
|
117
|
+
# expirationDate: expires_on,
|
118
|
+
# ticker: stock.ticker,
|
119
|
+
# })
|
120
|
+
# puts! out, 'sync'
|
121
|
+
# self.end_inner_price = ( out.bid + out.ask ) / 2
|
122
|
+
# self.end_inner_delta = out.delta
|
123
|
+
# end
|
124
|
+
|
125
|
+
## long call spread
|
126
|
+
# def sync
|
127
|
+
# # puts! [
|
128
|
+
# # [ inner_strike, expires_on, stock.ticker ],
|
129
|
+
# # [ outer_strike, expires_on, stock.ticker ],
|
130
|
+
# # ], 'init sync inner, outer'
|
131
|
+
# inner = Tda::Option.get_quote({
|
132
|
+
# contractType: 'CALL',
|
133
|
+
# strike: inner_strike,
|
134
|
+
# expirationDate: expires_on,
|
135
|
+
# ticker: stock.ticker,
|
136
|
+
# })
|
137
|
+
# outer = Tda::Option.get_quote({
|
138
|
+
# contractType: 'CALL',
|
139
|
+
# strike: outer_strike,
|
140
|
+
# expirationDate: expires_on,
|
141
|
+
# ticker: stock.ticker,
|
142
|
+
# })
|
143
|
+
# puts! [inner, outer], 'sync inner, outer'
|
144
|
+
# self.end_outer_price = ( outer.bid + outer.ask ) / 2
|
145
|
+
# self.end_outer_delta = outer.delta
|
146
|
+
|
147
|
+
# self.end_inner_price = ( inner.bid + inner.ask ) / 2
|
148
|
+
# self.end_inner_delta = inner.delta
|
149
|
+
# end
|
150
|
+
|
151
|
+
def sync
|
152
|
+
put_call = Iro::Strategy::LONG == strategy.long_or_short ? 'CALL' : 'PUT'
|
153
|
+
puts! [
|
154
|
+
[ inner_strike, expires_on, stock.ticker ],
|
155
|
+
[ outer_strike, expires_on, stock.ticker ],
|
156
|
+
], 'init sync inner, outer'
|
157
|
+
inner = Tda::Option.get_quote({
|
158
|
+
contractType: put_call,
|
159
|
+
strike: inner_strike,
|
160
|
+
expirationDate: expires_on,
|
161
|
+
ticker: stock.ticker,
|
162
|
+
})
|
163
|
+
outer = Tda::Option.get_quote({
|
164
|
+
contractType: put_call,
|
165
|
+
strike: outer_strike,
|
166
|
+
expirationDate: expires_on,
|
167
|
+
ticker: stock.ticker,
|
110
168
|
})
|
169
|
+
puts! [inner, outer], 'sync inner, outer'
|
170
|
+
self.end_outer_price = ( outer.bid + outer.ask ) / 2
|
171
|
+
self.end_outer_delta = outer.delta
|
111
172
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
173
|
+
self.end_inner_price = ( inner.bid + inner.ask ) / 2
|
174
|
+
self.end_inner_delta = inner.delta
|
175
|
+
end
|
176
|
+
def sync_short_debit_put_spread
|
177
|
+
puts! [
|
178
|
+
[ inner_strike, expires_on, stock.ticker ],
|
179
|
+
[ outer_strike, expires_on, stock.ticker ],
|
180
|
+
], 'init sync inner, outer'
|
181
|
+
inner = Tda::Option.get_quote({
|
182
|
+
contractType: 'PUT',
|
183
|
+
strike: inner_strike,
|
184
|
+
expirationDate: expires_on,
|
185
|
+
ticker: stock.ticker,
|
186
|
+
})
|
187
|
+
outer = Tda::Option.get_quote({
|
188
|
+
contractType: 'PUT',
|
189
|
+
strike: outer_strike,
|
190
|
+
expirationDate: expires_on,
|
191
|
+
ticker: stock.ticker,
|
192
|
+
})
|
193
|
+
puts! [inner, outer], 'sync inner, outer'
|
194
|
+
self.end_outer_price = ( outer.bid + outer.ask ) / 2
|
195
|
+
self.end_outer_delta = outer.delta
|
126
196
|
|
127
|
-
|
128
|
-
|
129
|
-
|
197
|
+
self.end_inner_price = ( inner.bid + inner.ask ) / 2
|
198
|
+
self.end_inner_delta = inner.delta
|
199
|
+
end
|
130
200
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
next_symbol: next_position[:symbol],
|
135
|
-
next_mark: next_position[:mark],
|
136
|
-
should_rollp: out,
|
137
|
-
# status: Iro::Position::STATE_PROPOSED,
|
138
|
-
})
|
201
|
+
##
|
202
|
+
## decisions
|
203
|
+
##
|
139
204
|
|
140
|
-
|
141
|
-
|
142
|
-
|
205
|
+
def calc_rollp
|
206
|
+
self.next_reasons = []
|
207
|
+
self.next_symbol = nil
|
208
|
+
self.next_delta = nil
|
209
|
+
|
210
|
+
out = strategy.send( "calc_rollp_#{strategy.kind}", self )
|
211
|
+
|
212
|
+
self.rollp = out[0]
|
213
|
+
self.next_reasons.push out[1]
|
214
|
+
save
|
215
|
+
|
216
|
+
# update({
|
217
|
+
# next_delta: next_position[:delta],
|
218
|
+
# next_outcome: next_position[:mark] - end_price,
|
219
|
+
# next_symbol: next_position[:symbol],
|
220
|
+
# next_mark: next_position[:mark],
|
221
|
+
# should_rollp: out,
|
222
|
+
# # status: Iro::Position::STATE_PROPOSED,
|
223
|
+
# })
|
143
224
|
end
|
144
225
|
|
145
226
|
|
@@ -149,19 +230,6 @@ class Iro::Position
|
|
149
230
|
( expires_on.to_date - Time.now.to_date ).to_i < 7
|
150
231
|
end
|
151
232
|
|
152
|
-
## If I'm near below water
|
153
|
-
##
|
154
|
-
## expires_on = cc.expires_on ; strategy = cc.strategy ; strike = cc.strike ; nil
|
155
|
-
def must_roll?
|
156
|
-
if ( current_underlying_strike + strategy.buffer_above_water ) > strike
|
157
|
-
return true
|
158
|
-
end
|
159
|
-
## @TODO: This one should not happen, I should log appropriately. _vp_ 2023-03-19
|
160
|
-
if ( expires_on.to_date - Time.now.to_date ).to_i < 1
|
161
|
-
return true
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
233
|
## strike = cc.strike ; strategy = cc.strategy ; nil
|
166
234
|
def near_below_water?
|
167
235
|
strike < current_underlying_strike + strategy.buffer_above_water
|
data/app/models/iro/purse.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
class Iro::Purse
|
3
3
|
include Mongoid::Document
|
4
4
|
include Mongoid::Timestamps
|
5
|
+
include Mongoid::Paranoia
|
5
6
|
store_in collection: 'iro_purses'
|
6
7
|
|
7
8
|
field :slug
|
@@ -10,12 +11,25 @@ class Iro::Purse
|
|
10
11
|
|
11
12
|
has_many :positions, class_name: 'Iro::Position', inverse_of: :purse
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
field :
|
14
|
+
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
|
15
|
+
|
16
|
+
field :unit, type: :integer, default: 10
|
17
|
+
|
18
|
+
## for rolling only:
|
19
|
+
field :height, type: :integer, default: 100
|
20
|
+
|
21
|
+
field :mark_every_n_usd, type: :float, default: 1
|
22
|
+
field :n_next_positions, type: :integer, default: 5
|
23
|
+
## with unit 10, sum_scale .001
|
24
|
+
## with unit 100, sum_scale .0001
|
25
|
+
field :summary_scale, type: :float, default: 0.001
|
26
|
+
|
27
|
+
field :available_amount, type: :float
|
16
28
|
|
17
29
|
def to_s
|
18
30
|
slug
|
19
31
|
end
|
20
|
-
|
32
|
+
def self.list
|
33
|
+
[[nil,nil]] + all.map { |p| [p, p.id] }
|
34
|
+
end
|
21
35
|
end
|
data/app/models/iro/stock.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
include Math
|
2
|
+
require 'business_time'
|
1
3
|
|
2
4
|
class Iro::Stock
|
3
5
|
include Mongoid::Document
|
4
6
|
include Mongoid::Timestamps
|
7
|
+
include Mongoid::Paranoia
|
5
8
|
store_in collection: 'iro_stocks'
|
6
9
|
|
7
10
|
STATUS_ACTIVE = 'active'
|
@@ -17,9 +20,18 @@ class Iro::Stock
|
|
17
20
|
index({ ticker: -1 }, { unique: true })
|
18
21
|
|
19
22
|
field :last, type: :float
|
23
|
+
field :options_price_increment, type: :float
|
24
|
+
|
25
|
+
field :stdev, type: :float
|
20
26
|
|
21
27
|
has_many :positions, class_name: 'Iro::Position', inverse_of: :stock
|
22
28
|
has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :stock
|
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
|
23
35
|
|
24
36
|
def to_s
|
25
37
|
ticker
|
@@ -30,4 +42,76 @@ class Iro::Stock
|
|
30
42
|
def self.tickers_list
|
31
43
|
[[nil,nil]] + all.map { |sss| [ sss.ticker, sss.ticker ] }
|
32
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
|
+
|
33
117
|
end
|