iron_warbler 2.0.7.23 → 2.0.7.24

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: 1b73ac7c5b7ca0d29fe20cd0db0dcdca4713a710790bff2cffe8c30c3a18d2d2
4
+ data.tar.gz: daef47e2b8841f0253e55d19a8c8703c7aff086c53711b52db3dfdb470a97a78
5
5
  SHA512:
6
- metadata.gz: 98dcf54902ba16226374f047be14008e280ae241591fa9182ca248d27c55a4328d8a2811de56d569ce7f9b0df24aaf88281a07f5c55edc99e665a1841cfbdc51
7
- data.tar.gz: ce86ed7930411a2aef0c45b1f0299b329246c9b04058b8a8817ebf7ac0092e58c241901d89f4adaf092f58120c8faf47f580924ef7456b427c4b8152dec8901b
6
+ metadata.gz: e59a49397b934c8537abe40eb23eba846aa770956e02168722b2eb5fd6a1e19581388dc43d3ecb6e211e90cffec5cf8b02ef5eaa6efdcb1f40b4599c4125c8e8
7
+ data.tar.gz: 8f10a4503e57e02d17ca0dd7a310fc73489dfd22fb23e23f16a79de72f3a589790c09e9facbfb2441daf3fcef2b93456bcac12ce7a767c35ce7e29c2ccffc48f
data/README.txt ADDED
@@ -0,0 +1,19 @@
1
+
2
+ = Install =
3
+
4
+ From: https://docs.galpy.org/en/latest/installation.html
5
+
6
+ brew install gsl
7
+
8
+ = Use =
9
+
10
+ calculator: https://www.omnicalculator.com/finance/black-scholes
11
+
12
+ From: https://pythoninoffice.com/calculate-black-scholes-option-price-in-python/
13
+
14
+ -=---
15
+
16
+
17
+ https://api.tdameritrade.com/v1/accounts/232718838/orders/13597943085
18
+
19
+
@@ -2,7 +2,8 @@
2
2
  .purses-gameuiW {
3
3
  border: 1px solid red;
4
4
  width: 100%;
5
- overflow: scroll;
5
+ // overflow-x: scroll;
6
+ overflow-y: hidden;
6
7
  }
7
8
 
8
9
  .purses-gameui {
@@ -78,7 +79,7 @@
78
79
  bottom: -10%;
79
80
  border: 2px dotted green;
80
81
  background: rgba(0,255,0, 0.5);
81
- height: 10%;
82
+ height: 0.5em;
82
83
  }
83
84
 
84
85
  .sprite {
@@ -142,32 +143,6 @@
142
143
  background: rgba(255,0,0, 0.75);
143
144
  }
144
145
 
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
146
  .grid-mark {
172
147
  // border: 1px solid red;
173
148
 
@@ -206,8 +181,8 @@
206
181
  .labelC {
207
182
  background: white;
208
183
  position: absolute;
209
- top: 1.5em;
210
- left: -1.5em;
184
+ bottom: -1em;
185
+ right: 0;
211
186
  }
212
187
  }
213
188
  }
@@ -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,178 @@ 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
+ 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
15
185
 
16
186
  end
@@ -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
@@ -5,10 +5,10 @@
5
5
  px/usd
6
6
    
7
7
 
8
- -# %label height
9
- -# = f.number_field :height
10
- -# px
11
- -#   
8
+ %label height
9
+ = f.number_field :height
10
+ px
11
+   
12
12
 
13
13
  %label mark_every_n_usd
14
14
  = f.number_field :mark_every_n_usd, step: 0.01
@@ -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
@@ -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.24
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
@@ -293,6 +306,7 @@ files:
293
306
  - lib/tasks/db_tasks.rake
294
307
  - lib/tasks/iro_tasks.rake
295
308
  - lib/tasks/iro_tasks.rake-old
309
+ - lib/tasks/test_tasks.rake
296
310
  homepage: https://wasya.co
297
311
  licenses:
298
312
  - 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