wco_models 3.1.0.266 → 3.1.0.268
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 +4 -4
- data/app/models/iro/option.rb +28 -0
- data/app/models/iro/position.rb +14 -3
- data/app/models/iro/purse.rb +3 -0
- data/app/models/iro/stock.rb +43 -4
- data/app/models/iro/strategy.rb +7 -1
- data/app/models/tda/order.rb +57 -22
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85a68fabb586fada92d893f953866bd0e5a1600988ad67c12770b89fa2fa625a
|
|
4
|
+
data.tar.gz: d4cfcef90359a44a3472ce03b3162c1480d304ae1b9af8cd08e9242d76e4e421
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1fc4ca80b0583b321f9cf4363c9ff3a37bbb42aca43ef7ca25e009cd8d4756da44b9555f7968254a1385a6e6a30b8e471a348fd57e750b1591d34e4518e0f2b
|
|
7
|
+
data.tar.gz: '039c8aa93e7a5c659bac9c5d5ad7377df287e93f1de7cf6071ed71491c29946247a43366e27f46648d2ac632821a7434446cbe565e9ef036288ef29bd07ec6b1'
|
data/app/models/iro/option.rb
CHANGED
|
@@ -66,6 +66,34 @@ class Iro::Option
|
|
|
66
66
|
sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
=begin
|
|
70
|
+
symbol = "META 260424P00500000"
|
|
71
|
+
=end
|
|
72
|
+
def self.symbol_to_h symbol
|
|
73
|
+
ticker = symbol[0,6].strip
|
|
74
|
+
date_str = symbol[6,6]
|
|
75
|
+
type = symbol[12] == 'P' ? 'PUT' : 'CALL'
|
|
76
|
+
strike_str = symbol[13,8]
|
|
77
|
+
expires_on = Date.strptime(date_str, "%y%m%d")
|
|
78
|
+
strike = strike_str.to_i / 1000.0
|
|
79
|
+
return {
|
|
80
|
+
ticker: ticker,
|
|
81
|
+
strike: strike,
|
|
82
|
+
put_call: type,
|
|
83
|
+
expires_on: expires_on,
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def matches_h h
|
|
88
|
+
if h[:put_call] == put_call &&
|
|
89
|
+
h[:strike] == strike &&
|
|
90
|
+
h[:expires_on] == expires_on
|
|
91
|
+
return true
|
|
92
|
+
else
|
|
93
|
+
return false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
69
97
|
# before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
|
|
70
98
|
def sync
|
|
71
99
|
out = Tda::Option.get_quote({
|
data/app/models/iro/position.rb
CHANGED
|
@@ -7,7 +7,6 @@ class Iro::Position
|
|
|
7
7
|
|
|
8
8
|
field :next_gain_loss_amount, type: :float
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
STATUS_ACTIVE = 'active'
|
|
12
11
|
STATUS_CLOSED = 'closed'
|
|
13
12
|
STATUS_PREPARE = 'prepare'
|
|
@@ -20,6 +19,13 @@ class Iro::Position
|
|
|
20
19
|
scope :active, ->{ where( status: 'active' ) }
|
|
21
20
|
scope :proposed, ->{ where( status: 'proposed' ) }
|
|
22
21
|
|
|
22
|
+
def status_label st
|
|
23
|
+
labels = {}
|
|
24
|
+
labels[STATUS_PROPOSED] = 'Selected.'
|
|
25
|
+
return labels[st] || st
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
field :intent
|
|
23
29
|
|
|
24
30
|
belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
|
|
25
31
|
index({ purse_id: 1, ticker: 1 })
|
|
@@ -138,6 +144,13 @@ class Iro::Position
|
|
|
138
144
|
|
|
139
145
|
field :pending_price
|
|
140
146
|
|
|
147
|
+
## credit spread only
|
|
148
|
+
def close_price
|
|
149
|
+
pos = self
|
|
150
|
+
out = pos.outer.end_price - pos.inner.end_price
|
|
151
|
+
return out.round(2)
|
|
152
|
+
end
|
|
153
|
+
|
|
141
154
|
## place2 = credit-spread
|
|
142
155
|
def place2_price
|
|
143
156
|
pos = self
|
|
@@ -207,8 +220,6 @@ class Iro::Position
|
|
|
207
220
|
def calc_rollp
|
|
208
221
|
pos = self
|
|
209
222
|
pos.next_reasons = []
|
|
210
|
-
# pos.next_symbol = nil
|
|
211
|
-
# pos.next_delta = nil
|
|
212
223
|
|
|
213
224
|
out = strategy.send("calc_rollp_#{strategy.kind}", pos )
|
|
214
225
|
|
data/app/models/iro/purse.rb
CHANGED
|
@@ -12,6 +12,9 @@ class Iro::Purse
|
|
|
12
12
|
validates :slug, presence: true, uniqueness: true
|
|
13
13
|
index({ slug: -1 }, { unique: true })
|
|
14
14
|
|
|
15
|
+
TEMPLATE_GAMEUI = 'gameui'
|
|
16
|
+
TEMPLATE_TABLE = 'show'
|
|
17
|
+
|
|
15
18
|
has_many :positions, class_name: 'Iro::Position', inverse_of: :purse
|
|
16
19
|
|
|
17
20
|
has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :purse
|
data/app/models/iro/stock.rb
CHANGED
|
@@ -73,12 +73,43 @@ class ::Iro::Stock
|
|
|
73
73
|
|
|
74
74
|
=end
|
|
75
75
|
field :volatility, type: :float
|
|
76
|
+
field :volatility_annual
|
|
77
|
+
field :volatility_monthly
|
|
78
|
+
field :volatility_daily
|
|
76
79
|
def volatility duration: 1.year, recompute: false
|
|
77
80
|
if self[:volatility]
|
|
78
81
|
if !recompute
|
|
79
82
|
return self[:volatility]
|
|
80
83
|
end
|
|
81
84
|
end
|
|
85
|
+
stock = self
|
|
86
|
+
begin_on = Time.now - duration
|
|
87
|
+
points = ::Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
|
|
88
|
+
:date.gte => begin_on,
|
|
89
|
+
).order_by( date: :asc )
|
|
90
|
+
|
|
91
|
+
returns = []
|
|
92
|
+
points.each_cons(2) do |prev, curr|
|
|
93
|
+
returns << Math.log(curr.value / prev.value)
|
|
94
|
+
end
|
|
95
|
+
# puts! returns, 'returns'
|
|
96
|
+
|
|
97
|
+
mean = returns.sum / returns.size
|
|
98
|
+
variance = returns.sum { |r| (r - mean) ** 2 } / (returns.size - 1)
|
|
99
|
+
|
|
100
|
+
daily_vol = Math.sqrt(variance)
|
|
101
|
+
monthly_vol = daily_vol * Math.sqrt(21)
|
|
102
|
+
annual_vol = daily_vol * Math.sqrt(252)
|
|
103
|
+
|
|
104
|
+
self.update(volatility_annual: annual_vol, volatility_monthly: monthly_vol, volatility_daily: daily_vol)
|
|
105
|
+
annual_vol
|
|
106
|
+
end
|
|
107
|
+
def volatility_old duration: 1.year, recompute: false
|
|
108
|
+
if self[:volatility]
|
|
109
|
+
if !recompute
|
|
110
|
+
return self[:volatility]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
82
113
|
|
|
83
114
|
stock = self
|
|
84
115
|
begin_on = Time.now - duration - 1.day
|
|
@@ -107,10 +138,8 @@ class ::Iro::Stock
|
|
|
107
138
|
|
|
108
139
|
# n_periods = begin_on.to_date.business_days_until( Date.today )
|
|
109
140
|
out = Math.sqrt( sum_of_sq )*sqrt( n )
|
|
110
|
-
adjustment = 2.0
|
|
111
|
-
out = out * adjustment
|
|
112
141
|
puts! out, 'volatility (adjusted)'
|
|
113
|
-
self.update volatility: out
|
|
142
|
+
self.update( volatility: out, volatility_annual: out )
|
|
114
143
|
return out
|
|
115
144
|
end
|
|
116
145
|
|
|
@@ -147,7 +176,7 @@ class ::Iro::Stock
|
|
|
147
176
|
date_from ||= Time.now - 1.year - 1.week
|
|
148
177
|
date_to ||= date_from + 180.days
|
|
149
178
|
date_from = date_from.strftime('%Y-%m-%d')
|
|
150
|
-
date_to
|
|
179
|
+
date_to = date_to.strftime('%Y-%m-%d')
|
|
151
180
|
puts! [ticker, date_from, date_to], "ticker,date_from,date_to"
|
|
152
181
|
outs = HTTParty.get("https://api.stockdata.org/v1/data/eod?symbols=#{ticker}&date_from=#{date_from}&date_to=#{date_to}&api_token=#{STOCKDATA_ORG_KEY}")
|
|
153
182
|
outs['data'].each do |datum|
|
|
@@ -169,4 +198,14 @@ class ::Iro::Stock
|
|
|
169
198
|
end
|
|
170
199
|
end
|
|
171
200
|
|
|
201
|
+
def self.sync
|
|
202
|
+
tickers = Iro::Stock.all.map { |s| s.ticker }.join(',')
|
|
203
|
+
outs = Tda::Stock.get_quotes tickers
|
|
204
|
+
outs.map do |out|
|
|
205
|
+
Iro::Stock.where( ticker: out[:symbol] ).update_all( last: out[:last] )
|
|
206
|
+
end
|
|
207
|
+
puts "+++ Synced stocks."
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
|
|
172
211
|
end
|
data/app/models/iro/strategy.rb
CHANGED
|
@@ -94,6 +94,12 @@ class Iro::Strategy
|
|
|
94
94
|
field :next_usd_above_mark, type: :float
|
|
95
95
|
validates :next_usd_above_mark, presence: true
|
|
96
96
|
|
|
97
|
+
INTENT_CLOSE = 'try-close'
|
|
98
|
+
INTENT_ROLL = 'try-roll'
|
|
99
|
+
INTENTS = [ nil, INTENT_CLOSE, INTENT_ROLL ]
|
|
100
|
+
field :intent
|
|
101
|
+
|
|
102
|
+
|
|
97
103
|
def begin_delta_covered_call p
|
|
98
104
|
p.inner.begin_delta
|
|
99
105
|
end
|
|
@@ -365,7 +371,7 @@ class Iro::Strategy
|
|
|
365
371
|
|
|
366
372
|
|
|
367
373
|
def to_s
|
|
368
|
-
"#{kind} #{stock} #{descr}"
|
|
374
|
+
"#{kind} #{stock} #{next_spread_amount}- #{intent} | #{descr}"
|
|
369
375
|
end
|
|
370
376
|
def self.list long_or_short = nil
|
|
371
377
|
these = long_or_short ? where( long_or_short: long_or_short ) : all
|
data/app/models/tda/order.rb
CHANGED
|
@@ -66,6 +66,39 @@ class Tda::Order
|
|
|
66
66
|
# end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
|
|
70
|
+
def self.close_credit_spread_q pos
|
|
71
|
+
query = {
|
|
72
|
+
orderType: pos.pending_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
|
|
73
|
+
session: "NORMAL",
|
|
74
|
+
duration: "DAY",
|
|
75
|
+
price: pos.pending_price.abs,
|
|
76
|
+
orderStrategyType: "SINGLE",
|
|
77
|
+
orderLegCollection: [
|
|
78
|
+
## close
|
|
79
|
+
{
|
|
80
|
+
instruction: "BUY_TO_CLOSE",
|
|
81
|
+
quantity: pos.q,
|
|
82
|
+
instrument: {
|
|
83
|
+
symbol: pos.inner.symbol,
|
|
84
|
+
assetType: "OPTION",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
instruction: "SELL_TO_CLOSE",
|
|
89
|
+
quantity: pos.q,
|
|
90
|
+
instrument: {
|
|
91
|
+
symbol: pos.outer.symbol,
|
|
92
|
+
assetType: "OPTION",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
return query
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
## open credit spread?!
|
|
69
102
|
def self.credit_spread_q pos
|
|
70
103
|
query = {
|
|
71
104
|
orderType: pos.place2_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
|
|
@@ -96,6 +129,30 @@ class Tda::Order
|
|
|
96
129
|
return query
|
|
97
130
|
end
|
|
98
131
|
|
|
132
|
+
|
|
133
|
+
def self.place_order! query
|
|
134
|
+
# puts! query, '#place_order'
|
|
135
|
+
|
|
136
|
+
profile = Wco::Profile.pi
|
|
137
|
+
results = self.post("/accounts/#{profile.schwab_account_hash}/orders", {
|
|
138
|
+
headers: {
|
|
139
|
+
'content-type' => 'application/json',
|
|
140
|
+
accept: 'application/json',
|
|
141
|
+
Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
|
|
142
|
+
},
|
|
143
|
+
body: query.to_json,
|
|
144
|
+
})
|
|
145
|
+
puts! results, 'place_order!() results'
|
|
146
|
+
puts! results.code, 'results.code'
|
|
147
|
+
# if 201 != results.code
|
|
148
|
+
# throw results
|
|
149
|
+
# end
|
|
150
|
+
order_id = results.headers['location'].split('/').last
|
|
151
|
+
# response = JSON.parse results.body
|
|
152
|
+
return { schwab_order_id: order_id, schwab_status: 'WORKING' }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
99
156
|
## obsolete, I don't do covered calls anymore?
|
|
100
157
|
def self.roll_covered_call_q pos
|
|
101
158
|
roll_price = pos.inner.begin_price - pos.autoprev.inner.end_price
|
|
@@ -180,26 +237,4 @@ class Tda::Order
|
|
|
180
237
|
return query
|
|
181
238
|
end
|
|
182
239
|
|
|
183
|
-
def self.place_order! query
|
|
184
|
-
# puts! query, '#place_order'
|
|
185
|
-
|
|
186
|
-
profile = Wco::Profile.pi
|
|
187
|
-
results = self.post("/accounts/#{profile.schwab_account_hash}/orders", {
|
|
188
|
-
headers: {
|
|
189
|
-
'content-type' => 'application/json',
|
|
190
|
-
accept: 'application/json',
|
|
191
|
-
Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
|
|
192
|
-
},
|
|
193
|
-
body: query.to_json,
|
|
194
|
-
})
|
|
195
|
-
puts! results, 'place_order!() results'
|
|
196
|
-
puts! results.code, 'results.code'
|
|
197
|
-
# if 201 != results.code
|
|
198
|
-
# throw results
|
|
199
|
-
# end
|
|
200
|
-
order_id = results.headers['location'].split('/').last
|
|
201
|
-
# response = JSON.parse results.body
|
|
202
|
-
return { schwab_order_id: order_id, schwab_status: 'WORKING' }
|
|
203
|
-
end
|
|
204
|
-
|
|
205
240
|
end
|