wco_models 3.1.0.240 → 3.1.0.241

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/wco/chip.scss +2 -0
  3. data/app/assets/stylesheets/wco/main.scss +11 -0
  4. data/app/controllers/wco/leads_controller.rb +37 -0
  5. data/app/controllers/wco/profiles_controller.rb +6 -0
  6. data/app/helpers/wco/application_helper.rb +1 -1
  7. data/app/mailers/iro/alert_mailer.rb +12 -0
  8. data/app/models/ability.rb +1 -1
  9. data/app/models/iro/datapoint.rb +17 -15
  10. data/app/models/iro/option.rb +36 -9
  11. data/app/models/iro/position.rb +117 -51
  12. data/app/models/iro/purse.rb +4 -3
  13. data/app/models/iro/stock.rb +31 -0
  14. data/app/models/iro/strategy.rb +94 -47
  15. data/app/models/tda/option.rb +100 -28
  16. data/app/models/tda/order.rb +135 -0
  17. data/app/models/tda/stock.rb +1 -2
  18. data/app/models/wco/office_action_template.rb +2 -1
  19. data/app/models/wco/profile.rb +10 -0
  20. data/app/models/wco/tag.rb +2 -1
  21. data/app/models/wco_email/email_filter.rb +15 -10
  22. data/app/models/wco_email/email_filter_condition.rb +5 -5
  23. data/app/models/wco_email/email_template.rb +2 -0
  24. data/app/models/wco_hosting/appliance_tmpl.rb +8 -3
  25. data/app/views/wco/leads/_header.haml +2 -1
  26. data/app/views/wco/leads/_table.haml +5 -3
  27. data/app/views/wco/leads/new.haml +0 -3
  28. data/app/views/wco/leads/new_import.haml +20 -0
  29. data/app/views/wco/profiles/_form.haml +12 -0
  30. data/app/views/wco/profiles/_header.haml +1 -0
  31. data/app/views/wco/reports/show.haml +15 -13
  32. data/app/views/wco/tags/show.haml +7 -1
  33. data/config/routes.rb +2 -1
  34. data/lib/wco_models.rb +1 -1
  35. metadata +5 -3
  36. data/app/views/wco/leads/_form_import.haml +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afeb2a36830a27cb6b8fd3607f24f229123a1d2c46136aaad50b56cea0f3d300
4
- data.tar.gz: bb8705a59baf74db8e59ad581ac81696c9f8ed3a3874e9fb82d8d63c84c1da37
3
+ metadata.gz: 992dd5056deab4631dd31cda86e3acd76e266b6d4b4e35e1649151f7fc612e81
4
+ data.tar.gz: b966eb486db7625e6438587694021ad0f8bf72e959fb77e4daaca336ad9df877
5
5
  SHA512:
6
- metadata.gz: 1ef5758d81ae26e830b1c4b571990a089a1b04b303fca235c49a76669ee3688f444bdf5f19841e36bd31bdd4d4399df6b6558e515164adb890147f40acbc1b5a
7
- data.tar.gz: 11d69fa6b9502e7556bfca85fb1c4a49bc8cc0a836963525dbfadbc9e45877e40f3350f3f4c9337aaddef92083030cbcc2a376af4fabf66080a79a9244d5d939
6
+ metadata.gz: 2b84c1ae7b5dd1d0ef0e15557fb50a46b08b3d6bfbb17991e35fe1e429f7069c8b9b6b236df86ca56897d2d4ad7b7a1a3cc72246f3f008548b5d7e45300e6d43
7
+ data.tar.gz: ed56f590661d865a96840b1736f9b3e5ae451ec6f3b7a501a1a804da148ff12fdaecd54301d609b2b776832b975ad36fbf65bfbf612b7bec8a4395e60a72a4c2
@@ -4,6 +4,8 @@
4
4
 
5
5
  .chip,
