wco_models 3.1.0.184 → 3.1.0.188

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.
@@ -0,0 +1,304 @@
1
+
2
+ class Iro::Position
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Paranoia
6
+ store_in collection: 'iro_positions'
7
+
8
+ field :prev_gain_loss_amount, type: :float
9
+ attr_accessor :next_gain_loss_amount
10
+ def prev_gain_loss_amount
11
+ out = autoprev.outer.end_price - autoprev.inner.end_price
12
+ out += inner.begin_price - outer.begin_price
13
+ end
14
+
15
+
16
+ STATUS_ACTIVE = 'active'
17
+ STATUS_CLOSED = 'closed'
18
+ STATUS_PROPOSED = 'proposed'
19
+ ## one more, 'selected' after proposed?
20
+ STATUS_PENDING = 'pending' ## 'working'
21
+ STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PROPOSED, STATUS_PENDING ]
22
+ field :status
23
+ validates :status, presence: true
24
+ scope :active, ->{ where( status: 'active' ) }
25
+
26
+ belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
27
+ index({ purse_id: 1, ticker: 1 })
28
+
29
+ belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
30
+ delegate :ticker, to: :stock
31
+
32
+ belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
33
+ delegate :put_call, to: :strategy
34
+ delegate :long_or_short, to: :strategy
35
+ delegate :credit_or_debit, to: :strategy
36
+
37
+ belongs_to :next_strategy, class_name: 'Iro::Strategy', inverse_of: :next_position, optional: true
38
+
39
+
40
+ belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxts, optional: true
41
+ belongs_to :autoprev, class_name: 'Iro::Position', inverse_of: :autonxt, optional: true
42
+ ## there are many of these, for viewing on the 'roll' view
43
+ has_many :nxts, class_name: 'Iro::Position', inverse_of: :prev
44
+ has_one :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev
45
+
46
+ ## Options
47
+
48
+ belongs_to :inner, class_name: 'Iro::Option', inverse_of: :inner
49
+ validates_associated :inner
50
+
51
+ belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer
52
+ validates_associated :outer
53
+
54
+ accepts_nested_attributes_for :inner, :outer
55
+
56
+ field :outer_strike, type: :float
57
+ # validates :outer_strike, presence: true
58
+
59
+ field :inner_strike, type: :float
60
+ # validates :inner_strike, presence: true
61
+
62
+ field :expires_on
63
+ validates :expires_on, presence: true
64
+
65
+ field :quantity, type: :integer
66
+ validates :quantity, presence: true
67
+ def q; quantity; end
68
+
69
+ field :begin_on
70
+
71
+ field :end_on
72
+
73
+ def begin_delta
74
+ strategy.send("begin_delta_#{strategy.kind}", self)
75
+ end
76
+ def end_delta
77
+ strategy.send("end_delta_#{strategy.kind}", self)
78
+ end
79
+
80
+ def breakeven
81
+ strategy.send("breakeven_#{strategy.kind}", self)
82
+ end
83
+
84
+ def current_underlying_strike
85
+ Iro::Stock.find_by( ticker: ticker ).last
86
+ end
87
+
88
+ def refresh
89
+ out = Tda::Option.get_quote({
90
+ contractType: 'CALL',
91
+ strike: strike,
92
+ expirationDate: expires_on,
93
+ ticker: ticker,
94
+ })
95
+ update({
96
+ end_delta: out[:delta],
97
+ end_price: out[:last],
98
+ })
99
+ print '^'
100
+ end
101
+
102
+ def net_percent
103
+ net_amount / max_gain
104
+ end
105
+ def net_amount # each
106
+ strategy.send("net_amount_#{strategy.kind}", self)
107
+ end
108
+ def max_gain # each
109
+ strategy.send("max_gain_#{strategy.kind}", self)
110
+ end
111
+ def max_loss # each
112
+ strategy.send("max_loss_#{strategy.kind}", self)
113
+ end
114
+
115
+
116
+ def sync
117
+ inner.sync
118
+ outer.sync
119
+ end
120
+
121
+
122
+ ##
123
+ ## decisions
124
+ ##
125
+
126
+ field :next_reasons, type: :array, default: []
127
+ field :rollp, type: :float
128
+
129
+ ## should_roll?
130
+ def calc_rollp
131
+ self.next_reasons = []
132
+ # self.next_symbol = nil
133
+ # self.next_delta = nil
134
+
135
+ out = strategy.send( "calc_rollp_#{strategy.kind}", self )
136
+
137
+ self.rollp = out[0]
138
+ self.next_reasons.push out[1]
139
+ save
140
+ end
141
+
142
+ def calc_nxt
143
+ pos = self
144
+
145
+ ## 7 days ahead - not configurable so far
146
+ outs = Tda::Option.get_quotes({
147
+ contractType: pos.put_call,
148
+ expirationDate: next_expires_on,
149
+ ticker: ticker,
150
+ })
151
+ outs_bk = outs.dup
152
+
153
+ outs = outs.select do |out|
154
+ out[:bidSize] + out[:askSize] > 0
155
+ end
156
+
157
+ if 'CALL' == pos.put_call
158
+ ;
159
+ elsif 'PUT' == pos.put_call
160
+ outs = outs.reverse
161
+ end
162
+
163
+ ## next_inner_strike
164
+ outs = outs.select do |out|
165
+ if Iro::Strategy::CREDIT == pos.credit_or_debit
166
+ if Iro::Strategy::SHORT == pos.long_or_short
167
+ ## short credit call
168
+ out[:strikePrice] >= strategy.next_inner_strike
169
+ elsif Iro::Strategy::LONG == pos.long_or_short
170
+ ## long credit put
171
+ out[:strikePrice] <= strategy.next_inner_strike
172
+ end
173
+ else
174
+ raise 'zz3 - @TODO: implement, debit spreads'
175
+ end
176
+ end
177
+ puts! outs[0][:strikePrice], 'after calc next_inner_strike'
178
+ puts! outs, 'outs'
179
+
180
+ ## next_buffer_above_water
181
+ outs = outs.select do |out|
182
+ if Iro::Strategy::SHORT == pos.long_or_short
183
+ out[:strikePrice] > strategy.next_buffer_above_water + strategy.stock.last
184
+ elsif Iro::Strategy::LONG == pos.long_or_short
185
+ out[:strikePrice] < strategy.stock.last - strategy.next_buffer_above_water
186
+ else
187
+ raise 'zz4 - this cannot happen'
188
+ end
189
+ end
190
+ puts! outs[0][:strikePrice], 'after calc next_buffer_above_water'
191
+ puts! outs, 'outs'
192
+
193
+ ## next_inner_delta
194
+ outs = outs.select do |out|
195
+ if 'CALL' == pos.put_call
196
+ out_delta = out[:delta] rescue 1
197
+ out_delta <= strategy.next_inner_delta
198
+ elsif 'PUT' == pos.put_call
199
+ out_delta = out[:delta] rescue 0
200
+ out_delta <= strategy.next_inner_delta
201
+ else
202
+ raise 'zz5 - this cannot happen'
203
+ end
204
+ end
205
+ puts! outs[0][:strikePrice], 'after calc next_inner_delta'
206
+ puts! outs, 'outs'
207
+
208
+ inner = outs[0]
209
+ outs = outs.select do |out|
210
+ if 'CALL' == pos.put_call
211
+ out[:strikePrice] >= inner[:strikePrice].to_f + strategy.next_spread_amount
212
+ elsif 'PUT' == pos.put_call
213
+ out[:strikePrice] <= inner[:strikePrice].to_f - strategy.next_spread_amount
214
+ end
215
+ end
216
+ outer = outs[0]
217
+
218
+ if inner && outer
219
+ o_attrs = {
220
+ expires_on: next_expires_on,
221
+ put_call: pos.put_call,
222
+ stock_id: pos.stock_id,
223
+ }
224
+ inner_ = Iro::Option.new(o_attrs.merge({
225
+ strike: inner[:strikePrice],
226
+ begin_price: ( inner[:bid] + inner[:ask] )/2,
227
+ begin_delta: inner[:delta],
228
+ end_price: ( inner[:bid] + inner[:ask] )/2,
229
+ end_delta: inner[:delta],
230
+ }))
231
+ outer_ = Iro::Option.new(o_attrs.merge({
232
+ strike: outer[:strikePrice],
233
+ begin_price: ( outer[:bid] + outer[:ask] )/2,
234
+ begin_delta: outer[:delta],
235
+ end_price: ( outer[:bid] + outer[:ask] )/2,
236
+ end_delta: outer[:delta],
237
+ }))
238
+ pos.autonxt ||= Iro::Position.new
239
+ pos.autonxt.update({
240
+ prev_gain_loss_amount: 'a',
241
+ status: 'proposed',
242
+ stock: strategy.stock,
243
+ inner: inner_,
244
+ outer: outer_,
245
+ inner_strike: inner_.strike,
246
+ outer_strike: outer_.strike,
247
+ begin_on: Time.now.to_date,
248
+ expires_on: next_expires_on,
249
+ purse: purse,
250
+ strategy: strategy,
251
+ quantity: 1,
252
+ autoprev: pos,
253
+ })
254
+
255
+ pos.autonxt.sync
256
+ pos.autonxt.save!
257
+ pos.save
258
+ return pos
259
+
260
+ else
261
+ throw 'zmq - should not happen'
262
+ end
263
+ end
264
+
265
+
266
+
267
+ ## ok
268
+ def next_expires_on
269
+ out = expires_on.to_datetime.next_occurring(:monday).next_occurring(:friday)
270
+ if !out.workday?
271
+ out = Time.previous_business_day(out)
272
+ end
273
+ return out
274
+ end
275
+
276
+ ## ok
277
+ def self.long
278
+ where( long_or_short: Iro::Strategy::LONG )
279
+ end
280
+
281
+ ## ok
282
+ def self.short
283
+ where( long_or_short: Iro::Strategy::SHORT )
284
+ end
285
+
286
+ def to_s
287
+ out = "#{stock} (#{q}) #{expires_on.to_datetime.strftime('%b %d')} #{strategy.long_or_short} ["
288
+ if Iro::Strategy::LONG == long_or_short
289
+ if outer.strike
290
+ out = out + "$#{outer.strike}->"
291
+ end
292
+ out = out + "$#{inner.strike}"
293
+ else
294
+ out = out + "$#{inner.strike}"
295
+ if outer.strike
296
+ out = out + "<-$#{outer.strike}"
297
+ end
298
+ end
299
+ out += "] "
300
+ return out
301
+ end
302
+ end
303
+
304
+
@@ -0,0 +1,93 @@
1
+
2
+ ##
3
+ ## Specifically Option or Stock priceitem?
4
+ ## Priceitems are intra-day! See Datapoint for daily data
5
+ ##
6
+ class Iro::Priceitem
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+ store_in collection: 'iro_price_items'
10
+
11
+ ## PUT, CALL, STOCK
12
+ field :putCall, type: String ## kind
13
+ field :symbol, type: String
14
+ field :description, type: String
15
+ field :ticker, type: String
16
+ # belongs_to :stock, inverse_of: :priceitems
17
+
18
+ field :bid, type: Float
19
+ field :bidSize, type: Integer
20
+ field :ask, type: Float
21
+ field :askSize, type: Integer
22
+ field :last, type: Float
23
+
24
+ field :openPrice, type: Float
25
+ field :lowPrice, type: Float
26
+ field :highPrice, type: Float
27
+ field :closePrice, type: Float
28
+
29
+ field :quote_at, type: DateTime
30
+ field :quoteTimeInLong, type: Integer
31
+ field :timestamp, type: Integer
32
+ field :totalVolume, type: Integer
33
+ field :mark, type: Float
34
+ field :exchangeName, type: String
35
+ field :volatility, type: Float
36
+
37
+ field :expirationDate, type: :date
38
+ field :delta, type: Float
39
+ field :gamma, type: Float
40
+ field :theta, type: Float
41
+ field :openInterest, type: Integer
42
+ field :strikePrice, type: Float
43
+
44
+ def self.my_find props={}
45
+ lookup = { '$lookup': {
46
+ 'from': 'iro_price_items',
47
+ 'localField': 'date',
48
+ 'foreignField': 'date',
49
+ 'pipeline': [
50
+ { '$sort': { 'value': -1 } },
51
+ ],
52
+ 'as': 'dates',
53
+ } }
54
+ lookup_merge = { '$replaceRoot': {
55
+ 'newRoot': { '$mergeObjects': [
56
+ { '$arrayElemAt': [ "$dates", 0 ] }, "$$ROOT"
57
+ ] }
58
+ } }
59
+
60
+
61
+ match = { '$match': {
62
+ 'date': {
63
+ '$gte': props[:begin_on],
64
+ '$lte': props[:end_on],
65
+ }
66
+ } }
67
+
68
+ group = { '$group': {
69
+ '_id': "$date",
70
+ 'my_doc': { '$first': "$$ROOT" }
71
+ } }
72
+
73
+ outs = Iro::Date.collection.aggregate([
74
+ match,
75
+
76
+ lookup,
77
+ lookup_merge,
78
+
79
+ group,
80
+ { '$replaceRoot': { 'newRoot': "$my_doc" } },
81
+ # { '$replaceRoot': { 'newRoot': "$my_doc" } },
82
+
83
+
84
+ { '$project': { '_id': 0, 'date': 1, 'value': 1 } },
85
+ { '$sort': { 'date': 1 } },
86
+ ])
87
+
88
+ puts! 'result'
89
+ pp outs.to_a
90
+ # puts! outs.to_a, 'result'
91
+ end
92
+
93
+ end
@@ -0,0 +1,71 @@
1
+
2
+ require 'distribution'
3
+ N = Distribution::Normal
4
+
5
+ class Iro::Purse
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ include Mongoid::Paranoia
9
+ store_in collection: 'iro_purses'
10
+
11
+ field :slug
12
+ validates :slug, presence: true, uniqueness: true
13
+ index({ slug: -1 }, { unique: true })
14
+
15
+ has_many :positions, class_name: 'Iro::Position', inverse_of: :purse
16
+
17
+ # has_and_belongs_to_many :strategies, class_name: 'Iro::Strategy', inverse_of: :purses
18
+ # belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
19
+
20
+ field :unit, type: :integer, default: 10
21
+ ## with unit 10, .001
22
+ ## with unit 100, .0001
23
+ field :summary_unit, type: :float, default: 0.001
24
+
25
+ ## for rolling only:
26
+ field :height, type: :integer, default: 100
27
+
28
+ field :mark_every_n_usd, type: :float, default: 1
29
+ field :n_next_positions, type: :integer, default: 5
30
+
31
+ field :available_amount, type: :float
32
+ def available
33
+ available_amount
34
+ end
35
+
36
+ def balance
37
+ 0.01
38
+ end
39
+
40
+ def delta_wt_avg( begin_end, long_short, inner_outer )
41
+ max_loss_total = 0
42
+
43
+ out = positions.send( long_short ).map do |pos|
44
+ max_loss_total += pos.max_loss * pos.q
45
+ pos.max_loss * pos.q * pos.send( inner_outer ).send( "#{begin_end}_delta" )
46
+ end
47
+ # puts! out, 'delta_wt_avg 1'
48
+ out = out.reduce( &:+ ) / max_loss_total rescue 0
49
+ # puts! out, 'delta_wt_avg 2'
50
+ return out
51
+ end
52
+ ## delta to plot percentage
53
+ ## convert to normal between 0 and 3 std
54
+ def delta_to_plot_p( *args )
55
+ x = delta_wt_avg( *args ).abs
56
+ if x < 0.5
57
+ y = 1
58
+ else
59
+ y = 2 - 1/( 1.5 - x )
60
+ end
61
+ y_ = "#{ (y*100) .to_i}%"
62
+ return y_
63
+ end
64
+
65
+ def to_s
66
+ slug
67
+ end
68
+ def self.list
69
+ [[nil,nil]] + all.map { |p| [p, p.id] }
70
+ end
71
+ end
@@ -0,0 +1,141 @@
1
+ include Math
2
+ require 'business_time'
3
+
4
+ ##
5
+ ## https://www.macrotrends.net/stocks/charts/META/meta-platforms/stock-price-history
6
+ ##
7
+ class Iro::Stock
8
+ include Mongoid::Document
9
+ include Mongoid::Timestamps
10
+ include Mongoid::Paranoia
11
+ store_in collection: 'iro_stocks'
12
+
13
+ STATUS_ACTIVE = 'active'
14
+ STATUS_INACTIVE = 'inactive'
15
+ STATUSES = [ nil, 'active', 'inactive' ]
16
+ def self.active
17
+ where( status: STATUS_ACTIVE )
18
+ end
19
+ field :status, default: STATUS_ACTIVE
20
+
21
+ field :ticker
22
+ validates :ticker, uniqueness: true, presence: true
23
+ index({ ticker: -1 }, { unique: true })
24
+ def symbol; ticker; end
25
+ def symbol= a; ticker = a; end
26
+
27
+ field :last, type: :float
28
+ field :options_price_increment, type: :float
29
+
30
+ field :stdev, type: :float
31
+
32
+ has_many :positions, class_name: 'Iro::Position', inverse_of: :stock
33
+ has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :stock
34
+ # has_many :purses, class_name: 'Iro::Purse', inverse_of: :stock
35
+ has_many :options, class_name: 'Iro::Option', inverse_of: :stock
36
+ has_many :priceitems, inverse_of: :stock
37
+
38
+ belongs_to :user
39
+ LONG_ONLY = 'long-only'
40
+ LONG_OR_SHORT = 'long-or-short'
41
+ SHORT_ONLY = 'short-only'
42
+ field :sentiment, default: LONG_OR_SHORT
43
+ field :sentiment_num, default: 0 # 1 is very long, -1 is very short
44
+
45
+ default_scope { order_by({ ticker: :asc }) }
46
+
47
+ ## my_find
48
+ def self.f ticker
49
+ self.find_by ticker: ticker
50
+ end
51
+
52
+ def to_s
53
+ ticker
54
+ end
55
+ def self.list
56
+ [[nil,nil]] + all.map { |sss| [ sss.ticker, sss.id ] }
57
+ end
58
+ def self.tickers_list
59
+ [[nil,nil]] + all.map { |sss| [ sss.ticker, sss.ticker ] }
60
+ end
61
+
62
+ =begin
63
+ stock = Iro::Stock.find_by( ticker: 'NVDA' )
64
+
65
+ duration = 1.month
66
+ stock.volatility_from_mo
67
+
68
+ duration = 1.year
69
+ stock.volatility_from_yr
70
+
71
+ =end
72
+ field :volatility, type: :float
73
+ def volatility duration: 1.year, recompute: false
74
+ if self[:volatility]
75
+ if !recompute
76
+ return self[:volatility]
77
+ end
78
+ end
79
+
80
+ stock = self
81
+ begin_on = Time.now - duration - 1.day
82
+ points = Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
83
+ :date.gte => begin_on,
84
+ ).order_by( date: :asc )
85
+
86
+ puts! [points.first.date, points.last.date], "from,to"
87
+
88
+ points_p = []
89
+ points.each_with_index do |p, idx|
90
+ next if idx == 0
91
+ prev = points[idx-1]
92
+
93
+ out = p.value / prev.value - 1
94
+ points_p.push out
95
+ end
96
+ n = points_p.length
97
+
98
+ avg = points_p.reduce(&:+) / n
99
+ _sum_of_sq = []
100
+ points_p.map do |p|
101
+ _sum_of_sq.push( ( p - avg )*( p - avg ) )
102
+ end
103
+ sum_of_sq = _sum_of_sq.reduce( &:+ ) / n
104
+
105
+ # n_periods = begin_on.to_date.business_days_until( Date.today )
106
+ out = Math.sqrt( sum_of_sq )*sqrt( n )
107
+ adjustment = 2.0
108
+ out = out * adjustment
109
+ puts! out, 'volatility (adjusted)'
110
+ self.update volatility: out
111
+ return out
112
+ end
113
+
114
+ def volatility_from_mo
115
+ volatility( duration: 1.month )
116
+ end
117
+ def volatility_from_yr
118
+ volatility( duration: 1.year )
119
+ end
120
+ def stdev recompute: nil
121
+ if !self[:stdev] || recompute
122
+ out = volatility_from_yr
123
+ self[:stdev] = out
124
+ save( validate: false )
125
+ return out
126
+ else
127
+ self[:stdev]
128
+ end
129
+ end
130
+
131
+ ## stdev
132
+ ## From: https://stackoverflow.com/questions/19484891/how-do-i-find-the-standard-deviation-in-ruby
133
+ # contents = [1,2,3,4,5,6,7,8,9]
134
+ # n = contents.size # => 9
135
+ # contents.map!(&:to_f) # => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
136
+ # mean = contents.reduce(&:+)/n # => 5.0
137
+ # sum_sqr = contents.map {|x| x * x}.reduce(&:+) # => 285.0
138
+ # std_dev = Math.sqrt((sum_sqr - n * mean * mean)/(n-1)) # => 2.7386127875258306
139
+
140
+
141
+ end