wco_models 3.1.0.265 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 327c2ab838cce926d5afd9e979b7acb2979aa8e78d1745470ea29ccac2e81197
4
- data.tar.gz: 42615e28beeb55afbdf91eb592c58b22dae4a89d168e2ba17ce9046a7ac68857
3
+ metadata.gz: a1f54c396bd801b8243d24de86159a104d390d1b74ce79bd12c3bde3bda2d682
4
+ data.tar.gz: 137492819d59dc6cffb3a220f74f4352970dd69a3d8a01a585e3f17c7260dd1c
5
5
  SHA512:
6
- metadata.gz: 9ca18a2f3190d96d1c8a2d9b62823886b8bb77870b22246a4af13f1f55081c65dd8bfc7b15e0db9fd232d0efc0968c7c4dd0ac8c9e0da1063c486b08fe2c5d9c
7
- data.tar.gz: 8c4c8a85bcd9087b837e6c70b42082967c9b2d502c9cc12651a946cceab10c6d85ca1d18646fa18f6b56862b51cd8436e9071a7eaa8bda059dbd6a1252cd65b3
6
+ metadata.gz: b8b9b3d27b172ad9094d5231865e2864881696718bb014a4ae7c73405a5e58eb4df313ef1340a14bbc6109293b1656e060493d0127eb00988bb9f8400ac92d7f
7
+ data.tar.gz: cc019d6173c28f78dd506300a8bd692086d9d84752e2744dfa547d03d61dc3a47513af1be09653e588200ed444d0528bf5c940294d3c65e2b7e64a8d17a3139e
@@ -37,9 +37,6 @@ $enable-important-utilities: false;
37
37
  border-bottom: 1px solid gray;
38
38
  border-left: 1px solid gray;
39
39
 
40
- // border-top: 2px solid white;
41
- // border-left: 2px solid white;
42
-
43
40
  display: flex;
44
41
 
45
42
  margin-top: 1em;
@@ -10,17 +10,10 @@ class Iro::Option
10
10
 
11
11
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
12
12
  def ticker; stock.ticker; end
13
- # field :ticker
14
- # validates :ticker, presence: true
15
13
 
16
14
  CALL = 'CALL'
17
15
  PUT = 'PUT'
18
16
 
19
- ## for now, recompute every time
20
- # field :symbol
21
- ## each option can be a leg in a position, no uniqueness
22
- # validates :symbol, uniqueness: true, presence: true
23
-
24
17
  field :put_call, type: :string # 'PUT' or 'CALL'
25
18
  validates :put_call, presence: true
26
19
 
@@ -29,11 +22,6 @@ class Iro::Option
29
22
  field :strike, type: :float
30
23
  validates :strike, presence: true
31
24
 
32
- def to_s
33
- "#{symbol} :: #{expires_on.strftime('%Y-%m-%d')} #{put_call} #{strike}"
34
- end
35
-
36
-
37
25
  field :expires_on, type: :date
38
26
  validates :expires_on, presence: true
39
27
  def self.expirations_list full: false, n: 5
@@ -70,38 +58,41 @@ class Iro::Option
70
58
 
71
59
  field :last, type: :float
72
60
 
73
- ## for TDA
74
- ## "COST_030626C1030"
75
- def symbol_old
76
- if !self[:symbol]
77
- p_c_ = put_call == 'PUT' ? 'P' : 'C'
78
- strike_ = strike.to_i == strike ? strike.to_i : strike
79
- sym = "#{stock.ticker}_#{expires_on.strftime("%m%d%y")}#{p_c_}#{strike_}" # XYZ_011819P45
80
- self[:symbol] = sym
81
- save
82
- end
83
- self[:symbol]
84
- end
85
-
86
- ## for schwab
61
+ ## for schwab, eg:
87
62
  ## "COST 260306C01030000"
88
63
  def symbol
89
64
  p_c_ = put_call == 'PUT' ? 'P' : 'C'
