This repository was archived by the owner on Aug 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathhistoric_data_cli
More file actions
executable file
·186 lines (142 loc) · 6.38 KB
/
Copy pathhistoric_data_cli
File metadata and controls
executable file
·186 lines (142 loc) · 6.38 KB
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
#!/usr/bin/env ruby
# This script connects to IB API, and downloads historic data
require 'rubygems'
require 'time'
require 'getopt/long'
require 'bundler/setup'
require 'ib-ruby'
include Getopt
opt = Getopt::Long.getopts(
["--help", BOOLEAN],
["--end", REQUIRED],
["--port", REQUIRED],
["--security", REQUIRED],
["--duration", REQUIRED],
["--barsize", REQUIRED],
["--csv", BOOLEAN],
["--dateformat", REQUIRED],
["--nonregularhours", BOOLEAN],
["--what", OPTIONAL]
)
if opt["help"] || opt["security"].nil? || opt["security"].empty?
puts <<ENDHELP
This program requires a TWS or Gateway running on localhost.
----------
One argument is required: --security, the security specification you want, in
"long serialized IB-Ruby" format. This is a colon-separated string of the format:
symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
For example, to query the Apple 500 Strike Call expiring in January 2013, use:
$ historic_data_cli --security AAPL:OPT:201301:500:CALL::SMART::USD:
Consult contract.rb for allowed values, and see also the examples in the symbols/ directory
(load them in irb and run security#serialize_ib_ruby to see the appropriate string.)
----------
Options:
--end is is the last time we want data for. The default is now.
This is eval'ed by Ruby, so you can use a Ruby expression, which must return a Time object.
--duration is time period before --end, in seconds. The default is 86400 sec (1 day).
TWS imposes limit of 86400 sec (1 day) worth of historic data per request.
--what determines what the data will be comprised of. This can be
"trades", "midpoint", "bid", or "ask". The default is "trades".
--barsize determines how long each bar will be.
Possible bar values (from the IB documentation):
Values less than 30 sec do not appear to work for some securities.
sec1 = 1 sec
sec5 = 5 sec
sec15 = 15 sec
sec30 = 30 sec
min1 = 1 minute
min2 = 2 minutes
min5 = 5 minutes
min15 = 15 minutes (default)
min30 = 30 minutes
hour1 = 1 hour
day1 = 1 day
--nonregularhours : Normally, only data from the instrument's regular trading
hours is returned. If --nonregularhours is given, all data available during the time
span requested is returned, even for time intervals when the market was illiquid.
--dateformat : 1 (default) human-readable time/date format, like "20050307 11:32:16".
If you set it to 2 instead, you will get UNIX epoch offsets (seconds since Jan 1, 1970).
--csv : print out the historic data in CSV format, with header.
--port : 4001 for Gateway (default), 7496 for TWS, or your custom port
ENDHELP
exit
end
### Parameters
# DURATION is how much historic data we want, in seconds, before END_DATE_TIME.
DURATION = (opt["duration"] && opt["duration"].to_i) || 86400
if DURATION > 86400
STDERR.puts("\nTWS rejects --duration longer than 86400 seconds (1 day).\n")
exit(1)
end
# This is the last time we want data for.
END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib
# This can be :trades, :midpoint, :bid, or :asked
WHAT = (opt["what"] && opt["what"].to_sym) || :trades
# Values less than 4 do not appear to actually work; they are rejected by the server.
#
BAR_SIZE = (opt["barsize"] && opt["barsize"].to_sym) || :min15
# If REGULAR_HOURS_ONLY is set to 0, all data available during the time
# span requested is returned, even data bars covering time
# intervals where the market in question was illiquid. If useRTH
# has a non-zero value, only data within the "Regular Trading
# Hours" of the product in question is returned, even if the time
# span requested falls partially or completely outside of them.
REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1
# Using a DATE_FORMAT of 1 will cause the dates in the returned
# messages with the historic data to be in a text format, like
# "20050307 11:32:16". If you set :format_date to 2 instead, you
# will get an offset in seconds from the beginning of 1970, which
# is the same format as the UNIX epoch time.
DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1
PORT = (opt["port"] && opt["port"]) || '4002'
lastMessageTime = Queue.new # for communicating with the reader thread.
# First, connect to IB TWS.
ib = IB::Connection.new( :client_id => 1112, :port => PORT) do | gw|
# Subscribe to TWS alerts/errors
gw.subscribe(:Alert) { |msg| puts msg.to_human }
# Subscribe to incoming HistoricalData events. The code passed in the
# block will be executed when a message of the subscribed type is
# received, with the received message as its argument. In this case,
# we just print out the data.
#
# Note that we have to look the ticker id of each incoming message
# up in local memory to figure out what security it relates to.
# The incoming message packet from TWS just identifies it by ticker id.
#
gw.subscribe(:HistoricalData) do |msg|
if opt["csv"]
puts "date,time,open,high,low,close,volume,wap,has_gaps"
msg.results.each do |datum|
puts "#{datum.time},#{datum.open},#{datum.high},#{datum.low}," +
"#{datum.close},#{datum.volume},#{datum.wap},#{datum.has_gaps}"
end
else
STDERR.puts "Received #{msg.count} items:"
msg.results.each { |datum| puts datum.to_s }
end
lastMessageTime.push(Time.now)
end
end
# Now we actually request historical data for the symbols we're
# interested in. TWS will respond with a HistoricalData message,
# which will be received by the code above.
ib.send_message :RequestHistoricalData,
:request_id => 123,
:contract => opt["security"],
:end_date_time => END_DATE_TIME,
:duration => DURATION, # seconds == 1 hour
:bar_size => BAR_SIZE, # 1 minute bars
:what_to_show => WHAT,
:use_RTH => REGULAR_HOURS_ONLY,
:keep_up_todate => 0,
:format_date => DATE_FORMAT
# A complication here is that IB does not send any indication when all historic data
# is done being delivered. So we have to guess - when there is no more new data for
# some period, we interpret that as "end of data" and exit.
sleep 2
while true
lastTime = lastMessageTime.pop # blocks until a message is ready on the queue
sleep 0.1 # .. wait ..
exit if lastMessageTime.empty? # if still no more messages after 2 more seconds, exit.
end