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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d52ff07afe73ec8c7240fbfb85a78a1cd4bf66c9db7c49fd69d5e7c3c56114aa
4
- data.tar.gz: 33592a3dd04ab62d82c36d77070323735112bdb5966f8ba3009ff182c593f51e
3
+ metadata.gz: 85a68fabb586fada92d893f953866bd0e5a1600988ad67c12770b89fa2fa625a
4
+ data.tar.gz: d4cfcef90359a44a3472ce03b3162c1480d304ae1b9af8cd08e9242d76e4e421
5
5
  SHA512:
6
- metadata.gz: 88fc66fef4da9de8509ff2a46088229c73ea4041aa2aee67c1cd2771dbec93bafa164b6a6d39b7e312e3f86000b36cfc554fb33f9677b495fde357df60ed1739
7
- data.tar.gz: 816df1b774e34f02dea1d1f23176a1ba5e0c00f84a1c04afb40084cd79af2671d1e28194b70ce8f90db6c239b672cc352ae8d27a00b9ac49e5f60977d49fad06
6
+ metadata.gz: c1fc4ca80b0583b321f9cf4363c9ff3a37bbb42aca43ef7ca25e009cd8d4756da44b9555f7968254a1385a6e6a30b8e471a348fd57e750b1591d34e4518e0f2b
7
+ data.tar.gz: '039c8aa93e7a5c659bac9c5d5ad7377df287e93f1de7cf6071ed71491c29946247a43366e27f46648d2ac632821a7434446cbe565e9ef036288ef29bd07ec6b1'
@@ -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({
@@ -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
 
@@ -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
@@ -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 = date_to.strftime('%Y-%m-%d')
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
@@ -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
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wco_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.266
4
+ version: 3.1.0.268
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Pudeyev