90
65
  strike_ = format("%08d", (strike.to_f * 1000).round)
91
66
  sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
92
67
  end
68
+
93
69
  =begin
94
- def symbol_trash ## it persists - which I dont do right now
95
- if !self[:symbol]
96
- p_c_ = put_call == 'PUT' ? 'P' : 'C'
97
- strike_ = format("%08d", (strike.to_f * 1000).round)
98
- sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
99
- self[:symbol] = sym
100
- save
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
101
94
  end
102
- self[:symbol]
103
95
  end
104
- =end
105
96
 
106
97
  # before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
107
98
  def sync
@@ -117,65 +108,7 @@ class Iro::Option
117
108
  self.save! ## 2026-02-19 this must be present.
118
109
  end
119
110
 
120
- def self.max_pain hash
121
- outs = {}
122
-
123
- %w| put call |.each do |contractType|
124
- dates = hash["#{contractType}ExpDateMap"]
125
- dates.each do |_date, strikes| ## _date="2023-02-10:5"
126
- date = _date.split(':')[0].to_date.to_s
127
- outs[date] ||= {
128
- 'all' => {},
129
- 'call' => {},
130
- 'put' => {},
131
- 'summary' => {},
132
- }
133
-
134
- strikes.each do |_strike, _v| ## _strike="18.5"
135
- strike = _strike.to_f
136
-
137
- ## calls
138
- mem_c = 0
139
- strikes.keys.reverse.each do |_key|
140
- if _key == _strike
141
- break
142
- end
143
- key = _key.to_f
144
- tmp = hash["callExpDateMap"][_date][_key][0]['openInterest'] * ( key - strike )
145
- mem_c += tmp
146
- end
147
- outs[date]['call'][_strike] = mem_c
148
-
149
- ## puts
150
- mem_p = 0
151
- strikes.keys.each do |_key|
152
- if _key == _strike
153
- break
154
- end
155
- key = _key.to_f
156
- tmp = hash["putExpDateMap"][_date][_key][0]['openInterest'] * ( strike - key )
157
- mem_p += tmp
158
- end
159
- outs[date]['put'][_strike] = mem_p
160
- outs[date]['all'][_strike] = mem_c + mem_p
161
-
162
- end
163
- end
164
- end
165
-
166
- ## compute summary
167
- outs.each do |date, types|
168
- all = types['all']
169
- outs[date]['summary'] = { 'value' => all.keys[0] }
170
- all.each do |strike, amount|
171
- if amount < all[ outs[date]['summary']['value'] ]
172
- outs[date]['summary']['value'] = strike
173
- end
174
- end
175
- end
176
-
177
- return outs
111
+ def to_s
112
+ "#{symbol} :: #{expires_on.strftime('%Y-%m-%d')} #{put_call} #{strike}"
178
113
  end
179
-
180
-
181
114
  end
