iron_warbler 2.0.7.23 → 2.0.7.24

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