iron_warbler 2.0.7.8 → 2.0.7.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iron_warbler/positions.scss +47 -0
  3. data/app/assets/stylesheets/iron_warbler/strategies.scss +0 -0
  4. data/app/assets/stylesheets/iron_warbler/utils.css +44 -0
  5. data/app/controllers/iro/alerts_controller.rb +6 -6
  6. data/app/controllers/iro/datapoints_controller.rb +0 -2
  7. data/app/controllers/iro/positions_controller.rb +62 -0
  8. data/app/controllers/iro/profiles_controller.rb +0 -2
  9. data/app/controllers/iro/purses_controller.rb +65 -0
  10. data/app/controllers/iro/stocks_controller.rb +10 -4
  11. data/app/controllers/iro/strategies_controller.rb +66 -0
  12. data/app/models/iro/option.rb +0 -7
  13. data/app/models/iro/position.rb +222 -0
  14. data/app/models/iro/purse.rb +17 -0
  15. data/app/models/iro/stock.rb +12 -2
  16. data/app/models/iro/strategy.rb +38 -0
  17. data/app/models/tda/option.rb +137 -0
  18. data/app/models/tda/{api.rb → stock.rb} +1 -1
  19. data/app/views/iro/_main_header.haml +16 -6
  20. data/app/views/iro/alerts/index.haml +1 -1
  21. data/app/views/iro/positions/_form.haml +59 -0
  22. data/app/views/iro/positions/_reasons.haml +4 -0
  23. data/app/views/iro/positions/_table.haml +137 -0
  24. data/app/views/iro/positions/edit.haml +4 -0
  25. data/app/views/iro/positions/new.haml +4 -0
  26. data/app/views/iro/purses/_form.haml +9 -0
  27. data/app/views/iro/purses/edit.haml +3 -0
  28. data/app/views/iro/purses/index.haml +11 -0
  29. data/app/views/iro/purses/show.haml +10 -0
  30. data/app/views/iro/stocks/_form.haml +4 -0
  31. data/app/views/iro/strategies/_form.haml +37 -0
  32. data/app/views/iro/strategies/_header.haml +8 -0
  33. data/app/views/iro/strategies/_show.haml +18 -0
  34. data/app/views/iro/strategies/_table.haml +18 -0
  35. data/app/views/iro/strategies/edit.haml +3 -0
  36. data/app/views/iro/strategies/new.haml +3 -0
  37. data/config/routes.rb +6 -0
  38. data/lib/iron_warbler.rb +4 -0
  39. data/lib/tasks/iro_tasks.rake +35 -0
  40. metadata +42 -13
  41. data/app/assets/stylesheets/iron_warbler/main.css +0 -13
  42. data/app/assets/stylesheets/scaffold.css +0 -80
  43. data/app/views/iro/alerts/edit.html.erb +0 -6
  44. data/app/views/iro/alerts/new.html.erb +0 -5
  45. data/app/views/iro/alerts/show.html.erb +0 -34
  46. data/app/views/iro/profiles/_form.html.erb +0 -32
  47. data/app/views/iro/profiles/edit.html.erb +0 -6
  48. data/app/views/iro/profiles/index.html.erb +0 -31
  49. data/app/views/iro/profiles/new.html.erb +0 -5
  50. data/app/views/iro/profiles/show.html.erb +0 -19
