iron_warbler 2.0.7.23 → 2.0.7.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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