wco_models 3.1.0.266 → 3.1.0.267
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 +16 -3
- data/app/models/iro/stock.rb +33 -4
- 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: a1f54c396bd801b8243d24de86159a104d390d1b74ce79bd12c3bde3bda2d682
|
|
4
|
+
data.tar.gz: 137492819d59dc6cffb3a220f74f4352970dd69a3d8a01a585e3f17c7260dd1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8b9b3d27b172ad9094d5231865e2864881696718bb014a4ae7c73405a5e58eb4df313ef1340a14bbc6109293b1656e060493d0127eb00988bb9f8400ac92d7f
|
|
7
|
+
data.tar.gz: cc019d6173c28f78dd506300a8bd692086d9d84752e2744dfa547d03d61dc3a47513af1be09653e588200ed444d0528bf5c940294d3c65e2b7e64a8d17a3139e
|
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,15 @@ 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
|
+
INTENT_CLOSE = 'close.'
|
|
29
|
+
INTENTS = [ nil, INTENT_CLOSE ]
|
|
30
|
+
field :intent
|
|
23
31
|
|
|
24
32
|
belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
|
|
25
33
|
index({ purse_id: 1, ticker: 1 })
|
|
@@ -138,6 +146,13 @@ class Iro::Position
|
|
|
138
146
|
|
|
139
147
|
field :pending_price
|
|
140
148
|
|
|
149
|
+
## credit spread only
|
|
150
|
+
def close_price
|
|
151
|
+
pos = self
|
|
152
|
+
out = pos.outer.end_price - pos.inner.end_price
|
|
153
|
+
return out.round(2)
|
|
154
|
+
end
|
|
155
|
+
|
|
141
156
|
## place2 = credit-spread
|
|
142
157
|
def place2_price
|
|
143
158
|
pos = self
|
|
@@ -207,8 +222,6 @@ class Iro::Position
|
|
|
207
222
|
def calc_rollp
|
|
208
223
|
pos = self
|
|
209
224
|
pos.next_reasons = []
|
|
210
|
-
# pos.next_symbol = nil
|
|
211
|
-
# pos.next_delta = nil
|
|
212
225
|
|
|
213
226
|
out = strategy.send("calc_rollp_#{strategy.kind}", pos )
|
|
214
227
|
|
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|
|
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
|