wco_models 3.1.0.187 → 3.1.0.189

Sign up to get free protection for your applications and to get access to all the features.
@@ -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