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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.txt +19 -0
  3. data/app/assets/stylesheets/iron_warbler/application.css +12 -0
  4. data/app/assets/stylesheets/iron_warbler/positions.scss +2 -0
  5. data/app/assets/stylesheets/iron_warbler/positions_gameui.scss +11 -0
  6. data/app/assets/stylesheets/iron_warbler/purses_gameui.scss +11 -32
  7. data/app/assets/stylesheets/iron_warbler/purses_summary.scss +98 -0
  8. data/app/assets/stylesheets/iron_warbler/utils.scss +4 -0
  9. data/app/controllers/iro/alerts_controller.rb +2 -0
  10. data/app/controllers/iro/application_controller.rb +13 -0
  11. data/app/controllers/iro/positions_controller.rb +197 -23
  12. data/app/controllers/iro/purses_controller.rb +60 -6
  13. data/app/controllers/iro/stocks_controller.rb +30 -18
  14. data/app/controllers/iro/strategies_controller.rb +12 -1
  15. data/app/models/iro/alert.rb +1 -0
  16. data/app/models/iro/datapoint.rb +7 -7
  17. data/app/models/iro/option.rb +174 -3
  18. data/app/models/iro/position.rb +124 -56
  19. data/app/models/iro/purse.rb +18 -4
  20. data/app/models/iro/stock.rb +84 -0
  21. data/app/models/iro/strategy.rb +122 -27
  22. data/app/models/tda/option.rb +104 -8
  23. data/app/views/iro/_main_header.haml +28 -17
  24. data/app/views/iro/application/home.haml +6 -1
  25. data/app/views/iro/positions/_form.haml +1 -2
  26. data/app/views/iro/positions/_gameui_covered_call.haml +10 -13
  27. data/app/views/iro/positions/_gameui_covered_call.haml-bk +8 -0
  28. data/app/views/iro/positions/_gameui_long_debit_call_spread.haml +5 -4
  29. data/app/views/iro/positions/_gameui_short_debit_put_spread.haml +5 -4
  30. data/app/views/iro/positions/_header.haml +2 -2
  31. data/app/views/iro/positions/_header_covered_call.haml +7 -1
  32. data/app/views/iro/positions/_header_long_debit_call_spread.haml +19 -1
  33. data/app/views/iro/positions/_prepare_covered_call.haml +25 -0
  34. data/app/views/iro/positions/_prepare_long_debit_call_spread.haml +23 -0
  35. data/app/views/iro/positions/_prepare_short_debit_put_spread.haml +23 -0
  36. data/app/views/iro/positions/_table.haml +33 -24
  37. data/app/views/iro/positions/prepare.haml +24 -0
  38. data/app/views/iro/positions/roll-cc.haml-bk +40 -0
  39. data/app/views/iro/positions/roll.haml-trash +42 -0
  40. data/app/views/iro/purses/_form.haml +9 -0
  41. data/app/views/iro/purses/_form_extra_fields.haml +25 -13
  42. data/app/views/iro/purses/_header.haml +15 -16
  43. data/app/views/iro/purses/_summary.haml +69 -0
  44. data/app/views/iro/purses/gameui.haml +13 -7
  45. data/app/views/iro/purses/index.haml +3 -1
  46. data/app/views/iro/purses/show.haml +5 -1
  47. data/app/views/iro/stocks/_form.haml +5 -0
  48. data/app/views/iro/stocks/_grid_is_long.haml +2 -2
  49. data/app/views/iro/stocks/_grid_is_short.haml +2 -2
  50. data/app/views/iro/stocks/index.haml +3 -2
  51. data/app/views/iro/strategies/_form.haml +12 -6
  52. data/app/views/iro/strategies/_show.haml +3 -3
  53. data/app/views/iro/strategies/_table.haml +15 -6
  54. data/app/views/iro/strategies/show.haml +14 -0
  55. data/app/views/layouts/iro/application.haml +4 -0
  56. data/config/routes.rb +6 -2
  57. data/lib/tasks/db_tasks.rake +1 -1
  58. data/lib/tasks/test_tasks.rake +54 -0
  59. metadata +28 -6
  60. data/app/models/iro/trash/position_covered_call.rb +0 -4
  61. data/app/models/iro/trash/position_debit_spread.rb +0 -251
  62. data/app/views/iro/positions/roll.haml +0 -83
  63. 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 edit
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 create
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
- @tickers_list = Iro::Stock.list
74
+ @stocks_list = Iro::Stock.list
64
75
  end
65
76
 
66
77
  end
@@ -2,6 +2,7 @@
2
2
  class Iro::Alert
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
+ include Mongoid::Paranoia
5
6
  store_in collection: 'iro_alerts'
6
7
 
7
8
  # SLEEP_TIME_SECONDS = Rails.env.production? ? 60 : 15
@@ -138,15 +138,15 @@ class Iro::Datapoint
138
138
  flag = create({
139
139
  kind: 'STOCK',
140
140
  symbol: symbol,
141
- date: row['date'],
142
- quote_at: row['date'],
141
+ date: row['Date'],
142
+ quote_at: row['Date'],
143
143
 
144
- volume: row['volume'],
144
+ volume: row['Volume'],
145
145
 
146
- open: row['open'],
147
- high: row['high'],
148
- low: row['low'],
149
- value: row['close'],
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
@@ -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
- # field :ticker
9
- # validates :ticker, uniqueness: true, presence: true
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
@@ -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 :gain_loss_amount
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.breakeven(self)
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 :should_rollp, type: :float
98
-
99
- ##
100
- ## decisions
101
- ##
102
-
103
- def should_roll?
104
- puts! 'shold_roll?'
105
-
106
- update({
107
- next_reasons: [],
108
- next_symbol: nil,
109
- next_delta: nil,
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
- if must_roll?
113
- out = 1.0
114
- elsif can_roll?
115
-
116
- if end_delta < strategy.threshold_delta
117
- next_reasons.push "delta is lower than threshold"
118
- out = 0.91
119
- elsif 1 - end_outer_price/begin_outer_price > strategy.threshold_netp
120
- next_reasons.push "made enough percent profit (dubious)"
121
- out = 0.61
122
- else
123
- next_reasons.push "neutral"
124
- out = 0.33
125
- end
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
- else
128
- out = 0.0
129
- end
197
+ self.end_inner_price = ( inner.bid + inner.ask ) / 2
198
+ self.end_inner_delta = inner.delta
199
+ end
130
200
 
131
- update({
132
- next_delta: next_position[:delta],
133
- next_outcome: next_position[:mark] - end_price,
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
- puts! next_reasons, 'next_reasons'
141
- puts! out, 'out'
142
- return out > 0.5
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
@@ -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
- field :unit, type: :integer
14
- field :height, type: :integer
15
- field :mark_every_n_usd, type: :float
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
@@ -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