ish_models 0.0.33.152 → 0.0.33.157
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.
- checksums.yaml +4 -4
- data/lib/ish/user_profile.rb +4 -2
- data/lib/ish_models.rb +0 -10
- metadata +3 -14
- data/lib/warbler/alphavantage_stockwatcher.rb +0 -42
- data/lib/warbler/ameritrade.rb +0 -90
- 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 -41
- 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: 39d1f84c7a5f67986bcdabe0452417ca60d29474b76ce8f5e30996d69ea6381a
|
4
|
+
data.tar.gz: 3330d74f59f302da138729b3d50578232fd1dde889ce1615acd641a6c1bb8ff0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17049062ec4c11a3937e9e3e39eba3d4f18b97882359a0ac72f9596f034f84c425bda551f36757885af48feb6bbd42603976b00d1bb3707e7a67336121e20b58
|
7
|
+
data.tar.gz: 1fc3d98a4ac0cb4dfeedc247cdbeaa809c98d6cddabdf6a6096a05b91c55389573ed715298d60267f242a339d6444276b3d56542b590396d299580708e0ba74f
|
data/lib/ish/user_profile.rb
CHANGED
@@ -37,8 +37,10 @@ class Ish::UserProfile
|
|
37
37
|
has_many :leads, :class_name => '::Ish::Lead'
|
38
38
|
has_many :photos
|
39
39
|
has_many :reports, inverse_of: :user_profile
|
40
|
-
|
41
|
-
has_many :
|
40
|
+
|
41
|
+
# has_many :stock_watches, class_name: 'IronWarbler::StockWatch'
|
42
|
+
# has_many :option_watches, class_name: 'IronWarbler::OptionWatch'
|
43
|
+
|
42
44
|
has_many :videos, inverse_of: :user_profile
|
43
45
|
has_many :newsitems, inverse_of: :user_profile
|
44
46
|
|
data/lib/ish_models.rb
CHANGED
@@ -61,15 +61,5 @@ require 'tag'
|
|
61
61
|
require 'venue'
|
62
62
|
require 'video'
|
63
63
|
|
64
|
-
require 'warbler/option_watch'
|
65
|
-
require 'warbler/stock_watch'
|
66
|
-
require 'warbler/ameritrade'
|
67
|
-
|
68
|
-
## warbler
|
69
|
-
# require 'warbler/covered_call'
|
70
|
-
# require 'warbler/iron_condor'
|
71
|
-
# require 'warbler/stock_action'
|
72
|
-
|
73
|
-
|
74
64
|
|
75
65
|
|
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.157
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- piousbox
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.0
|
19
|
+
version: 7.3.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 7.0
|
26
|
+
version: 7.3.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: mongoid_paranoia
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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,90 +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 |
|
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
|
-
if _opts[:ticker]
|
77
|
-
opts[:symbol] = _opts[:ticker].upcase
|
78
|
-
else
|
79
|
-
raise Ish::InputError.new("Invalid input, missing 'ticker'.")
|
80
|
-
end
|
81
|
-
|
82
|
-
query = { apikey: ::TD_AME[:apiKey], strikeCount: 1 }.merge opts
|
83
|
-
path = "/v1/marketdata/chains"
|
84
|
-
out = self.get path, { query: query }
|
85
|
-
out = out.parsed_response.deep_symbolize_keys
|
86
|
-
tmp_sym = "#{opts[:contractType].to_s.downcase}ExpDateMap".to_sym
|
87
|
-
out[tmp_sym].first[1].first[1][0]
|
88
|
-
end
|
89
|
-
|
90
|
-
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,41 +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 the same as price!
|
14
|
-
field :strike, :type => Float
|
15
|
-
validates :strike, presence: true
|
16
|
-
|
17
|
-
## What is the price of the option at some strike?
|
18
|
-
field :price, type: Float
|
19
|
-
validates :price, presence: true
|
20
|
-
|
21
|
-
field :contractType
|
22
|
-
validates :contractType, presence: true
|
23
|
-
|
24
|
-
field :date
|
25
|
-
validates :date, presence: true
|
26
|
-
|
27
|
-
NOTIFICATION_TYPES = [ :NONE, :EMAIL, :SMS ]
|
28
|
-
ACTIONS = NOTIFICATION_TYPES
|
29
|
-
NOTIFICATION_NONE = :NONE
|
30
|
-
NOTIFICATION_EMAIL = :EMAIL
|
31
|
-
NOTIFICATION_SMS = :SMS
|
32
|
-
field :notification_type, :type => Symbol, :as => :action
|
33
|
-
|
34
|
-
DIRECTIONS = [ :ABOVE, :BELOW ]
|
35
|
-
DIRECTION_ABOVE = :ABOVE
|
36
|
-
DIRECTION_BELOW = :BELOW
|
37
|
-
field :direction, :type => Symbol
|
38
|
-
|
39
|
-
belongs_to :profile, :class_name => 'Ish::UserProfile'
|
40
|
-
|
41
|
-
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
|