@@ -0,0 +1,38 @@
1
+
2
+ class Iro::Strategy
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'iro_strategies'
6
+
7
+ field :slug
8
+ validates :slug, presence: true, uniqueness: true
9
+
10
+ has_many :positions, class_name: 'Iro::Position', inverse_of: :strategy
11
+
12
+ ## multiple strategies per ticker
13
+ field :ticker
14
+ validates :ticker, presence: true
15
+ index({ ticker: 1 })
16
+ # belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
17
+
18
+
19
+ field :buffer_above_water, type: :float
20
+ field :next_max_delta, type: :float
21
+ field :next_min_strike, type: :float
22
+ field :threshold_delta, type: :float
23
+ field :threshold_netp, type: :float
24
+
25
+ def self.for_ticker ticker
26
+ where( ticker: ticker )
27
+ end
28
+
29
+
30
+ def to_s
31
+ slug
32
+ end
33
+
34
+ def self.list
35
+ [[nil,nil]] + all.map { |ttt| [ ttt.slug, ttt.id ] }
36
+ end
37
+
38
+ end
@@ -0,0 +1,137 @@
1
+
2
+ require 'httparty'
3
+
4
+ class Tda::Option
5
+
6
+ include ::HTTParty
7
+ base_uri 'https://api.tdameritrade.com'
8
+
9
+
10
+ ##
11
+ ## 2023-02-05 _vp_ :: Gets the entire chain
12
+ ##
13
+ def self.get_chain params
14
+ opts = { symbol: params[:ticker] } ## use 'GME' as symbol here even though a symbol is eg 'GME_021023P2.5'
15
+ query = { apikey: ::TD_AMERITRADE[:apiKey] }.merge opts
16
+ # puts! query, 'input opts'
17
+
18
+ path = "/v1/marketdata/chains"
19
+ out = self.get path, { query: query }
20
+ timestamp = DateTime.parse out.headers['date']
21
+ out = out.parsed_response.deep_symbolize_keys
22
+
23
+
24
+ outs = []
25
+ %w| put call |.each do |contractType|
26
+ tmp_sym = "#{contractType}ExpDateMap".to_sym
27
+ _out = out[tmp_sym]
28
+ _out.each do |date, vs| ## date="2023-02-10:5"
29
+ vs.each do |strike, _v| ## strike="18.5"
30
+ v = _v[0] ## v={} many attrs
31
+ v = v.except( :lastSize, :optionDeliverablesList, :settlementType,
32
+ :deliverableNote, :pennyPilot, :mini )
33
+ v.each do |k, i|
34
+ if i == 'NaN'
35
+ v[k] = nil
36
+ end
37
+ end
38
+
39
+ v[:timestamp] = timestamp
40
+ v[:ticker] = params[:ticker]
41
+ outs.push( v )
42
+ end
43
+ end
44
+ end
45
+
46
+ outs.each do |x|
47
+ opi = ::Iro::OptionPriceItem.create( x )
48
+ if !opi.persisted?
49
+ puts! opi.errors.full_messages, "Cannot create OptionPriceItem"
50
+ end
51
+ end
52
+ end
53
+
54
+ ##
55
+ ## 2023-03-18 _vp_ This is what I should be using to check if a position should be rolled.
56
+ ##
57
+ def self.get_quote params
58
+ ::Tda::Option.get_quotes(params)[0]
59
+ end
60
+
61
+ ##
62
+ ## params: contractType, strike, expirationDate, ticker
63
+ ##
64
+ ## ow = { contractType: 'PUT', ticker: 'GME', date: '2022-12-09' }
65
+ ## query = {:apikey=>"<>", :toDate=>"2022-12-09", :fromDate=>"2022-12-09", :symbol=>"GME"}
66
+ ##
67
+ ## 2023-02-04 _vp_ :: Too specific, but I want the entire chain, every 1-min
68
+ ## 2023-02-06 _vp_ :: Continue.
69
+ ##
70
+ def self.get_quotes params
71
+ puts! params, 'Tda::Option#get_quotes'
72
+ opts = {}
73
+
74
+ #
75
+ # Validate input ???
76
+ #
77
+ validOpts = %i| contractType |
78
+ validOpts.each do |s|
79
+ if params[s]
80
+ opts[s] = params[s]
81
+ else
82
+ raise Iwa::InputError.new("Invalid input, missing '#{s}'.")
83
+ end
84
+ end
85
+ if params[:expirationDate]
86
+ opts[:fromDate] = opts[:toDate] = params[:expirationDate]
87
+ else
88
+ raise Iwa::InputError.new("Invalid input, missing 'date'.")
89
+ end
90
+ if params[:ticker]
91
+ opts[:symbol] = params[:ticker].upcase
92
+ else
93
+ raise Iwa::InputError.new("Invalid input, missing 'ticker'.")
94
+ end
95
+
96
+ if params[:strike]
97
+ opts[:strike] = params[:strike]
98
+ end
99
+
100
+ query = { apikey: ::TD_AMERITRADE[:apiKey] }.merge opts
101
+ # puts! query, 'input opts'
102
+
103
+ path = "/v1/marketdata/chains"
104
+ out = self.get path, { query: query }
105
+ timestamp = DateTime.parse out.headers['date']
106
+ ## out = HTTParty.get "https://api.tdameritrade.com#{path}", { query: query }
107
+ out = out.parsed_response.deep_symbolize_keys
108
+
109
+
110
+ tmp_sym = "#{opts[:contractType].to_s.downcase}ExpDateMap".to_sym
111
+ outs = []
112
+ out = out[tmp_sym]
113
+ out.each do |date, vs|
114
+ vs.each do |strike, _v|
115
+ v = _v[0]
116
+ v = v.except( :lastSize, :optionDeliverablesList, :settlementType,
117
+ :deliverableNote, :pennyPilot, :mini )
118
+ v[:timestamp] = timestamp
119
+ outs.push( v )
120
+ end
121
+ end
122
+
123
+ # puts! outs, 'outs'
124
+ return outs
125
+ end
126
+
127
+
128
+ end
129
+
130
+ =begin
131
+
132
+ outs = Tda::Option.get_quotes({
133
+ contractType: 'CALL', strike: 20.0, expirationDate: '2024-01-12',
134
+ ticker: 'GME',
135
+ })
136
+
137
+ =end
@@ -1,7 +1,7 @@
1
1
 