@@ -0,0 +1,62 @@
1
+
2
+
3
+ def self.max_pain hash
4
+ outs = {}
5
+
6
+ %w| put call |.each do |contractType|
7
+ dates = hash["#{contractType}ExpDateMap"]
8
+ dates.each do |_date, strikes| ## _date="2023-02-10:5"
9
+ date = _date.split(':')[0].to_date.to_s
10
+ outs[date] ||= {
11
+ 'all' => {},
12
+ 'call' => {},
13
+ 'put' => {},
14
+ 'summary' => {},
15
+ }
16
+
17
+ strikes.each do |_strike, _v| ## _strike="18.5"
18
+ strike = _strike.to_f
19
+
20
+ ## calls
21
+ mem_c = 0
22
+ strikes.keys.reverse.each do |_key|
23
+ if _key == _strike
24
+ break
25
+ end
26
+ key = _key.to_f
27
+ tmp = hash["callExpDateMap"][_date][_key][0]['openInterest'] * ( key - strike )
28
+ mem_c += tmp
29
+ end
30
+ outs[date]['call'][_strike] = mem_c
31
+
32
+ ## puts
33
+ mem_p = 0
34
+ strikes.keys.each do |_key|
35
+ if _key == _strike
36
+ break
37
+ end
38
+ key = _key.to_f
39
+ tmp = hash["putExpDateMap"][_date][_key][0]['openInterest'] * ( strike - key )
40
+ mem_p += tmp
41
+ end
42
+ outs[date]['put'][_strike] = mem_p
43
+ outs[date]['all'][_strike] = mem_c + mem_p
44
+
45
+ end
46
+ end
47
+ end
48
+
49
+ ## compute summary
50
+ outs.each do |date, types|
51
+ all = types['all']
52
+ outs[date]['summary'] = { 'value' => all.keys[0] }
53
+ all.each do |strike, amount|
54
+ if amount < all[ outs[date]['summary']['value'] ]
55
+ outs[date]['summary']['value'] = strike
56
+ end
57
+ end
58
+ end
59
+
60
+ return outs
61
+ end
62
+
@@ -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'
@@ -17,14 +16,31 @@ class Iro::Position
17
16
  STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PREPARE, STATUS_PROPOSED, STATUS_PENDING ]
18
17
  field :status
19
18
  validates :status, presence: true
20
- scope :active, ->{ where( status: 'active' ) }
21
- field :schwab_status
19
+ scope :active, ->{ where( status: 'active' ) }
20
+ scope :proposed, ->{ where( status: 'proposed' ) }
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
22
31
 
23
32
  belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
24
33
  index({ purse_id: 1, ticker: 1 })
25
34
 
26
35
  belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
27
- delegate :ticker, to: :stock
36
+ field :ticker
37
+ def ticker
38
+ if !self[:ticker]
39
+ self[:ticker] = stock.ticker
40
+ self.save
41
+ end
42
+ self[:ticker]
43
+ end
28
44
 
29
45
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
30
46
  delegate :long_or_short, to: :strategy
@@ -77,6 +93,7 @@ class Iro::Position
77
93
  field :end_on
78
94
 
79
95
  field :schwab_order_id, type: :integer
96
+ field :schwab_status
80
97
 
81
98
  def begin_delta
82
99
  strategy.send("begin_delta_#{strategy.kind}", self)
@@ -126,6 +143,23 @@ class Iro::Position
126
143
  print '^'
127
144
  end
128
145
 
146
+
147
+ field :pending_price
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
+
156
+ ## place2 = credit-spread
157
+ def place2_price
158
+ pos = self
159
+ out = pos.inner.begin_price - pos.outer.begin_price
160
+ return out.round(2)
161
+ end
162
+
129
163
  def roll_price
130
164
  pos = self
131
165
  out = pos.autoprev.outer.end_price - pos.autoprev.inner.end_price + pos.inner.begin_price - pos.outer.begin_price
@@ -188,8 +222,6 @@ class Iro::Position
188
222
  def calc_rollp
189
223
  pos = self
190
224
  pos.next_reasons = []
191
- # pos.next_symbol = nil
192
- # pos.next_delta = nil
193
225
 
194
226
  out = strategy.send("calc_rollp_#{strategy.kind}", pos )
195
227
 
@@ -28,10 +28,7 @@ class Iro::Purse
28
28
  field :n_next_positions, type: :integer, default: 5
29
29
 
30
30
  field :available_amount, type: :float
31
- validates :available_amount, presence: true
32
- def available
33
- available_amount
34
- end
31
+ # validates :available_amount, presence: true
35
32
 
36
33
  def to_s
37
34
  slug
@@ -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|
@@ -364,9 +364,6 @@ class Iro::Strategy
364
364
  end
365
365
 
366
366
 
367
- # def slug
368
- # "#{kind} #{stock}"
369
- # end
370
367
  def to_s
371
368
  "#{kind} #{stock} #{descr}"
