-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarket.rb
197 lines (168 loc) · 6.22 KB
/
market.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
require 'yaml'
Dir.chdir File.dirname(File.expand_path(__FILE__))
CONFIG = YAML.load File.read("config.yml")
require 'open-uri'
require 'alpaca/trade/api'
require 'alphavantagerb'
require 'kder'
require 'histogram/array'
require_relative 'db.rb'
require_relative 'simulator.rb'
Alpaca::Trade::Api.configure do |config|
config.endpoint = "https://api.alpaca.markets"
config.key_id = CONFIG[:Alpaca][:ID]
config.key_secret = CONFIG[:Alpaca][:secret]
end
ALP_CLIENT = Alpaca::Trade::Api::Client.new
AV_CLIENT = Alphavantage::Client.new :key => CONFIG[:AV][:key]
class Alpaca::Trade::Api::Client
# This takes care of the issue where I was not able to provide other options
# to the GET request. Now, I can specify "before" and "after" IAW the API.
def bars(timeframe, symbols, opts={})
opts[:limit] ||= 100
opts[:symbols] = symbols.join(',')
validate_timeframe(timeframe)
response = get_request(data_endpoint, "v1/bars/#{timeframe}", opts)
json = JSON.parse(response.body)
json.keys.each_with_object({}) do |symbol, hash|
hash[symbol] = json[symbol].map { |bar| ::Alpaca::Trade::Api::Bar.new(bar) }
end
end
# FIXME
def stock_bars(symbol, opts={})
opts[:limit] ||= 100
opts[:timeframe] ||= '1Day'
response = get_request(data_endpoint, "v2/stocks/#{symbol}/bars", opts)
json = JSON.parse(response.body)
p json
json.keys.each_with_object({}) do |symbol, hash|
hash[symbol] = json[symbol].map { |bar| ::Alpaca::Trade::Api::Bar.new(bar) }
end
end
# Enabling me to use the "qty" query parameter. Playing it extra safe
# by not even sending the parameter unless there's a specified number
def close_position(symbol: nil, qty: nil)
response = delete_request(endpoint,
"v2/positions/#{symbol}#{qty ? "?qty=#{qty}" : ""}")
raise NoPositionForSymbol,
JSON.parse(response.body)['message'] if response.status == 404
::Alpaca::Trade::Api::Position.new(JSON.parse(response.body))
end
end
##############################################################
# How do we define what a precipitous drop in stock price is?
#
# {-100..-0.3 => 99,
# -0.3..-0.2 => 243,
# -0.2..-0.1 => 2532,
# -0.1.. 0 => 267752,
# 0 .. 0.1 => 309317,
# 0.1.. 0.2 => 3218,
# 0.2.. 0.3 => 377,
# 0.3.. 100 => 151}
#
# This is across the NYSE from 1 JAN 2019 to 31 DEC 2019.
##############################################################
module Market
module Stock
extend self
CLOSE = "16:00" # closing time of the markets
DELAY = 15 * 60 # how long to wait (in sec) before grabbing data
# Alpaca download but don't install
def download(tickers, opts={})
span = opts.delete(:span) || 'day'
opts[:limit] ||= 1000
opts.each do |k, v|
if [String, Date, DateTime, Time].include? v.class
opts[k] = DateTime.parse(v.to_s).to_s
end
end
# `CLIENT.bars` returns a hash, so this will also merge them all
# into one. key collision will only happen if the key is duplicated
# in the `ticker` argument.
symbols = tickers.map {|t| t.symbol }
data = symbols.each_slice(50).map do |ticks|
ALP_CLIENT.bars span, ticks, opts
end.inject({}) {|h, v| h.merge v }
# strip out any bar that could be from today's incomplete data
data.each do |sym, bars|
bars.delete_if do |bar|
bar.date == Time.parse(Date.today.to_s) &&
Time.now < (Time.parse(CLOSE) + DELAY)
end
end
# provide a hash so that we can get the ID without
# fetching the symbol from the DB
tids = tickers.map {|t| [t.symbol, t] }.to_h
# Put the data in a hash format so that it's consistent with the
# AlphaVantage style and allows for easy use with DB#multi_insert
data.map do |sym, bars|
[sym, bars.map do |bar|
{:date => bar.time,
:open => bar.open,
:high => bar.high,
:low => bar.low,
:close => bar.close,
:volume => bar.volume,
:span => 'day',
:ticker_id => tids[sym].id
}
end]
end.to_h
end
def install(tickers, opts={})
return {} if opts[:after] == Time.parse(Date.today.to_s)
return {} if opts[:after] == Time.parse(Date.today.to_s) - 1.day &&
Time.now < (Time.parse(CLOSE) + DELAY)
updates = download tickers, opts
DB[:bars].multi_insert updates.values.flatten
updates
end
# can only do one stock at a time
# AlphaVantage
def download_stock(ticker, after: '1900-01-01', before: Date.today.strftime("%Y-%m-%d"))
stock = AV_CLIENT.stock :symbol => ticker.symbol
series = stock.timeseries :outputsize => 'full'
bars = series.output['Time Series (Daily)']
bars = bars.filter {|k, bar| k > after && k < before }
bars.map do |k, bar|
{:date => Time.parse(k),
:open => bar['1. open'].to_f,
:high => bar['2. high'].to_f,
:low => bar['3. low'].to_f,
:close => bar['4. close'].to_f,
:volume => bar['5. volume'].to_i,
:span => 'day',
:ticker_id => ticker.id
}
end
end
def install_stock(stock, **kwargs)
data = download_stock stock, **kwargs
DB[:bars].multi_insert data
end
end
module Futures
def download(future: nil, after: '1900-01-01', before: Date.today.strftime("%Y-%m-%d"))
url = "https://query1.finance.yahoo.com/v7/finance/download/" +
"#{future.ymbol}?" +
"period1=#{after.to_i}&" +
"period2=#{before.to_i}&" +
"interval=1d&events=history&includeAdjustedClose=true"
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) " +
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 " +
"Safari/605.1.15"
data = URI.open(url, "User-Agent" => user_agent) do |site|
site.read
end
data.split("\n").map {|line| line.split "," }.map do |line|
{:date => Time.parse(line[0]),
:open => line[1].to_f,
:high => line[2].to_f,
:low => line[3].to_f,
:close => line[5].to_f,
:volume => line[6].to_f}
end
end
end
end