2
2
  require 'httparty'
3
3
 
4
- class Tda::Api
4
+ class Tda::Stock
5
5
  include ::HTTParty
6
6
  base_uri 'https://api.tdameritrade.com'
7
7
 
@@ -1,13 +1,23 @@
1
1
 
2
+ .main-header.maxwidth
2
3
 
4
+ %i.fa.fa-compress.collapse-expand#collapseHeaderEmail
5
+ Iron Warbler
6
+
7
+ -# .header.collapse-expand#IroMenu
8
+ -# %h5.title Iron Warbler
3
9
 
4
- .main-menu
5
- %h5 Main Menu
6
10
  %ul
7
- %li= link_to 'Home - Dashboard', root_path
8
- %li= link_to 'Stocks', stocks_path
11
+ %li= link_to 'ROOT', root_path
12
+ %li
13
+ = link_to "Stocks (#{Iro::Stock.all.length})", stocks_path
9
14
  -# %li= link_to 'Options', options_path
10
- %li Max Pain
11
- %li= link_to 'Alerts', alerts_path
15
+ -# %li Max Pain
16
+ %li
17
+ = link_to "Alerts (#{Iro::Alert.all.length})", alerts_path
18
+ %li
19
+ = link_to "Purses (#{Iro::Purse.all.length})", purses_path
20
+ %li
21
+ = render '/iro/strategies/header'
12
22
 
13
23
 
@@ -1,5 +1,5 @@
1
1
 
2
- .iro-alerts--index
2
+ .iro-alerts--index.maxwidth
3
3
  %h5 Iro Alerts
4
4
 
5
5
  %ul