372
369
  end
@@ -18,7 +18,7 @@ class Tda::Order
18
18
  },
19
19
  })
20
20
  puts! results, 'results'
21
- return results
21
+ return results.deep_symbolize_keys
22
22
  end
23
23
 
24
24
  ## not used - the hash is stored
@@ -33,6 +33,126 @@ class Tda::Order
33
33
  puts! results, 'results'
34
34
  end
35
35
 
36
+ def self.get_orders
37
+ profile = Wco::Profile.pi
38
+ today = Time.now.strftime("%Y-%m-%d")
39
+ results = self.get("/accounts/#{profile.schwab_account_hash}/orders", {
40
+ headers: {
41
+ accept: 'application/json',
42
+ Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
43
+ },
44
+ query: {
45
+ fromEnteredTime: "#{today}T00:00:00Z",
46
+ toEnteredTime: "#{today}T23:59:59Z",
47
+ },
48
+ })
49
+ puts! results, 'get_orders() results'
50
+ puts! results.code, 'results.code'
51
+ return results
52
+ end
53
+
54
+ def self.cancel_order!( id )
55
+ profile = Wco::Profile.pi
56
+ results = self.delete("/accounts/#{profile.schwab_account_hash}/orders/#{id}", {
57
+ headers: {
58
+ accept: 'application/json',
59
+ Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
60
+ },
61
+ })
62
+ puts! results, 'cancel_order!() results'
63
+ puts! results.code, 'results.code'
64
+ # if !results.code == '200'
65
+ # throw 'could not cancel order'
66
+ # end
67
+ end
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?!
102
+ def self.credit_spread_q pos
103
+ query = {
104
+ orderType: pos.place2_price > 0 ? "NET_CREDIT" : "NET_DEBIT",
105
+ session: "NORMAL",
106
+ duration: "DAY",
107
+ price: pos.pending_price,
108
+ orderStrategyType: "SINGLE",
109
+ orderLegCollection: [
110
+ ## open
111
+ {
112
+ instruction: "BUY_TO_OPEN",
113
+ quantity: pos.q,
114
+ instrument: {
115
+ symbol: pos.outer.symbol,
116
+ assetType: "OPTION",
117
+ },
118
+ },
119
+ {
120
+ instruction: "SELL_TO_OPEN",
121
+ quantity: pos.q,
122
+ instrument: {
123
+ symbol: pos.inner.symbol,
124
+ assetType: "OPTION",
125
+ },
126
+ },
127
+ ],
128
+ }
129
+ return query
130
+ end
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
+
36
156
  ## obsolete, I don't do covered calls anymore?
37
157
  def self.roll_covered_call_q pos
38
158
  roll_price = pos.inner.begin_price - pos.autoprev.inner.end_price
@@ -117,20 +237,4 @@ class Tda::Order
117
237
  return query
118
238
  end
119
239
 
120
- def self.place_order query
121
- puts! query, '#place_order'
122
-
123
- profile = Wco::Profile.pi
124
- results = self.post("/accounts/#{profile.schwab_account_hash}/orders", {
125
- headers: {
126
- 'content-type' => 'application/json',
127
- accept: 'application/json',
128
- Authorization: "Bearer #{profile[:schwab_exec_access_token]}",
129
- },
130
- body: query.to_json,
131
- })
132
- order_id = results.headers['location'].split('/').last
133
- return order_id
134
- end
135
-
136
240
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wco_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.265
4
+ version: 3.1.0.267
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Pudeyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-07 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ahoy_matey
@@ -477,6 +477,7 @@ files:
477
477
  - app/models/iro/datapoint.rb
478
478
  - app/models/iro/date.rb
479
479
  - app/models/iro/option.rb
480
+ - app/models/iro/option.rb-bk
480
481
  - app/models/iro/option_black_scholes.rb-bk
481
482
  - app/models/iro/position.rb
482
483
  - app/models/iro/priceitem.rb