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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f64245aea1af513cc312b8c87be3ccfb2a81dbdd8d65842800dc88a29b264bab
4
- data.tar.gz: c0034386ae09bead5e5273e9d344a7fd8c85985d8687d588cb23e7fa251a9087
3
+ metadata.gz: 8184e7abf0df5faad6e8d99504be22235e0fa846801be7bd2869e7f81631349b
4
+ data.tar.gz: ac0dab40337f109eadccd06b943203e19c507abc9a05cc3e66f0f453b8275d82
5
5
  SHA512:
6
- metadata.gz: 98dcf54902ba16226374f047be14008e280ae241591fa9182ca248d27c55a4328d8a2811de56d569ce7f9b0df24aaf88281a07f5c55edc99e665a1841cfbdc51
7
- data.tar.gz: ce86ed7930411a2aef0c45b1f0299b329246c9b04058b8a8817ebf7ac0092e58c241901d89f4adaf092f58120c8faf47f580924ef7456b427c4b8152dec8901b
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
+
@@ -9,3 +9,11 @@
9
9
  }
10
10
  }
11
11
 
12
+ .select {
13
+ position: absolute;
14
+ z-index: 5;
15
+
16
+ right: -100px;
17
+ text-align: left;
18
+ width: calc(100px - 1em);
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: 90%;
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: -10%;
77
+ bottom: 0;
79
78
  border: 2px dotted green;
80
79
  background: rgba(0,255,0, 0.5);
81
- height: 10%;
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
- top: 1.5em;
210
- left: -1.5em;
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.new({
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_
@@ -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,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
- # field :ticker
10
- # validates :ticker, uniqueness: true, presence: true
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
@@ -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
 
@@ -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
- # field :height, type: :integer, default: 100
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
@@ -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
@@ -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() * u}px " }
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() *u}px " }
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}; border-right: #{border};" }
21
- .Position
22
- .MaxGain{ style: "width: #{pos.max_gain * u}px" }
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
@@ -0,0 +1,11 @@
1
+
2
+ - pos = @position
3
+
4
+ -## long call spread
5
+ %ul
6
+ %li
7
+ buy to close
8
+ = pos.prev.outer
9
+ %li sell to close
10
+ %li buy to open
11
+ %li sell to open
@@ -5,10 +5,10 @@
5
5
  px/usd
6
6
  &nbsp;&nbsp;
7
7
 
8
- -# %label height
9
- -# = f.number_field :height
10
- -# px
11
- -# &nbsp;&nbsp;
8
+ %label height
9
+ = f.number_field :height
10
+ px/q
11
+ &nbsp;&nbsp;
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
  &nbsp;&nbsp;
20
20
 
21
21
  %label summary_scale
22
- = f.number_field :summary_scale, step: 0.0001, style: "width: 120px"
22
+ = f.number_field :summary_scale, step: 0.000001, style: "width: 120px"
23
23
 
24
24
  &nbsp;&nbsp;
25
25
 
@@ -2,7 +2,8 @@
2
2
  - u = @purse.unit
3
3
 
4
4
  .purses--gameui
5
- .row
5
+
6
+ .row.padded
6
7
  .col-md-6
7
8
  = render '/iro/purses/header', purse: @purse
8
9
  .col-md-6
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', 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
- post 'positions/:id/roll', to: 'positions#do_roll', as: :roll_position
16
- get 'positions/:id/refresh', to: 'positions#refresh', as: :refresh_position
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
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  namespace :db do
3
3
 
4
- ## date, volume, open, high, low, close
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.23
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-18 00:00:00.000000000 Z
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,4 +0,0 @@
1
-
2
- class Iro::PositionCoveredCall < Iro::Position
3
-
4
- end
@@ -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