ish_models 0.0.33.150 → 0.0.33.154
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- metadata +1 -12
- data/lib/warbler/alphavantage_stockwatcher.rb +0 -42
- data/lib/warbler/ameritrade.rb +0 -85
- data/lib/warbler/covered_call.rb +0 -20
- data/lib/warbler/covered_call_watcher.rb +0 -78
- data/lib/warbler/iron_condor.rb +0 -325
- data/lib/warbler/iron_condor_watcher.rb +0 -95
- data/lib/warbler/option_watch.rb +0 -37
- data/lib/warbler/stock_action.rb +0 -18
- data/lib/warbler/stock_option.rb +0 -29
- data/lib/warbler/stock_watch.rb +0 -27
- data/lib/warbler/yahoo_stockwatcher.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84ff89e4f7f1155b9b1f5a5a7ce1b5bf14f608c5493fad0b9a84ec1e3d436c81
|
4
|
+
data.tar.gz: 3c8fc7e7a93296b7b7b958acdeecabfd9081747d66d5417ef2b2b84ff2a5a857
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2297f787e2479cb25fe94d38552fd4ccf17991ff6a175330d26ee2194ad1f5ff244b87381d51818fa551d9f64a30e8248c3757816798218231495737290d325b
|
7
|
+
data.tar.gz: 3ee3b3b2d9f8078186ee1610b542a472826f1984c719383eaac03beb108bfec2e0fbc70cd8173299f3a5786d348c5a83b63efe9cb01f211f8943822997286584
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ish_models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.33.
|
4
|
+
version: 0.0.33.154
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- piousbox
|
@@ -156,17 +156,6 @@ files:
|
|
156
156
|
- lib/tag.rb
|
157
157
|
- lib/venue.rb
|
158
158
|
- lib/video.rb
|
159
|
-
- lib/warbler/alphavantage_stockwatcher.rb
|
160
|
-
- lib/warbler/ameritrade.rb
|
161
|
-
- lib/warbler/covered_call.rb
|
162
|
-
- lib/warbler/covered_call_watcher.rb
|
163
|
-
- lib/warbler/iron_condor.rb
|
164
|
-
- lib/warbler/iron_condor_watcher.rb
|
165
|
-
- lib/warbler/option_watch.rb
|
166
|
-
- lib/warbler/stock_action.rb
|
167
|
-
- lib/warbler/stock_option.rb
|
168
|
-
- lib/warbler/stock_watch.rb
|
169
|
-
- lib/warbler/yahoo_stockwatcher.rb
|
170
159
|
homepage: https://wasya.co
|
171
160
|
licenses:
|
172
161
|
- Proprietary
|
@@ -1,42 +0,0 @@
|
|
1
|
-
|
2
|
-
class AlphavantageStockwatcher
|
3
|
-
|
4
|
-
def initialize
|
5
|
-
end
|
6
|
-
|
7
|
-
# every minute, for alphavantage.co
|
8
|
-
def watch
|
9
|
-
while true
|
10
|
-
|
11
|
-
if Time.now.hour > 14 && Time.now.hours < 21
|
12
|
-
|
13
|
-
stocks = Ish::StockWatch.where( :notification_type => :EMAIL )
|
14
|
-
# puts! stocks.map(&:ticker), "Watching these stocks:"
|
15
|
-
stocks.each do |stock|
|
16
|
-
# puts! stock.ticker, 'stock'
|
17
|
-
r = HTTParty.get "https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=#{stock.ticker}&interval=1min&apikey=X1C5GGH5MZSXMF3O", timeout: 10
|
18
|
-
r2 = JSON.parse( r.body )['Time Series (1min)']
|
19
|
-
r3 = r2[r2.keys.first]['4. close'].to_f
|
20
|
-
if stock.direction == :ABOVE && r3 >= stock.price ||
|
21
|
-
stock.direction == :BELOW && r3 <= stock.price
|
22
|
-
IshManager::ApplicationMailer.stock_alert( stock ).deliver
|
23
|
-
|
24
|
-
## actions
|
25
|
-
## exit the position
|
26
|
-
# stock.stock_actions.where( :is_active => true ).each do |action|
|
27
|
-
# # @TODO: actions
|
28
|
-
# end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
32
|
-
print '.'
|
33
|
-
else
|
34
|
-
print '-'
|
35
|
-
end
|
36
|
-
|
37
|
-
sleep 60
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
data/lib/warbler/ameritrade.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'httparty'
|
3
|
-
|
4
|
-
#
|
5
|
-
# * make calls every once in a while
|
6
|
-
# * If the option price dips below a threshold, close the position (create the order to buy back the option)
|
7
|
-
#
|
8
|
-
|
9
|
-
# cron job or service? Well, I've historically done service. Cron is easier tho. The wiring should be for both.
|
10
|
-
|
11
|
-
# https://developer.tdameritrade.com/option-chains/apis/get/marketdata/chains
|
12
|
-
# FVRR_082021P200
|
13
|
-
|
14
|
-
|
15
|
-
module Warbler::Ameritrade
|
16
|
-
|
17
|
-
CONFIG = {
|
18
|
-
underlying_downprice_tolerance: 0.14,
|
19
|
-
}
|
20
|
-
|
21
|
-
## AKA stop loss
|
22
|
-
def self.main_fvrr_2
|
23
|
-
|
24
|
-
# @TODO: pass the info on the position in here.
|
25
|
-
strike_price = 200
|
26
|
-
|
27
|
-
# What is my risk tolerance here? 14% down movement of the underlying
|
28
|
-
response = ::Warbler::Ameritrade::Api.get_quote({ symbol: 'FVRR' })
|
29
|
-
last_price = response[:lastPrice]
|
30
|
-
tolerable_price = ( strike_price * (1-CONFIG[:underlying_downprice_tolerance]) )
|
31
|
-
|
32
|
-
if last_price < tolerable_price
|
33
|
-
puts! 'LIMIT TRIGGERED, LETS EXIT' # @TODO: send an email
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
class ::Warbler::Ameritrade::Api
|
40
|
-
include ::HTTParty
|
41
|
-
base_uri 'https://api.tdameritrade.com'
|
42
|
-
PUT = 'PUT'
|
43
|
-
CALL = 'CALL'
|
44
|
-
|
45
|
-
def self.get_quote opts
|
46
|
-
# validate input
|
47
|
-
%i| symbol |.each do |s|
|
48
|
-
if !opts[s]
|
49
|
-
raise Ish::InputError.new("invalid input, missing #{s}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
path = "/v1/marketdata/#{opts[:symbol]}/quotes"
|
54
|
-
out = self.get path, { query: { apikey: ::TD_AME[:apiKey] } }
|
55
|
-
out = out.parsed_response[out.parsed_response.keys[0]].symbolize_keys
|
56
|
-
out
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.get_option _opts
|
60
|
-
opts = {}
|
61
|
-
|
62
|
-
# validate input
|
63
|
-
validOpts = %i| contractType strike symbol|
|
64
|
-
validOpts.each do |s|
|
65
|
-
if _opts[s]
|
66
|
-
opts[s] = _opts[s]
|
67
|
-
else
|
68
|
-
raise Ish::InputError.new("invalid input, missing #{s}")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
if _opts[:date]
|
72
|
-
opts[:fromDate] = opts[:toDate] = _opts[:date]
|
73
|
-
else
|
74
|
-
raise Ish::InputError.new("invalid input, missing 'date'")
|
75
|
-
end
|
76
|
-
|
77
|
-
query = { apikey: ::TD_AME[:apiKey], strikeCount: 1 }.merge opts
|
78
|
-
path = "/v1/marketdata/chains"
|
79
|
-
out = self.get path, { query: query }
|
80
|
-
out = out.parsed_response.deep_symbolize_keys
|
81
|
-
tmp_sym = "#{opts[:contractType].to_s.downcase}ExpDateMap".to_sym
|
82
|
-
out[tmp_sym].first[1].first[1][0]
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
data/lib/warbler/covered_call.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
|
2
|
-
class Ish::CoveredCall
|
3
|
-
include Mongoid::Document
|
4
|
-
include Mongoid::Timestamps
|
5
|
-
|
6
|
-
store_in collection: 'ish_covered_call'
|
7
|
-
|
8
|
-
field :expires_on, type: Date
|
9
|
-
validates :expires_on, presence: true
|
10
|
-
field :n_contracts, type: Integer
|
11
|
-
validates :n_contracts, presence: true
|
12
|
-
field :ticker
|
13
|
-
validates :ticker, uniqueness: { scope: :expires_on }
|
14
|
-
validates :ticker, presence: true
|
15
|
-
|
16
|
-
#
|
17
|
-
# Internal, below
|
18
|
-
#
|
19
|
-
|
20
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
|
2
|
-
class ::Ish::CoveredCallWatcher
|
3
|
-
|
4
|
-
def initialize
|
5
|
-
@consumer = OAuth::Consumer.new ALLY_CREDS[:consumer_key], ALLY_CREDS[:consumer_secret], { :site => 'https://api.tradeking.com' }
|
6
|
-
@access_token = OAuth::AccessToken.new(@consumer, ALLY_CREDS[:access_token], ALLY_CREDS[:access_token_secret])
|
7
|
-
end
|
8
|
-
|
9
|
-
def new_order
|
10
|
-
ccall = ::Ish::CoveredCall.all.first
|
11
|
-
xml = ccall.new_multileg_order_example
|
12
|
-
print! xml, 'xml'
|
13
|
-
path = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
14
|
-
# path = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
15
|
-
response = @access_token.post(path, xml)
|
16
|
-
print! response.body, 'response'
|
17
|
-
end
|
18
|
-
|
19
|
-
def watch_once
|
20
|
-
ccalls = ::Ish::CoveredCall.all
|
21
|
-
ccalls.each do |ccall|
|
22
|
-
puts! ccall.ticker, 'Watching this ccall'
|
23
|
-
|
24
|
-
path = "/v1/market/ext/quotes.json?symbols=#{ccall.ticker}"
|
25
|
-
response = @access_token.get(path, {'Accept' => 'application/json'})
|
26
|
-
json = JSON.parse( response.body ).deep_symbolize_keys
|
27
|
-
bid = json[:response][:quotes][:quote][:bid].to_f
|
28
|
-
ask = json[:response][:quotes][:quote][:ask].to_f
|
29
|
-
natural = ( bid + ask ) / 2
|
30
|
-
|
31
|
-
# puts! [ bid, ask ], 'bid, ask'
|
32
|
-
# puts! [ ccall.upper_panic_threshold, ccall.lower_panic_threshold ], 'upper/lower panic'
|
33
|
-
|
34
|
-
## upper panic
|
35
|
-
if bid > ccall.upper_panic_threshold
|
36
|
-
xml = ccall.rollup_xml access_token=@access_token, natural=natural
|
37
|
-
print! xml, 'xml'
|
38
|
-
|
39
|
-
IshManager::ApplicationMailer.ccall_followup_alert( ccall, { action: :rollup } ).deliver_later
|
40
|
-
|
41
|
-
## place order
|
42
|
-
path_preview = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
43
|
-
response = @access_token.post( path_preview, xml )
|
44
|
-
print! response.body
|
45
|
-
# path_order = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
46
|
-
# response = @access_token.post( path_order, xml )
|
47
|
-
# print! response.body
|
48
|
-
end
|
49
|
-
|
50
|
-
## lower panic
|
51
|
-
if ask < ccall.lower_panic_threshold
|
52
|
-
xml = ccall.rolldown_xml access_token=@access_token, natural=natural
|
53
|
-
print! xml, 'xml'
|
54
|
-
|
55
|
-
IshManager::ApplicationMailer.ccall_followup_alert( ccall, { action: :rolldown } ).deliver_later
|
56
|
-
|
57
|
-
## place order
|
58
|
-
path_preview = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
59
|
-
response = @access_token.post( path_preview, xml )
|
60
|
-
print! response.body
|
61
|
-
# path_order = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
62
|
-
# response = @access_token.post( path_order, xml )
|
63
|
-
# print! response.body
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
data/lib/warbler/iron_condor.rb
DELETED
@@ -1,325 +0,0 @@
|
|
1
|
-
|
2
|
-
=begin
|
3
|
-
|
4
|
-
c.update_attributes( call_sell_strike: 242, call_buy_strike: 243, put_sell_strike: 229, put_buy_strike: 228 )
|
5
|
-
|
6
|
-
=end
|
7
|
-
|
8
|
-
class Ish::IronCondor
|
9
|
-
include Mongoid::Document
|
10
|
-
include Mongoid::Timestamps
|
11
|
-
|
12
|
-
store_in collection: 'ish_iron_condor'
|
13
|
-
|
14
|
-
field :expires_on, type: Date
|
15
|
-
validates :expires_on, presence: true
|
16
|
-
field :n_contracts, type: Integer
|
17
|
-
validates :n_contracts, presence: true
|
18
|
-
field :ticker
|
19
|
-
validates :ticker, uniqueness: { scope: :expires_on }
|
20
|
-
validates :ticker, presence: true
|
21
|
-
|
22
|
-
#
|
23
|
-
# Internal, below
|
24
|
-
#
|
25
|
-
|
26
|
-
def created_on; created_at.to_date; end
|
27
|
-
|
28
|
-
def self.all_filled
|
29
|
-
where( status: :filled )
|
30
|
-
end
|
31
|
-
|
32
|
-
STATUSES = [ :queued, :placed, :filled, :rolling_up, :rolling_down, :rolled_up, :rolled_down, :expired ]
|
33
|
-
field :status
|
34
|
-
field :enter_price, type: Float
|
35
|
-
field :call_sell_strike, type: Float
|
36
|
-
field :call_buy_strike, type: Float
|
37
|
-
field :put_sell_strike, type: Float
|
38
|
-
field :put_buy_strike, type: Float
|
39
|
-
field :new_call_sell_strike, type: Float
|
40
|
-
field :new_call_buy_strike, type: Float
|
41
|
-
field :new_put_sell_strike, type: Float
|
42
|
-
field :new_put_buy_strike, type: Float
|
43
|
-
|
44
|
-
field :iv_annual, type: Float
|
45
|
-
def iv_period
|
46
|
-
n_days = created_on.business_days_until expires_on
|
47
|
-
result = iv_annual.to_f / Math.sqrt(252/n_days)
|
48
|
-
end
|
49
|
-
alias_method :period_iv, :iv_period
|
50
|
-
|
51
|
-
## how close to a sell leg I need to be to take followup action
|
52
|
-
def panic_percentage
|
53
|
-
0.01 # 1% for QQQ
|
54
|
-
end
|
55
|
-
|
56
|
-
def buysell_spread
|
57
|
-
1 # $1 for QQQ
|
58
|
-
end
|
59
|
-
|
60
|
-
def get_call_sell_strike
|
61
|
-
result = enter_price * ( 1 - period_iv/100 )
|
62
|
-
result = result.ceil
|
63
|
-
end
|
64
|
-
def get_call_buy_strike
|
65
|
-
call_sell_strike + buysell_spread
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_put_sell_strike
|
69
|
-
result = enter_price * ( 1 - period_iv/100 )
|
70
|
-
result = result.floor
|
71
|
-
end
|
72
|
-
def get_put_buy_strike
|
73
|
-
put_sell_strike - buysell_spread
|
74
|
-
end
|
75
|
-
|
76
|
-
def upper_panic_threshold
|
77
|
-
result = call_sell_strike * ( 1 - panic_percentage )
|
78
|
-
end
|
79
|
-
|
80
|
-
def lower_panic_threshold
|
81
|
-
result = put_sell_strike * ( 1 + panic_percentage )
|
82
|
-
end
|
83
|
-
|
84
|
-
def new_multileg_order_example_done
|
85
|
-
ticker = 'QQQ'
|
86
|
-
px = 0.08
|
87
|
-
account_id = ALLY_CREDS[:account_id]
|
88
|
-
n_contracts = 1
|
89
|
-
sell_strike = 237.0
|
90
|
-
buy_strike = 237.5
|
91
|
-
expiration = '2020-02-21'.to_date
|
92
|
-
|
93
|
-
tmpl = <<~AOL
|
94
|
-
<FIXML xmlns="http://www.fixprotocol.org/FIXML-5-0-SP2">
|
95
|
-
<NewOrdMleg TmInForce="0" Px="#{px}" OrdTyp="2" Acct="#{account_id}">
|
96
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O">
|
97
|
-
<Leg Side="2" Strk="#{sell_strike}"
|
98
|
-
Mat="#{expiration.strftime('%Y-%m-%d')}T00:00:00.000-05:00" MMY="#{expiration.strftime('%y%m')}"
|
99
|
-
SecTyp="OPT" CFI="OC" Sym="#{ticker}"/>
|
100
|
-
</Ord>
|
101
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O">
|
102
|
-
<Leg Side="1" Strk="#{buy_strike}"
|
103
|
-
Mat="#{expiration.strftime('%Y-%m-%d')}T00:00:00.000-05:00" MMY="#{expiration.strftime('%y%m')}"
|
104
|
-
SecTyp="OPT" CFI="OC" Sym="#{ticker}"/>
|
105
|
-
</Ord>
|
106
|
-
</NewOrdMleg>
|
107
|
-
</FIXML>
|
108
|
-
AOL
|
109
|
-
end
|
110
|
-
|
111
|
-
def new_purchase_trash
|
112
|
-
ticker = 'AXU'
|
113
|
-
px = 2.06
|
114
|
-
account_id = ALLY_CREDS[:account_id]
|
115
|
-
n_contracts = 1
|
116
|
-
strike = 2.06
|
117
|
-
|
118
|
-
xml = <<~AOL
|
119
|
-
<FIXML xmlns="http://www.fixprotocol.org/FIXML-5-0-SP2">
|
120
|
-
<Order TmInForce="0" Typ="1" Side="1" Acct="#{account_id}">
|
121
|
-
<Instrmt SecTyp="CS" Sym="#{ticker}"/>
|
122
|
-
<OrdQty Qty="1"/>
|
123
|
-
</Order>
|
124
|
-
</FIXML>
|
125
|
-
AOL
|
126
|
-
end
|
127
|
-
|
128
|
-
## https://www.ally.com/api/invest/documentation/fixml/
|
129
|
-
## https://www.ally.com/api/invest/documentation/trading/
|
130
|
-
## follow up, roll up
|
131
|
-
## buy call to close
|
132
|
-
## sell call to close
|
133
|
-
## sell call to open
|
134
|
-
## buy call to open
|
135
|
-
def rollup_xml access_token=nil, natural=nil
|
136
|
-
@access_token ||= access_token
|
137
|
-
|
138
|
-
new_call_sell_strike = ( natural * ( 1 + period_iv ) ).ceil
|
139
|
-
new_call_buy_strike = new_call_sell_strike + buysell_spread
|
140
|
-
|
141
|
-
# get the costs of the option first, to compute `Px`
|
142
|
-
ymd = expires_on.strftime('%y%m%d')
|
143
|
-
price8 = (new_call_sell_strike*1000).to_i.to_s.rjust(8, '0')
|
144
|
-
path = "/v1/market/ext/quotes.json?symbols=#{ticker}#{ymd}C#{price8}"
|
145
|
-
puts! path, 'path sell'
|
146
|
-
response = @access_token.post(path, {'Accept' => 'application/json'})
|
147
|
-
json_sell = JSON.parse( response.body ).deep_symbolize_keys
|
148
|
-
json_sell_bid = json_sell[:response][:quotes][:quote][:bid].to_f
|
149
|
-
json_sell_ask = json_sell[:response][:quotes][:quote][:ask].to_f
|
150
|
-
|
151
|
-
price8 = (new_call_buy_strike*1000).to_s.rjust(8, '0')
|
152
|
-
path = "/v1/market/ext/quotes.json?symbols=#{ticker}#{ymd}C#{price8}"
|
153
|
-
response = @access_token.post(path, {'Accept' => 'application/json'})
|
154
|
-
json_buy = JSON.parse( response.body ).deep_symbolize_keys
|
155
|
-
json_buy_bid = json_buy[:response][:quotes][:quote][:bid].to_f
|
156
|
-
json_buy_ask = json_buy[:response][:quotes][:quote][:ask].to_f
|
157
|
-
|
158
|
-
px_sell = ( json_sell_bid.to_f + json_sell_ask ) / 2
|
159
|
-
px_sell = px_sell # .round 2
|
160
|
-
px_buy = ( json_buy_bid + json_buy_ask )/ 2
|
161
|
-
px_buy = px_buy # .round 2
|
162
|
-
px = px_sell - px_buy
|
163
|
-
px = ( px * 20 ).round.to_f / 20 # down to nearest 0.05
|
164
|
-
|
165
|
-
json_puts! json_sell, 'json_sell'
|
166
|
-
json_puts! json_buy, 'json_buy'
|
167
|
-
puts! px, '^00 - px'
|
168
|
-
|
169
|
-
=begin
|
170
|
-
update( status: :rolling_up,
|
171
|
-
new_call_sell_strike: new_call_sell_strike,
|
172
|
-
new_call_buy_strike: new_call_buy_strike )
|
173
|
-
=end
|
174
|
-
|
175
|
-
rollup_tmpl =<<~AOL
|
176
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
177
|
-
<FIXML xmlns="http://www.fixprotocol.org/FIXML-5-0-SP2">
|
178
|
-
<NewOrdMleg
|
179
|
-
OrdTyp="2"
|
180
|
-
Px="#{px}"
|
181
|
-
Acct="#{ALLY_CREDS[:account_id]}"
|
182
|
-
TmInForce="0"
|
183
|
-
>
|
184
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="C" >
|
185
|
-
<Leg
|
186
|
-
AcctTyp="5"
|
187
|
-
Side="1"
|
188
|
-
Strk="#{call_sell_strike}"
|
189
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
190
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
191
|
-
SecTyp="OPT"
|
192
|
-
CFI="OC"
|
193
|
-
Sym="#{ticker}" />
|
194
|
-
</Ord>
|
195
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="C" >
|
196
|
-
<Leg
|
197
|
-
Side="2"
|
198
|
-
Strk="#{call_buy_strike}"
|
199
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
200
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
201
|
-
SecTyp="OPT"
|
202
|
-
CFI="OC"
|
203
|
-
Sym="#{ticker}" />
|
204
|
-
</Ord>
|
205
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O" >
|
206
|
-
<Leg
|
207
|
-
Side="2"
|
208
|
-
Strk="#{new_call_sell_strike}"
|
209
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
210
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
211
|
-
SecTyp="OPT"
|
212
|
-
CFI="OC"
|
213
|
-
Sym="#{ticker}" />
|
214
|
-
</Ord>
|
215
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O" >
|
216
|
-
<Leg
|
217
|
-
Side="1"
|
218
|
-
Strk="#{new_call_buy_strike}"
|
219
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
220
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
221
|
-
SecTyp="OPT"
|
222
|
-
CFI="OC"
|
223
|
-
Sym="#{ticker}" />
|
224
|
-
</Ord>
|
225
|
-
</NewOrdMleg>
|
226
|
-
</FIXML>
|
227
|
-
AOL
|
228
|
-
end
|
229
|
-
|
230
|
-
def rolldown_xml access_token=nil, natural=nil
|
231
|
-
@access_token ||= access_token
|
232
|
-
|
233
|
-
new_put_sell_strike = ( natural * ( 1 - period_iv ) ).floor
|
234
|
-
new_put_buy_strike = new_put_sell_strike - buysell_spread
|
235
|
-
|
236
|
-
# get the costs of the option first, to compute `Px`
|
237
|
-
ymd = expires_on.strftime('%y%m%d')
|
238
|
-
price8 = (new_put_sell_strike*1000).to_i.to_s.rjust(8, '0')
|
239
|
-
path = "/v1/market/ext/quotes.json?symbols=#{ticker}#{ymd}C#{price8}"
|
240
|
-
puts! path, 'path sell'
|
241
|
-
response = @access_token.post(path, {'Accept' => 'application/json'})
|
242
|
-
json_sell = JSON.parse( response.body ).deep_symbolize_keys
|
243
|
-
json_sell_bid = json_sell[:response][:quotes][:quote][:bid].to_f
|
244
|
-
json_sell_ask = json_sell[:response][:quotes][:quote][:ask].to_f
|
245
|
-
json_puts! json_sell, 'json_sell'
|
246
|
-
|
247
|
-
price8 = (new_put_buy_strike*1000).to_s.rjust(8, '0')
|
248
|
-
path = "/v1/market/ext/quotes.json?symbols=#{ticker}#{ymd}C#{price8}"
|
249
|
-
puts! path, 'path buy'
|
250
|
-
response = @access_token.post(path, {'Accept' => 'application/json'})
|
251
|
-
json_buy = JSON.parse( response.body ).deep_symbolize_keys
|
252
|
-
json_buy_bid = json_buy[:response][:quotes][:quote][:bid].to_f
|
253
|
-
json_buy_ask = json_buy[:response][:quotes][:quote][:ask].to_f
|
254
|
-
json_puts! json_buy, 'json_buy'
|
255
|
-
|
256
|
-
px_sell = ( json_sell_bid.to_f + json_sell_ask ) / 2
|
257
|
-
px_sell = px_sell # .round 2
|
258
|
-
px_buy = ( json_buy_bid + json_buy_ask )/ 2
|
259
|
-
px_buy = px_buy # .round 2
|
260
|
-
px = px_sell - px_buy
|
261
|
-
px = ( px * 20 ).round.to_f / 20 # down to nearest 0.05
|
262
|
-
puts! px, 'px'
|
263
|
-
|
264
|
-
=begin
|
265
|
-
update( status: :rolling_down,
|
266
|
-
new_put_sell_strike: new_put_sell_strike,
|
267
|
-
new_put_buy_strike: new_put_buy_strike )
|
268
|
-
=end
|
269
|
-
|
270
|
-
rollup_tmpl =<<~AOL
|
271
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
272
|
-
<FIXML xmlns="http://www.fixprotocol.org/FIXML-5-0-SP2">
|
273
|
-
<NewOrdMleg
|
274
|
-
OrdTyp="2"
|
275
|
-
Px="#{px}"
|
276
|
-
Acct="#{ALLY_CREDS[:account_id]}"
|
277
|
-
TmInForce="0"
|
278
|
-
>
|
279
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="C" >
|
280
|
-
<Leg
|
281
|
-
AcctTyp="5"
|
282
|
-
Side="1"
|
283
|
-
Strk="#{put_sell_strike}"
|
284
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
285
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
286
|
-
SecTyp="OPT"
|
287
|
-
CFI="OP"
|
288
|
-
Sym="#{ticker}" />
|
289
|
-
</Ord>
|
290
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="C" >
|
291
|
-
<Leg
|
292
|
-
Side="2"
|
293
|
-
Strk="#{put_buy_strike}"
|
294
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
295
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
296
|
-
SecTyp="OPT"
|
297
|
-
CFI="OP"
|
298
|
-
Sym="#{ticker}" />
|
299
|
-
</Ord>
|
300
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O" >
|
301
|
-
<Leg
|
302
|
-
Side="2"
|
303
|
-
Strk="#{new_put_sell_strike}"
|
304
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
305
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
306
|
-
SecTyp="OPT"
|
307
|
-
CFI="OP"
|
308
|
-
Sym="#{ticker}" />
|
309
|
-
</Ord>
|
310
|
-
<Ord OrdQty="#{n_contracts}" PosEfct="O" >
|
311
|
-
<Leg
|
312
|
-
Side="1"
|
313
|
-
Strk="#{new_put_buy_strike}"
|
314
|
-
Mat="#{expires_on.strftime('%Y-%m-%d')}T00:00:00.000‐05:00"
|
315
|
-
MMY="#{expires_on.strftime('%Y%m')}"
|
316
|
-
SecTyp="OPT"
|
317
|
-
CFI="OP"
|
318
|
-
Sym="#{ticker}" />
|
319
|
-
</Ord>
|
320
|
-
</NewOrdMleg>
|
321
|
-
</FIXML>
|
322
|
-
AOL
|
323
|
-
end
|
324
|
-
|
325
|
-
end
|
@@ -1,95 +0,0 @@
|
|
1
|
-
|
2
|
-
# result = @access_token.get('/v1/accounts.json', {'Accept' => 'application/json'})
|
3
|
-
# json = JSON.parse result.body
|
4
|
-
|
5
|
-
class ::Ish::IronCondorWatcher
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@consumer = OAuth::Consumer.new ALLY_CREDS[:consumer_key], ALLY_CREDS[:consumer_secret], { :site => 'https://api.tradeking.com' }
|
9
|
-
@access_token = OAuth::AccessToken.new(@consumer, ALLY_CREDS[:access_token], ALLY_CREDS[:access_token_secret])
|
10
|
-
end
|
11
|
-
|
12
|
-
def ally_status_update
|
13
|
-
path = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
14
|
-
response = @access_token.get( path, {'Accept' => 'application/json'})
|
15
|
-
print! response.body, 'body'
|
16
|
-
|
17
|
-
# have model AllyOrder ?
|
18
|
-
# Then, if the order is filled, adjust the condor (suppose rolled down):
|
19
|
-
# update field :put_sell_strike
|
20
|
-
# update field :put_buy_strike
|
21
|
-
# update field :status => :filled
|
22
|
-
end
|
23
|
-
|
24
|
-
=begin
|
25
|
-
def new_order
|
26
|
-
condor = ::Ish::IronCondor.all.first
|
27
|
-
xml = condor.new_multileg_order_example
|
28
|
-
print! xml, 'xml'
|
29
|
-
path = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
30
|
-
# path = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
31
|
-
response = @access_token.post(path, xml)
|
32
|
-
print! response.body, 'response'
|
33
|
-
end
|
34
|
-
=end
|
35
|
-
|
36
|
-
def watch_once
|
37
|
-
condors = ::Ish::IronCondor.all_filled
|
38
|
-
condors.each do |condor|
|
39
|
-
puts! condor.ticker, 'Watching this condor'
|
40
|
-
|
41
|
-
path = "/v1/market/ext/quotes.json?symbols=#{condor.ticker}"
|
42
|
-
response = @access_token.get(path, {'Accept' => 'application/json'})
|
43
|
-
json = JSON.parse( response.body ).deep_symbolize_keys
|
44
|
-
bid = json[:response][:quotes][:quote][:bid].to_f
|
45
|
-
ask = json[:response][:quotes][:quote][:ask].to_f
|
46
|
-
natural = ( bid + ask ) / 2
|
47
|
-
|
48
|
-
puts! [ bid, ask ], 'bid, ask'
|
49
|
-
puts! [ condor.upper_panic_threshold, condor.lower_panic_threshold ], 'upper/lower panic'
|
50
|
-
|
51
|
-
## upper panic
|
52
|
-
if bid > condor.upper_panic_threshold
|
53
|
-
xml = condor.rollup_xml access_token=@access_token, natural=natural
|
54
|
-
print! xml, 'xml'
|
55
|
-
|
56
|
-
IshManager::ApplicationMailer.condor_followup_alert( condor, { action: :rollup } ).deliver_later
|
57
|
-
|
58
|
-
## place order
|
59
|
-
path_preview = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
60
|
-
response = @access_token.post( path_preview, xml )
|
61
|
-
print! response.body
|
62
|
-
# path_order = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
63
|
-
# response = @access_token.post( path_order, xml )
|
64
|
-
# print! response.body
|
65
|
-
end
|
66
|
-
|
67
|
-
## lower panic
|
68
|
-
if ask < condor.lower_panic_threshold
|
69
|
-
xml = condor.rolldown_xml access_token=@access_token, natural=natural
|
70
|
-
print! xml, 'xml'
|
71
|
-
|
72
|
-
IshManager::ApplicationMailer.condor_followup_alert( { action: 'rolldown', condor_id: condor.id } ).deliver_later
|
73
|
-
|
74
|
-
## place order
|
75
|
-
path_preview = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders/preview.xml"
|
76
|
-
response = @access_token.post( path_preview, xml )
|
77
|
-
print! response.body
|
78
|
-
# path_order = "/v1/accounts/#{ALLY_CREDS[:account_id]}/orders.xml"
|
79
|
-
# response = @access_token.post( path_order, xml )
|
80
|
-
# print! response.body
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
data/lib/warbler/option_watch.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
|
2
|
-
class Warbler::OptionWatch
|
3
|
-
include Mongoid::Document
|
4
|
-
include Mongoid::Timestamps
|
5
|
-
store_in collection: 'ish_option_watches'
|
6
|
-
|
7
|
-
SLEEP_TIME_SECONDS = 60
|
8
|
-
|
9
|
-
field :ticker # like NVDA
|
10
|
-
validates :ticker, presence: true
|
11
|
-
# field :symbol # like NVDA_021822C230
|
12
|
-
|
13
|
-
## Strike isn't called price!
|
14
|
-
field :strike, :type => Float
|
15
|
-
validates :strike, presence: true
|
16
|
-
|
17
|
-
field :contractType
|
18
|
-
validates :contractType, presence: true
|
19
|
-
|
20
|
-
field :date
|
21
|
-
validates :date, presence: true
|
22
|
-
|
23
|
-
NOTIFICATION_TYPES = [ :NONE, :EMAIL, :SMS ]
|
24
|
-
ACTIONS = NOTIFICATION_TYPES
|
25
|
-
NOTIFICATION_NONE = :NONE
|
26
|
-
NOTIFICATION_EMAIL = :EMAIL
|
27
|
-
NOTIFICATION_SMS = :SMS
|
28
|
-
field :notification_type, :type => Symbol, :as => :action
|
29
|
-
|
30
|
-
DIRECTIONS = [ :ABOVE, :BELOW ]
|
31
|
-
DIRECTION_ABOVE = :ABOVE
|
32
|
-
DIRECTION_BELOW = :BELOW
|
33
|
-
field :direction, :type => Symbol
|
34
|
-
|
35
|
-
belongs_to :profile, :class_name => 'Ish::UserProfile'
|
36
|
-
|
37
|
-
end
|
data/lib/warbler/stock_action.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Stock action. Used to act on the existing inventory (before acting to aquire inventory)
|
4
|
-
# _vp_ 20171026
|
5
|
-
#
|
6
|
-
class Ish::StockAction
|
7
|
-
include Mongoid::Document
|
8
|
-
include Mongoid::Timestamps
|
9
|
-
|
10
|
-
store_in :collection => 'ish_stock_action'
|
11
|
-
|
12
|
-
belongs_to :profile, :class_name => 'Ish::UserProfile'
|
13
|
-
belongs_to :stock_watch, :class_name => 'Ish::StockWatch'
|
14
|
-
has_many :stock_options, :class_name => 'Ish::StockOption'
|
15
|
-
|
16
|
-
field :is_active, :type => Boolean, :default => true # whether anything will be done upon alert trigger
|
17
|
-
|
18
|
-
end
|
data/lib/warbler/stock_option.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Stock Option. Owned by a person. This is a position that is held (or historical data).
|
4
|
-
# _vp_ 20171026
|
5
|
-
#
|
6
|
-
class Ish::StockOption
|
7
|
-
include Mongoid::Document
|
8
|
-
include Mongoid::Timestamps
|
9
|
-
|
10
|
-
store_in :collection => 'ish_stock_option'
|
11
|
-
|
12
|
-
field :ticker
|
13
|
-
field :expires_on, :type => Date
|
14
|
-
field :strike, :type => Float
|
15
|
-
|
16
|
-
DIRECTIONS = [ :CALL, :PUT ]
|
17
|
-
field :direction, :type => Symbol
|
18
|
-
|
19
|
-
field :quantity, :type => Integer
|
20
|
-
field :is_active, :type => Integer, :default => true # whether this position is current or in the past
|
21
|
-
|
22
|
-
belongs_to :profile, :class_name => 'Ish::UserProfile'
|
23
|
-
belongs_to :stock_action, :class_name => 'Ish::StockAction', :optional => true
|
24
|
-
|
25
|
-
def to_s
|
26
|
-
"#{self.ticker} #{self.expires_on.to_time.strftime('%b %d %Y')} #{self.strike} (x #{self.quantity})"
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
data/lib/warbler/stock_watch.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
|
2
|
-
class Warbler::StockWatch
|
3
|
-
include Mongoid::Document
|
4
|
-
include Mongoid::Timestamps
|
5
|
-
store_in collection: 'ish_stock_watches'
|
6
|
-
|
7
|
-
SLEEP_TIME_SECONDS = 60
|
8
|
-
|
9
|
-
field :ticker
|
10
|
-
|
11
|
-
NOTIFICATION_TYPES = [ :NONE, :EMAIL, :SMS ]
|
12
|
-
ACTIONS = NOTIFICATION_TYPES
|
13
|
-
NOTIFICATION_NONE = :NONE
|
14
|
-
NOTIFICATION_EMAIL = :EMAIL
|
15
|
-
NOTIFICATION_SMS = :SMS
|
16
|
-
field :notification_type, :type => Symbol, :as => :action
|
17
|
-
|
18
|
-
field :price, :type => Float
|
19
|
-
|
20
|
-
DIRECTIONS = [ :ABOVE, :BELOW ]
|
21
|
-
DIRECTION_ABOVE = :ABOVE
|
22
|
-
DIRECTION_BELOW = :BELOW
|
23
|
-
field :direction, :type => Symbol
|
24
|
-
|
25
|
-
belongs_to :profile, :class_name => 'Ish::UserProfile'
|
26
|
-
|
27
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
|
2
|
-
class ::YahooStockwatcher
|
3
|
-
|
4
|
-
# For: https://query1.finance.yahoo.com/v7/finance/chart/qqq?interval=1d&indicators=quote
|
5
|
-
def watch_once
|
6
|
-
|
7
|
-
stocks = Ish::StockWatch.where( :notification_type => :EMAIL )
|
8
|
-
# puts! stocks.map(&:ticker), "Watching these stocks"
|
9
|
-
stocks.each do |stock|
|
10
|
-
# puts! stock.ticker, 'ticker'
|
11
|
-
r = HTTParty.get "https://query1.finance.yahoo.com/v7/finance/chart/#{stock.ticker}?interval=1d&indicators=quote", timeout: 10
|
12
|
-
r = JSON.parse( r.body ).deep_symbolize_keys
|
13
|
-
r = r[:chart][:result][0][:meta][:regularMarketPrice]
|
14
|
-
if stock.direction == :ABOVE && r >= stock.price ||
|
15
|
-
stock.direction == :BELOW && r <= stock.price
|
16
|
-
IshManager::ApplicationMailer.stock_alert( stock ).deliver
|
17
|
-
|
18
|
-
## actions
|
19
|
-
## exit the position
|
20
|
-
# stock.stock_actions.where( :is_active => true ).each do |action|
|
21
|
-
# # @TODO: actions
|
22
|
-
# end
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|