@@ -0,0 +1,59 @@
1
+
2
+ .positions--form
3
+ = form_for position do |f|
4
+ .actions
5
+ = f.submit
6
+
7
+ .field
8
+ %label Purse
9
+ -# = f.select :purse_id, options_for_select( @
10
+ = f.text_field :purse_id
11
+
12
+ %label Status
13
+ = f.select :status, options_for_select( Iro::Position::STATUSES, selected: position.status )
14
+
15
+ %br
16
+ %br
17
+
18
+ .field
19
+ %label Ticker
20
+ = f.select :ticker, options_for_select( @tickers_list, selected: position.ticker )
21
+
22
+
23
+ %label Kind
24
+ = f.select :kind, options_for_select( Iro::Position::KINDS, selected: position.kind )
25
+ %label Strategy
26
+ = f.select :strategy_id, options_for_select( @strategies_list, selected: position.strategy_id )
27
+
28
+
29
+ %label Strike
30
+ = f.text_field :strike
31
+
32
+ .field
33
+ %label Expires on
34
+ = f.text_field :expires_on
35
+
36
+
37
+ %label Quantity
38
+ = f.text_field :quantity
39
+
40
+ %br
41
+ %br
42
+
43
+ .flex-row
44
+ %label Begin on
45
+ = f.text_field :begin_on
46
+ %label Begin price
47
+ = f.text_field :begin_price
48
+ %label Begin delta
49
+ = f.text_field :begin_delta
50
+ .flex-row
51
+ %label End on
52
+ = f.text_field :end_on
53
+ %label End price
54
+ = f.text_field :end_price
55
+ %label End delta
56
+ = f.text_field :end_delta
57
+
58
+ .actions
59
+ = f.submit
@@ -0,0 +1,4 @@
1
+
2
+ %ul.positions--reasons.modal-absolute.hide
3
+ - reasons.each do |nnn|
4
+ %li= nnn
@@ -0,0 +1,137 @@
1
+
2
+ .positions--table
3
+
4
+ %h5 Positions
5
+ %table.bordered
6
+ %tr
7
+ %th &nbsp;
8
+ %th
9
+ .status
10
+ Status
11
+ %br
12
+ Strategy
13
+ %th.ticker
14
+ .a Ticker
15
+ %th
16
+ .kind
17
+ Kind
18
+ -# covered call (mounted elephant),
19
+ -# reckless defence (credit put spread) (spearman)
20
+ -# reckless re-defence (soaring eagle)
21
+ %th.strike
22
+ .a Strike
23
+ %th
24
+ .expires_on Expires On
25
+ %th
26
+ .end_n_days End N Days
27
+ %th
28
+ .quantity Q
29
+
30
+ %th
31
+ .begin_on begin_on
32
+ %th.begin_price
33
+ .a Begin price
34
+ %th.to_open
35
+ .a To open
36
+ %th
37
+ .begin_delta begin_delta
38
+
39
+ %th
40
+ .end_price End Price
41
+ %th
42
+ .net_amount Net Amount
43
+ %th
44
+ .net_percent Net %
45
+ %th.end_delta
46
+ .a end_delta
47
+ -# %th
48
+ -# .end_n_days End n days
49
+ -# %th
50
+ -# .end_on End on
51
+ %th.should_rollp
52
+ .a should_rollp
53
+ %th.next_position
54
+ .a
55
+ next_symbol
56
+ %br
57
+ next_delta
58
+ %br
59
+ next_outcome
60
+
61
+ - positions.each_with_index do |position, idx|
62
+ - if idx > 0
63
+ - prev_position = positions[idx-1]
64
+ - if prev_position && position[:expires_on] != prev_position[:expires_on]
65
+ %tr
66
+ %td{ colspan: 18 }
67
+ %hr
68
+
69
+ %tr
70
+ %td.actions
71
+ .flex-row
72
+ = button_to 'x', position_path(position), method: :delete, data: { confirm: 'Are you sure?' }
73
+ = link_to '[~]', edit_position_path(position)
74
+ - if position.next_reasons.present? && position.should_rollp && position.should_rollp > 0.5
75
+ = button_to 'Roll', roll_position_path(position)
76
+ - if position.next_reasons.present?
77
+ .collapse-expand{ id: "ce-p-#{position.id}" } [Reasons]
78
+ = render '/iro/positions/reasons', reasons: position.next_reasons
79
+
80
+
81
+ %td.status.strategy
82
+ = position.status
83
+ -# %br
84
+ -# = link_to "[~ #{position.strategy}]", edit_strategy_path(position.strategy)
85
+ .collapse-expand{ id: "ce-pos-reasons-#{position.id}" }
86
+ [#{position.strategy}]
87
+ = render '/iro/strategies/show', strategy: position.strategy
88
+
89
+ %td.ticker
90
+ = position.ticker
91
+ %br
92
+ = pp_amount position.current_underlying_strike
93
+ %td.kind.mini
94
+ = position.kind
95
+ %td.strike
96
+ = pp_amount position.strike
97
+ %td.expires_on.mini
98
+ = position.expires_on rescue nil
99
+ %td.end_n_days
100
+ -# = ( position.expires_on.to_date - position.begin_on.to_date ).to_i
101
+ %b= ( position.expires_on.to_date - Time.now.to_date ).to_i
102
+ %td= position.quantity
103
+
104
+ %td.begin_on.mini
105
+ = position.begin_on rescue nil
106
+ %td.begin_price
107
+ = pp_amount position.begin_price
108
+ %td.to_open
109
+ = pp_amount( position.begin_price * 100 * position.quantity )
110
+ %td= position.begin_delta
111
+
112
+ %td.end_price
113
+ = pp_amount position.end_price
114
+ %td.net_amount
115
+ = pp_amount ( position.begin_price - position.end_price ) * 100 * position.quantity rescue nil
116
+ %td.net_percent
117
+ = pp_percent( ( position.begin_price - position.end_price ) / position.begin_price ) rescue nil
118
+ %td.end_delta= position.end_delta
119
+ -# %td.end_n_days &nbsp;
120
+ -# %td.end_on &nbsp;
121
+
122
+ %td.should_rollp
123
+ = pp_percent position.should_rollp rescue nil
124
+ %td.next_position.mini
125
+ .a= position.next_symbol
126
+ .a= position.next_delta
127
+ .a
128
+ = pp_amount position.next_mark
129
+ \=>
130
+ = pp_amount position.next_outcome * 100 * position.quantity rescue nil
131
+
132
+
133
+
134
+
135
+
136
+
137
+
@@ -0,0 +1,4 @@
1
+
2
+ .positions-edit.maxwidth
3
+
4
+ = render 'form', position: @position
@@ -0,0 +1,4 @@
1
+
2
+ .positions-new.maxwidth
3
+ %h5 New Position
4
+ = render 'form', position: @position
@@ -0,0 +1,9 @@
1
+
2
+ .iro-purses--form
3
+ = form_for purse do |f|
4
+ .flex-row
5
+ .field
6
+ %label Slug
7
+ = f.text_field :slug
8
+ .actions
9
+ = f.submit
@@ -0,0 +1,3 @@
1
+
2
+ .purses-edit.maxwidth
3
+ = render 'form', purse: @purse
@@ -0,0 +1,11 @@
1
+
2
+ .iro-purses--index.maxwidth
3
+ %h5 Iro Purses
4
+
5
+ %ul
6
+ - @purses.each do |purse|
7
+ %li
8
+ = link_to '[~]', edit_purse_path(purse)
9
+ = link_to purse.slug, purse_path(purse)
10
+
11
+ %li= render 'iro/purses/form', purse: Iro::Purse.new
@@ -0,0 +1,10 @@
1
+
2
+ .purses-show.padded
3
+
4
+ .maxwidth
5
+ .header
6
+ %h5.title Purse `#{@purse.slug}` (#{@purse.positions.length})
7
+ %ul.menu-secondary
8
+ %li= link_to '[+position]', new_position_path({ purse_id: @purse.id })
9
+
10
+ = render '/iro/positions/table', positions: @purse.positions
@@ -11,5 +11,9 @@
11
11
  .field
12
12
  = f.label :ticker
13
13
  = f.text_field :ticker
14
+
15
+ .field
16
+ %label Last
17
+ = f.number_field :last, placeholder: '80.0', step: 0.01
14
18
  .actions
15
19
  = f.submit
@@ -0,0 +1,37 @@
1
+
2
+ .strategies--form
3
+ = form_for strategy do |f|
4
+ .actions
5
+ = f.submit
6
+
7
+ .field
8
+ %label Ticker
9
+ = f.select :ticker, options_for_select( @tickers_list, selected: strategy.ticker )
10
+
11
+ .field
12
+ %label slug
13
+ = f.text_field :slug
14
+
15
+ .field
16
+ = f.label :buffer_above_water
17
+ = f.number_field :buffer_above_water, placeholder: "0.49", step: 0.01
18
+ .field
19
+ = f.label :next_max_delta
20
+ = f.number_field :next_max_delta, placeholder: "0.25", step: 0.01
21
+ .field
22
+ = f.label :next_min_strike
23
+ = f.number_field :next_min_strike, placeholder: "20.0", step: 0.01
24
+ .field
25
+ = f.label :threshold_delta
26
+ = f.number_field :threshold_delta, placeholder: "0.14", step: 0.01
27
+ .field
28
+ = f.label :threshold_netp
29
+ = f.number_field :threshold_netp, placeholder: "0.69", step: 0.01
30
+ -# .field
31
+ -# = f.label :current_underlying_strike
32
+ -# = f.number_field :current_underlying_strike, placeholder: "16.66", step: 0.01
33
+
34
+ .actions
35
+ = f.submit
36
+
37
+
@@ -0,0 +1,8 @@
1
+
2
+ = link_to "Strategies (#{Iro::Strategy.all.length})", strategies_path
3
+ .inline-search
4
+ = form_tag strategies_path, method: :get do
5
+ = text_field_tag :q
6
+ = link_to '[+]', new_strategy_path
7
+
8
+
@@ -0,0 +1,18 @@
1
+
2
+ .strategies--show.modal-absolute.hide
3
+ .header
4
+ Strategy
5
+ = link_to '[~]', edit_strategy_path(strategy)
6
+ = strategy
7
+ %ul
8
+ %li <b>ticker:</b> #{strategy.ticker}
9
+ %li <b>slug:</b> #{strategy.slug}
10
+
11
+ %li <b>buffer_above_water:</b> #{strategy.buffer_above_water}
12
+
13
+ %li <b>next_max_delta:</b> #{strategy.next_max_delta}
14
+ %li <b>next_min_strike:</b> #{strategy.next_min_strike}
15
+
16
+ %li <b>threshold_delta:</b> #{strategy.threshold_delta}
17
+ %li <b>threshold_netp:</b> #{strategy.threshold_netp}
18
+
@@ -0,0 +1,18 @@
1
+
2
+ - strategies ||= @strategies
3
+
4
+ .strategies--table
5
+
6
+ %table.bordered
7
+ %tr
8
+ %th &nbsp;
9
+ %th ticker
10
+ %th slug
11
+ %th next_max_delta
12
+ - strategies.each do |sss|
13
+ %tr
14
+ %td= link_to '[~]', edit_strategy_path(sss)
15
+ %td= sss.ticker
16
+ %td= sss.slug
17
+ %td= sss.next_max_delta
18
+
@@ -0,0 +1,3 @@
1
+
2
+ .strategies-edit.maxwidth
3
+ = render 'form', strategy: @strategy
@@ -0,0 +1,3 @@
1
+
2
+ .maxwidth
3
+ = render 'form', strategy: @strategy
data/config/routes.rb CHANGED
@@ -8,7 +8,13 @@ Iro::Engine.routes.draw do
8
8
  get 'datapoints', to: '/iro/datapoints#index'
9
9
 
10
10
  resources :option_watches
11
+
12
+ post 'positions/:id/roll', to: 'positions#roll', as: :roll_position
13
+ resources :positions
11
14
  resources :profiles
15
+ resources :purses
16
+
12
17
  resources :stocks
18
+ resources :strategies
13
19
 
14
20
  end
data/lib/iron_warbler.rb CHANGED
@@ -1,4 +1,8 @@
1
1
 
2
+ require 'business_time'
3
+
2
4
  require 'haml'
5
+
3
6
  require 'mongoid'
7
+
4
8
  require "iro/engine"
@@ -1,6 +1,41 @@
1
1
 
2
2
  namespace :iro do
3
3
 
4
+ desc 'recommend position actions'
5
+ task recommend_position_actions: :environment do
6
+ Iro::Position.active.where({ kind: 'covered_call' }).map &:should_roll?
7
+ end
8
+
9
+ desc 'refresh positions'
10
+ task refresh_positions: :environment do
11
+ Iro::Position.active.where({ kind: 'covered_call' }).map &:refresh
12
+ end
13
+
14
+ desc 'watch positions'
15
+ task watch_positions: :environment do
16
+ while true
17
+ if in_business
18
+
19
+ positions = Iro::Position.active.where({ kind: 'covered_call' })
20
+ positions.each do |position|
21
+ out = Tda::Option.get_quote({
22
+ contractType: 'CALL',
23
+ strike: position.strike,
24
+ expirationDate: position.expires_on,
25
+ ticker: position.ticker,
26
+ })
27
+ position.update({
28
+ end_delta: out[:delta],
29
+ end_price: out[:last],
30
+ })
31
+ end
32
+
33
+ print '.'
34
+ end
35
+ sleep 60 # seconds
36
+ end
37
+ end
38
+
4
39
  desc 'watch stocks'
5
40
  task watch_stocks: :environment do
6
41
  while true