6
6
  .Chip {
7
+ white-space: nowrap;
8
+
7
9
  background: var(--wco-color-2);
8
10
  border-radius: 1em 0 0 1em;
9
11
 
@@ -86,7 +86,10 @@ textarea.monospace {
86
86
 
87
87
  .descr {
88
88
  border: 1px solid red;
89
+ border-radius: 0.5em;
90
+
89
91
  padding: 0.5em;
92
+ margin-bottom: 0.5em;
90
93
  }
91
94
 
92
95
  /* E */
@@ -167,6 +170,14 @@ textarea.monospace {
167
170
  display: none !important;
168
171
  }
169
172
 
173
+ /* I */
174
+
175
+ input[type=file] {
176
+ margin: 0.5em 0;
177
+ border: 2px solid white;
178
+ border-radius: 0.5em;
179
+ padding: 0.5em;
180
+ }
170
181
 
171
182
  /* M */
172
183
 
@@ -26,6 +26,38 @@ class Wco::LeadsController < Wco::ApplicationController
26
26
  redirect_to action: :index
27
27
  end
28
28
 
29
+ def create_import
30
+ authorize! :create, Wco::Lead
31
+ file = params[:file]
32
+ selected_tag_ids = params[:tags] || []
33
+
34
+ if file.nil?
35
+ redirect_back fallback_location: root_path, alert: "No file selected" and return
36
+ end
37
+
38
+ CSV.foreach(file.path, headers: true) do |row|
39
+ lead_attrs = {
40
+ email: row['email'] || row['Email'],
41
+ name: row['name'] || row['Name'],
42
+ phone: row['phone'] || row['Phone'],
43
+ address: row['address'] || row['Address']
44
+ }.compact ## skip missing columns
45
+
46
+ lead = Wco::Lead.find_by( email: lead_attrs[:email] ) rescue nil
47
+ lead ||= Wco::Lead.create!(lead_attrs)
48
+
49
+ # Assign selected tags
50
+ selected_tag_ids.each do |tag_id|
51
+ lead.tags << Wco::Tag.find(tag_id)
52
+ end
53
+ # lead.save!
54
+ end
55
+
56
+ redirect_to wco.leads_path, notice: "Leads imported successfully"
57
+ rescue => e
58
+ redirect_back fallback_location: root_path, alert: "Error: #{e.message}"
59
+ end
60
+
29
61
  def edit
30
62
  authorize! :edit, Wco::Lead
31
63
  @lead = Wco::Lead.find params[:id]
@@ -48,6 +80,7 @@ class Wco::LeadsController < Wco::ApplicationController
48
80
  end
49
81
  end
50
82
 
83
+ @leads = @leads.includes( :tags )
51
84
  @leads = @leads.page( params[:leads_page ] ).per( current_profile.per_page )
52
85
  end
53
86
 
@@ -56,6 +89,10 @@ class Wco::LeadsController < Wco::ApplicationController
56
89
  @lead = Wco::Lead.new
57
90
  end
58
91
 
92
+ def new_import
93
+ authorize! :new, Wco::Lead
94
+ end
95
+
59
96
  def show
60
97
  @lead = Wco::Lead.where({ id: params[:id] }).first
61
98
  @lead ||= Wco::Lead.where({ email: params[:id] }).first
@@ -24,6 +24,12 @@ class Wco::ProfilesController < Wco::ApplicationController
24
24
  if params[:q]
25
25
  q = URI.decode(params[:q])
26
26
  @profiles = @profiles.where({ email: /#{q}/i })
27
+
28
+ if params[:q] == 'pi'
29
+ profile = Wco::Profile.find_by email: 'piousbox@gmail.com'
30
+ redirect_to action: 'edit', id: profile.id.to_s
31
+ return
32
+ end
27
33
  end
28
34
  end
29
35
 
@@ -42,7 +42,7 @@ module Wco::ApplicationHelper
42
42
  def pp_money a; pp_amount a; end
43
43
  def pp_currency a; pp_amount a; end
44
44
  def pp_percent a, config = { precision: 2}
45
- "#{(a*100).round( config[:precision] )}%" rescue '@TODO'
45
+ "#{(a*100).round( config[:precision] )}%" rescue 'nil@#pp_percent'
46
46
  end
47
47
 
48
48
  end
@@ -0,0 +1,12 @@
1
+
2
+ class Iro::AlertMailer < ActionMailer::Base
3
+ default from: 'no-reply@wasya.co'
4
+ layout 'mailer'
5
+
6
+ def stock_alert id
7
+ @alert = Iro::Alert.find id
8
+ mail( to: 'victor@piousbox.com',
9
+ subject: "#{Time.now.to_date} Iro::AlertMailer#stock_alert" )
10
+ end
11
+
12
+ end
@@ -10,7 +10,7 @@ class Ability
10
10
 
11
11
  if user
12
12
 
13
- if [ 'piousbox@gmail.com', 'victor@piousbox.com', 'test-1@wasya.co', 'victor@wasya.co' ].include? user.email
13
+ if [ 'piousbox@gmail.com', 'victor@piousbox.com', 'victor@wasya.co' ].include? user.email
14
14
  can :manage, :all
15
15
  end
16
16
 
@@ -1,3 +1,4 @@
1
+ require 'csv'
1
2
 
2
3
  ##
3
4
  ## Datapoints are at most daily!
@@ -42,20 +43,21 @@ class Iro::Datapoint
42
43
 
43
44
  field :date, type: Date
44
45
  index({ kind: -1, date: -1 })
45
- validates :date, uniqueness: { scope: [ :symbol ] }
46
+ validates :date, uniqueness: { scope: [ :kind, :symbol ] }
46
47
 
48
+ ## @obsolete, @deprecated, use :date instead?
47
49
  field :quote_at, type: DateTime
48
50
  index({ kind: -1, quote_at: -1 })
49
- validates :quote_at, uniqueness: { scope: [ :kind, :symbol ] } ## scope-by-kind is unnecessary here? _vp_ 2024-08-08
50
-
51
- field :open, type: Float
52
- field :high, type: Float
53
- field :low, type: Float
54
- def close; value; end
55
- def close= a; value= a; end
51
+ ## I don't understand why this was forced to be unique... I want the date to be unique.
52
+ # validates :quote_at, uniqueness: { scope: [ :kind, :symbol ] } ## scope-by-kind is unnecessary here? _vp_ 2024-08-08
56
53
 
54
+ field :open, type: Float
55
+ field :high, type: Float
56
+ field :low, type: Float
57
57
  field :value, type: Float
58
58
  validates :value, presence: true
59
+ def close; value; end
60
+ def close= a; value= a; end
59
61
 
60
62
 
61
63
  field :volume, type: Integer
@@ -165,15 +167,15 @@ class Iro::Datapoint
165
167
  flag = create({
166
168
  kind: KIND_STOCK,
167
169
  symbol: symbol,
168
- date: row['Date'],
169
- quote_at: row['Date'],
170
+ date: row['date'],
171
+ quote_at: row['date'],
170
172
 
171
- volume: row['Volume'],
173
+ volume: row['volume'],
172
174
 
173
- open: row['Open'],
174
- high: row['High'],
175
- low: row['Low'],
176
- value: row['Close'],
175
+ open: row['open'],
176
+ high: row['high'],
177
+ low: row['low'],
178
+ value: row['close'],
177
179
  })
178
180
  if flag.persisted?
179
181
  print '^'
@@ -16,7 +16,8 @@ class Iro::Option
16
16
  CALL = 'CALL'
17
17
  PUT = 'PUT'
18
18
 
19
- field :symbol
19
+ ## for now, recompute every time
20
+ # field :symbol
20
21
  ## each option can be a leg in a position, no uniqueness
21
22
  # validates :symbol, uniqueness: true, presence: true
22
23
 
@@ -28,6 +29,11 @@ class Iro::Option
28
29
  field :strike, type: :float
29
30
  validates :strike, presence: true
30
31
 
32
+ def to_s
33
+ "#{symbol} :: #{expires_on.strftime('%Y-%m-%d')} #{put_call} #{strike}"
34
+ end
35
+
36
+
31
37
  field :expires_on, type: :date
32
38
  validates :expires_on, presence: true
33
39
  def self.expirations_list full: false, n: 5
@@ -59,13 +65,14 @@ class Iro::Option
59
65
  field :end_delta, type: :float
60
66
 
61
67
 
62
- has_one :outer, class_name: 'Iro::Position', inverse_of: :outer
63
- has_one :inner, class_name: 'Iro::Position', inverse_of: :inner
68
+ has_one :pos_of_outer, class_name: 'Iro::Position', inverse_of: :outer
69
+ has_one :pos_of_inner, class_name: 'Iro::Position', inverse_of: :inner
64
70
 
65
71
  field :last, type: :float
66
72
 
67
73
  ## for TDA
68
- def symbol
74
+ ## "COST_030626C1030"
75
+ def symbol_old
69
76
  if !self[:symbol]
70
77
  p_c_ = put_call == 'PUT' ? 'P' : 'C'
71
78
  strike_ = strike.to_i == strike ? strike.to_i : strike
@@ -76,18 +83,38 @@ class Iro::Option
76
83
  self[:symbol]
77
84
  end
78
85
 
79
- before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
86
+ ## for schwab
87
+ ## "COST 260306C01030000"
88
+ def symbol
89
+ p_c_ = put_call == 'PUT' ? 'P' : 'C'
90
+ strike_ = format("%08d", (strike.to_f * 1000).round)
91
+ sym = "#{stock.ticker.ljust(6)}#{expires_on.strftime("%y%m%d")}#{p_c_}#{strike_}"
92
+ end
93
+ =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
101
+ end
102
+ self[:symbol]
103
+ end
104
+ =end
105
+
106
+ # before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
80
107
  def sync
81
108
  out = Tda::Option.get_quote({
82
109
  contractType: put_call,
83
110
  strike: strike,
84
- expirationDate: expires_on,
111
+ expirationDate: expires_on.strftime('%Y-%m-%d'),
85
112
  ticker: ticker,
86
113
  })
87
- puts! out, 'option sync'
114
+ puts! out, "option sync of `#{self.to_s}`"
88
115
  self.end_price = ( out.bid + out.ask ) / 2 rescue 0
89
- self.end_delta = out.delta if out.delta
90
- # self.save
116
+ self.end_delta = out.delta ? out.delta : 0.0
117
+ self.save! ## 2026-02-19 this must be present.
91
118
  end
92
119
 
93
120
  def self.max_pain hash
@@ -5,64 +5,71 @@ class Iro::Position
5
5
  include Mongoid::Paranoia
6
6
  store_in collection: 'iro_positions'
7
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
8
+ ## @trash, use next_gain_loss_amount instead
9
+ # field :prev_gain_loss_amount, type: :float
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
+ field :next_gain_loss_amount, type: :float
14
15
 
15
16
 
16
17
  STATUS_ACTIVE = 'active'
17
18
  STATUS_CLOSED = 'closed'
19
+ STATUS_PREPARE = 'prepare'
18
20
  STATUS_PROPOSED = 'proposed'
19
21
  ## one more, 'selected' after proposed?
20
22
  STATUS_PENDING = 'pending' ## 'working'
21
- STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PROPOSED, STATUS_PENDING ]
23
+ STATUSES = [ nil, STATUS_CLOSED, STATUS_ACTIVE, STATUS_PREPARE, STATUS_PROPOSED, STATUS_PENDING ]
22
24
  field :status
23
25
  validates :status, presence: true
24
26
  scope :active, ->{ where( status: 'active' ) }
27
+ field :schwab_status
25
28
 
26
- belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
29
+ belongs_to :purse, class_name: 'Iro::Purse', inverse_of: :positions
27
30
  index({ purse_id: 1, ticker: 1 })
28
31
 
29
- belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
32
+ belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :positions
30
33
  delegate :ticker, to: :stock
31
34
 
32
35
  belongs_to :strategy, class_name: 'Iro::Strategy', inverse_of: :positions
36
+ delegate :long_or_short, to: :strategy
37
+ delegate :credit_or_debit, to: :strategy
33
38
 
34
- ## no: the strategy can be wheel, and position is put-spread.
35
- # delegate :put_call, to: :strategy
36
39
  field :put_call, type: :string
37
40
  validates :put_call, presence: true
41
+ def put_call
42
+ self[:put_call] || self.strategy.put_call
43
+ end
38
44
 
39
- delegate :long_or_short, to: :strategy
40
- delegate :credit_or_debit, to: :strategy
41
45
 
42
46
  belongs_to :next_strategy, class_name: 'Iro::Strategy', inverse_of: :next_position, optional: true
43
47
 
44
48
 
45
- belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxts, optional: true
46
- belongs_to :autoprev, class_name: 'Iro::Position', inverse_of: :autonxt, optional: true
47
49
  ## there are many of these, for viewing on the 'roll' view
48
- has_many :nxts, class_name: 'Iro::Position', inverse_of: :prev
49
- has_one :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev
50
+ belongs_to :prev, class_name: 'Iro::Position', inverse_of: :nxts, optional: true
51
+ has_many :nxts, class_name: 'Iro::Position', inverse_of: :prev
52
+
53
+ ## 2026-02-26 using this one.
54
+ belongs_to :autonxt, class_name: 'Iro::Position', inverse_of: :autoprev, optional: true
55
+ has_one :autoprev, class_name: 'Iro::Position', inverse_of: :autonxt
56
+
50
57
 
51
58
  ## Options
52
59
 
53
- belongs_to :inner, class_name: 'Iro::Option', inverse_of: :inner
60
+ belongs_to :inner, class_name: 'Iro::Option', inverse_of: :pos_of_inner
54
61
  validates_associated :inner
55
62
 
56
- belongs_to :outer, class_name: 'Iro::Option', inverse_of: :outer
63
+ belongs_to :outer, class_name: 'Iro::Option', inverse_of: :pos_of_outer, optional: true
57
64
  validates_associated :outer
58
65
 
59
66
  accepts_nested_attributes_for :inner, :outer
60
67
 
61
68
  field :outer_strike, type: :float
62
- # validates :outer_strike, presence: true
69
+ validates :outer_strike, presence: true ## 2026-02-24 only to make finding easier.
63
70
 
64
71
  field :inner_strike, type: :float
65
- # validates :inner_strike, presence: true
72
+ validates :inner_strike, presence: true ## 2026-02-24 only to make finding easier.
66
73
 
67
74
  field :expires_on
68
75
  validates :expires_on, presence: true
@@ -75,6 +82,8 @@ class Iro::Position
75
82
 
76
83
  field :end_on
77
84
 
85
+ field :schwab_order_id, type: :integer
86
+
78
87
  def begin_delta
79
88
  strategy.send("begin_delta_#{strategy.kind}", self)
80
89
  end
@@ -83,9 +92,28 @@ class Iro::Position
83
92
  end
84
93
 
85
94
  def breakeven
86
- strategy.send("breakeven_#{strategy.kind}", self)
95
+ send("breakeven_#{strategy.kind}")
96
+ end
97
+ def breakeven_covered_call
98
+ p = self
99
+ p.inner.strike + p.inner.begin_price
100
+ end
101
+ def breakeven_long_debit_call_spread
102
+ p = self
103
+ p.inner.strike - p.max_gain
104
+ end
105
+ ## 2026-02-23
106
+ def breakeven_short_credit_call_spread
107
+ p = self
108
+ p.inner.strike + p.max_gain
109
+ end
110
+ ## 2026-02-23
111
+ def breakeven_long_credit_put_spread
112
+ p = self
113
+ p.inner.strike - p.max_gain
87
114
  end
88
115
 
116
+
89
117
  def current_underlying_strike
90
118
  Iro::Stock.find_by( ticker: ticker ).last
91
119
  end
@@ -104,16 +132,30 @@ class Iro::Position
104
132
  print '^'
105
133
  end
106
134
 
135
+ def roll_price
136
+ pos = self
137
+ out = pos.autoprev.outer.end_price - pos.autoprev.inner.end_price + pos.inner.begin_price - pos.outer.begin_price
138
+ return out.round(2)
139
+ end
140
+
141
+
107
142
  def net_percent
108
143
  net_amount / max_gain
109
144
  end
110
145
  def net_amount # each
111
146
  self.send("net_amount_#{strategy.kind}")
112
147
  end
148
+ def net_amount_covered_call
149
+ inner.begin_price - inner.end_price
150
+ end
113
151
  ## 2025-10-14 tested
114
152
  def net_amount_long_credit_put_spread ## each
115
153
  inner.begin_price - outer.begin_price + outer.end_price - inner.end_price
116
154
  end
155
+ ## 2026-02-19 tested
156
+ def net_amount_short_credit_call_spread
157
+ return net_amount_long_credit_put_spread
158
+ end
117
159
 
118
160
  def max_gain # each
119
161
  strategy.send("max_gain_#{strategy.kind}", self)
@@ -124,6 +166,18 @@ class Iro::Position
124
166
 
125
167
 
126
168
  def sync
169
+ if schwab_order_id
170
+ outs = Tda::Order.check_status schwab_order_id
171
+ update({ schwab_status: outs['status'] })
172
+ if [ Tda::Order::STATUS_FILLED, Tda::Order::STATUS_REPLACED ].include?( outs['status'] )
173
+ ## update amounts.
174
+ purse.update({ available_amount: purse.available_amount + next_gain_loss_amount*quantity*100 })
175
+ ## make this one active
176
+ update({ status: Iro::Position::STATUS_ACTIVE, next_gain_loss_amount: nil })
177
+ ## make previous one closed
178
+ autoprev.update({ status: Iro::Position::STATUS_CLOSED })
179
+ end
180
+ end
127
181
  inner.sync
128
182
  outer.sync
129
183
  end
@@ -138,26 +192,31 @@ class Iro::Position
138
192
 
139
193
  ## should_roll?
140
194
  def calc_rollp
141
- self.next_reasons = []
142
- # self.next_symbol = nil
143
- # self.next_delta = nil
195
+ pos = self
196
+ pos.next_reasons = []
197
+ # pos.next_symbol = nil
198
+ # pos.next_delta = nil
144
199
 
145
- out = strategy.send( "calc_rollp_#{strategy.kind}", self )
200
+ out = strategy.send("calc_rollp_#{strategy.kind}", pos )
146
201
 
147
- self.rollp = out[0]
148
- self.next_reasons.push out[1]
202
+ pos.rollp = out[0]
203
+ pos.next_reasons.push out[1]
149
204
  save
150
205
  end
151
206
 
152
207
  def calc_nxt
153
208
  pos = self
209
+ puts! pos, '#calc_nxt...'
154
210
 
155
- ## 7 days ahead - not configurable so far
156
- outs = Tda::Option.get_quotes({
211
+ ## 7 days ahead - not configurable
212
+ params = {
157
213
  contractType: pos.put_call,
158
214
  expirationDate: next_expires_on,
159
215
  ticker: ticker,
160
- })
216
+ }
217
+ puts! params, 'ze params'
218
+ outs = Tda::Option.get_quotes(params)
219
+ puts! outs, 'outs'
161
220
  outs_bk = outs.dup
162
221
 
163
222
  outs = outs.select do |out|
@@ -169,7 +228,7 @@ class Iro::Position
169
228
  elsif 'PUT' == pos.put_call
170
229
  outs = outs.reverse
171
230
  end
172
- puts! outs, '#calc_nxt.outs -> 2'
231
+ # puts! outs, '#calc_nxt.outs -> 2'
173
232
 
174
233
  ## next_inner_strike
175
234
  outs = outs.select do |out|
@@ -186,7 +245,7 @@ class Iro::Position
186
245
  end
187
246
  end
188
247
  puts! outs[0][:strikePrice], 'after calc next_inner_strike'
189
- puts! outs, 'outs'
248
+ # puts! outs, 'outs'
190
249
 
191
250
  ## next_buffer_above_water
192
251
  outs = outs.select do |out|
@@ -232,37 +291,44 @@ class Iro::Position
232
291
  put_call: pos.put_call,
233
292
  stock_id: pos.stock_id,
234
293
  }
235
- inner_ = Iro::Option.new(o_attrs.merge({
294
+ inner_attrs = o_attrs.merge({
236
295
  strike: inner[:strikePrice],
237
296
  begin_price: ( inner[:bid] + inner[:ask] )/2,
238
297
  begin_delta: inner[:delta],
239
298
  end_price: ( inner[:bid] + inner[:ask] )/2,
240
299
  end_delta: inner[:delta],
241
- }))
242
- outer_ = Iro::Option.new(o_attrs.merge({
300
+ })
301
+ outer_attrs = o_attrs.merge({
243
302
  strike: outer[:strikePrice],
244
303
  begin_price: ( outer[:bid] + outer[:ask] )/2,
245
304
  begin_delta: outer[:delta],
246
305
  end_price: ( outer[:bid] + outer[:ask] )/2,
247
306
  end_delta: outer[:delta],
248
- }))
249
- pos.autonxt ||= Iro::Position.new
250
- pos.autonxt.update({
251
- prev_gain_loss_amount: 'a',
307
+ })
308
+ autonxt_attrs = {
252
309
  put_call: pos.put_call,
253
310
  status: 'proposed',
254
311
  stock: strategy.stock,
255
- inner: inner_,
256
- outer: outer_,
257
- inner_strike: inner_.strike,
258
- outer_strike: outer_.strike,
312
+ inner_strike: inner_attrs[:strike],
313
+ outer_strike: outer_attrs[:strike],
259
314
  begin_on: Time.now.to_date,
260
315
  expires_on: next_expires_on,
261
316
  purse: purse,
262
317
  strategy: strategy,
263
- quantity: 1,
318
+ quantity: pos.quantity,
264
319
  autoprev: pos,
265
- })
320
+ }
321
+ pos.autonxt ||= Iro::Position.where({
322
+ inner_strike: inner_attrs[:strike],
323
+ outer_strike: outer_attrs[:strike],
324
+ purse: purse,
325
+ stock: strategy.stock,
326
+ strategy: strategy,
327
+ }).first
328
+ pos.autonxt ||= Iro::Position.new(autonxt_attrs)
329
+ pos.autonxt.update(autonxt_attrs)
330
+ pos.autonxt.inner.update(inner_attrs)
331
+ pos.autonxt.outer.update(outer_attrs)
266
332
 
267
333
  pos.autonxt.sync
268
334
  pos.autonxt.save!
@@ -282,7 +348,7 @@ class Iro::Position
282
348
  if !out.workday?
283
349
  out = Time.previous_business_day(out)
284
350
  end
285
- return out
351
+ return out.strftime('%Y-%m-%d')
286
352
  end
287
353
 
288
354
  ## ok
@@ -298,14 +364,14 @@ class Iro::Position
298
364
  def to_s
299
365
  out = "#{stock} (#{q}) #{expires_on.to_datetime.strftime('%b %d')} #{strategy.long_or_short} ["
300
366
  if Iro::Strategy::LONG == long_or_short
301
- if outer.strike
302
- out = out + "$#{outer.strike}->"
367
+ if outer&.strike
368
+ out = out + "$#{outer.strike} <- "
303
369
  end
304
370
  out = out + "$#{inner.strike}"
305
371
  else
306
372
  out = out + "$#{inner.strike}"
307
- if outer.strike
308
- out = out + "<-$#{outer.strike}"
373
+ if outer&.strike
374
+ out = out + " -> $#{outer.strike}"
309
375
  end
310
376
  end
311
377
  out += "] "
@@ -29,13 +29,14 @@ class Iro::Purse
29
29
  field :n_next_positions, type: :integer, default: 5
30
30
 
31
31
  field :available_amount, type: :float
32
+ validates :available_amount, presence: true
32
33
  def available
33
34
  available_amount
34
35
  end
35
36
 
36
- def balance
37
- 0.01
38
- end
37
+ # def balance
38
+ # 0.01
39
+ # end
39
40
 
40
41
  def delta_wt_avg( begin_end, long_short, inner_outer )
41
42
  max_loss_total = 0
@@ -26,9 +26,12 @@ class ::Iro::Stock
26
26
 
27
27
  field :last, type: :float
28
28
  field :options_price_increment, type: :float
29
+ validates :options_price_increment, presence: true
29
30
 
30
31
  field :stdev, type: :float
31
32
 
33
+ field :descr, type: :string
34
+
32
35
  has_many :positions, class_name: '::Iro::Position', inverse_of: :stock
33
36
  has_many :strategies, class_name: '::Iro::Strategy', inverse_of: :stock
34
37
  # has_many :purses, class_name: '::Iro::Purse', inverse_of: :stock
@@ -137,5 +140,33 @@ class ::Iro::Stock
137
140
  # sum_sqr = contents.map {|x| x * x}.reduce(&:+) # => 285.0
138
141
  # std_dev = Math.sqrt((sum_sqr - n * mean * mean)/(n-1)) # => 2.7386127875258306
139
142
 
143
+ ##
144
+ ## From: stockdata_org
145
+ ##
146
+ def get_historic_data date_from=nil, date_to=nil
147
+ date_from ||= Time.now - 1.year - 1.week
148
+ date_to ||= date_from + 180.days
149
+ date_from = date_from.strftime('%Y-%m-%d')
150
+ date_to = date_to.strftime('%Y-%m-%d')
151
+ puts! [ticker, date_from, date_to], "ticker,date_from,date_to"
152
+ 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
+ outs['data'].each do |datum|
154
+ existing = ::Iro::Datapoint.find_by({ symbol: ticker, date: datum['date'].to_date.strftime('%Y-%m-%d') }) rescue nil
155
+ if existing
156
+ print('.')
157
+ else
158
+ ::Iro::Datapoint.create!({ symbol: ticker,
159
+ kind: ::Iro::Datapoint::KIND_STOCK,
160
+ date: datum['date'].to_date.strftime('%Y-%m-%d'),
161
+ open: datum['open'],
162
+ high: datum['high'],
163
+ low: datum['low'],
164
+ value: datum['close'],
165
+ volume: datum['volume'],
166
+ })
167
+ print('^')
168
+ end
169
+ end
170
+ end
140
171
 
141
172
  end