wdevauld-ib-ruby 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/LICENSE +504 -0
- data/Manifest.txt +20 -0
- data/README.txt +50 -0
- data/Rakefile +33 -0
- data/bin/AccountInfo +71 -0
- data/bin/HistoricToCSV +129 -0
- data/bin/RequestHistoricData +312 -0
- data/bin/RequestMarketData +80 -0
- data/bin/SimpleTimeAndSales +101 -0
- data/bin/ib-ruby +8 -0
- data/lib/ib-ruby.rb +49 -0
- data/lib/ib-ruby/datatypes.rb +402 -0
- data/lib/ib-ruby/ib.rb +242 -0
- data/lib/ib-ruby/messages.rb +1449 -0
- data/lib/ib-ruby/symbols/forex.rb +62 -0
- data/lib/ib-ruby/symbols/futures.rb +114 -0
- data/spec/ib-ruby_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/test/test_ib-ruby.rb +162 -0
- metadata +96 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/AccountInfo
|
7
|
+
bin/HistoricToCSV
|
8
|
+
bin/RequestHistoricData
|
9
|
+
bin/RequestMarketData
|
10
|
+
bin/SimpleTimeAndSales
|
11
|
+
bin/ib-ruby
|
12
|
+
lib/ib-ruby.rb
|
13
|
+
lib/ib-ruby/datatypes.rb
|
14
|
+
lib/ib-ruby/ib.rb
|
15
|
+
lib/ib-ruby/messages.rb
|
16
|
+
lib/ib-ruby/symbols/forex.rb
|
17
|
+
lib/ib-ruby/symbols/futures.rb
|
18
|
+
spec/ib-ruby_spec.rb
|
19
|
+
spec/spec_helper.rb
|
20
|
+
test/test_ib-ruby.rb
|
data/README.txt
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
ib-ruby
|
2
|
+
By Wes Devauld (wes at devauld dot ca)
|
3
|
+
http://github.com/wdevauld/ib-ruby
|
4
|
+
|
5
|
+
This is a fork of Paul Legato's (pjlegato at gmail dot com) work found at:
|
6
|
+
http://github.com/pjlegato/ib-ruby
|
7
|
+
|
8
|
+
Copyright (C) 2009 Wes Devauld
|
9
|
+
|
10
|
+
== DESCRIPTION:
|
11
|
+
|
12
|
+
* Ruby Implementation of the Interactive Broker' TWS API
|
13
|
+
|
14
|
+
== FEATURES/PROBLEMS:
|
15
|
+
|
16
|
+
* This is a ALPHA release, and should not be used for live trading. Any features contained with are AS-IS and may not work in all conditions
|
17
|
+
* This code is not sanctioned by Interactive Brokers
|
18
|
+
* TODO Deal with Logging properly
|
19
|
+
== SYNOPSIS:
|
20
|
+
|
21
|
+
First, start up Interactive Broker's Trader Work Station. Ensure it is configured to allow API connections on localhost
|
22
|
+
|
23
|
+
>> require 'ib-ruby'
|
24
|
+
>> ib_connection = IB:IB.new()
|
25
|
+
|
26
|
+
== REQUIREMENTS:
|
27
|
+
|
28
|
+
* FIXME List all the requirements
|
29
|
+
|
30
|
+
== INSTALL:
|
31
|
+
|
32
|
+
* Ensure that http://gems.github.com is in your gem sources
|
33
|
+
* sudo gem install wdevauld-ib-ruby
|
34
|
+
|
35
|
+
== LICENSE:
|
36
|
+
|
37
|
+
This library is free software; you can redistribute it and/or modify
|
38
|
+
it under the terms of the GNU Lesser General Public License as
|
39
|
+
published by the Free Software Foundation; either version 2.1 of the
|
40
|
+
License, or (at your option) any later version.
|
41
|
+
|
42
|
+
This library is distributed in the hope that it will be useful, but
|
43
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
44
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
45
|
+
Lesser General Public License for more details.
|
46
|
+
|
47
|
+
You should have received a copy of the GNU Lesser General Public
|
48
|
+
License along with this library; if not, write to the Free Software
|
49
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
50
|
+
02110-1301 USA
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# This library is free software; you can redistribute it and/or
|
2
|
+
# modify it under the terms of the GNU Lesser General Public
|
3
|
+
# License see README.txt for more details
|
4
|
+
|
5
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
6
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
7
|
+
# are where the options are used.
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'bones'
|
11
|
+
Bones.setup
|
12
|
+
rescue LoadError
|
13
|
+
begin
|
14
|
+
load 'tasks/setup.rb'
|
15
|
+
rescue LoadError
|
16
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ensure_in_path 'lib'
|
21
|
+
require 'ib-ruby'
|
22
|
+
|
23
|
+
task :default => 'spec:run'
|
24
|
+
|
25
|
+
PROJ.name = 'ib-ruby'
|
26
|
+
PROJ.authors = 'Wes Devauld'
|
27
|
+
PROJ.email = 'wes@devauld.ca'
|
28
|
+
PROJ.url = 'http://github.com/wdevauld/ib-ruby/tree/master'
|
29
|
+
PROJ.version = IbRuby::VERSION
|
30
|
+
|
31
|
+
PROJ.spec.opts << '--color'
|
32
|
+
|
33
|
+
# EOF
|
data/bin/AccountInfo
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007 Paul Legato.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Lesser General Public License as
|
7
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful, but
|
11
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18
|
+
# 02110-1301 USA
|
19
|
+
#
|
20
|
+
|
21
|
+
$:.push(File.dirname(__FILE__) + "/../")
|
22
|
+
|
23
|
+
require 'ib'
|
24
|
+
require 'datatypes'
|
25
|
+
require 'symbols/futures'
|
26
|
+
|
27
|
+
# First, connect to IB TWS.
|
28
|
+
ib = IB::IB.new
|
29
|
+
|
30
|
+
# Uncomment this for verbose debug messages:
|
31
|
+
# IB::IBLogger.level = Logger::Severity::DEBUG
|
32
|
+
|
33
|
+
## Subscribe to the messages that TWS sends in response to a request
|
34
|
+
## for account data.
|
35
|
+
|
36
|
+
ib.subscribe(IB::IncomingMessages::AccountValue, lambda {|msg|
|
37
|
+
puts msg.to_human
|
38
|
+
})
|
39
|
+
|
40
|
+
ib.subscribe(IB::IncomingMessages::PortfolioValue, lambda {|msg|
|
41
|
+
puts msg.to_human
|
42
|
+
})
|
43
|
+
|
44
|
+
ib.subscribe(IB::IncomingMessages::AccountUpdateTime, lambda {|msg|
|
45
|
+
puts msg.to_human
|
46
|
+
})
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
msg = IB::OutgoingMessages::RequestAccountData.new({
|
51
|
+
:subscribe => true,
|
52
|
+
:account_code => ''
|
53
|
+
})
|
54
|
+
ib.dispatch(msg)
|
55
|
+
|
56
|
+
|
57
|
+
puts "\n\n\t******** Press <Enter> to quit.. *********\n\n"
|
58
|
+
|
59
|
+
gets
|
60
|
+
|
61
|
+
puts "Cancelling account data subscription.."
|
62
|
+
|
63
|
+
msg = IB::OutgoingMessages::RequestAccountData.new({
|
64
|
+
:subscribe => false,
|
65
|
+
:account_code => ''
|
66
|
+
})
|
67
|
+
ib.dispatch(msg)
|
68
|
+
|
69
|
+
|
70
|
+
puts "Done."
|
71
|
+
|
data/bin/HistoricToCSV
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007 Paul Legato.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Lesser General Public License as
|
7
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful, but
|
11
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18
|
+
# 02110-1301 USA
|
19
|
+
#
|
20
|
+
#####################################################################
|
21
|
+
#
|
22
|
+
# This program demonstrates how to download historic data and write it
|
23
|
+
# to a CSV file.
|
24
|
+
#
|
25
|
+
# To use, set CSV_FILE to the file you want to write (which will be
|
26
|
+
# overwritten automatically if it already exists), set the contract
|
27
|
+
# data, duration, data type (trades, bid, ask, midpoint), etc. as you
|
28
|
+
# like, and run the program.
|
29
|
+
#
|
30
|
+
# Note that it does not detect when the historic data from the server
|
31
|
+
# has stopped arriving automatically. This limitation will be
|
32
|
+
# addressed soon. For now, just press <Enter> when the data stream on
|
33
|
+
# the console stops, and the output file will be closed and the
|
34
|
+
# program terminated.
|
35
|
+
#
|
36
|
+
|
37
|
+
$:.push(File.dirname(__FILE__) + "/../")
|
38
|
+
|
39
|
+
require 'ib'
|
40
|
+
require 'datatypes'
|
41
|
+
require 'symbols/futures'
|
42
|
+
|
43
|
+
|
44
|
+
### Configurable Options
|
45
|
+
|
46
|
+
# if Quiet == false, status data will be printed to STDERR
|
47
|
+
Quiet = false
|
48
|
+
|
49
|
+
# How long to wait when no messages are received from TWS before
|
50
|
+
# exiting, in seconds
|
51
|
+
Timeout = 2
|
52
|
+
|
53
|
+
SymbolToRequest = IB::Symbols::Futures[:eur]
|
54
|
+
|
55
|
+
### end options
|
56
|
+
|
57
|
+
|
58
|
+
#
|
59
|
+
# Definition of what we want market data for. We have to keep track
|
60
|
+
# of what ticker id corresponds to what symbol ourselves, because the
|
61
|
+
# ticks don't include any other identifying information.
|
62
|
+
#
|
63
|
+
# The choice of ticker ids is, as far as I can tell, arbitrary.
|
64
|
+
#
|
65
|
+
# Note that as of 4/07 there is no historical data available for forex spot.
|
66
|
+
#
|
67
|
+
@market =
|
68
|
+
{
|
69
|
+
123 => SymbolToRequest
|
70
|
+
}
|
71
|
+
|
72
|
+
# To determine when the timeout has passed.
|
73
|
+
@last_msg_time = Time.now.to_i + 2
|
74
|
+
|
75
|
+
# Connect to IB TWS.
|
76
|
+
ib = IB::IB.new
|
77
|
+
|
78
|
+
# Uncomment this for verbose debug messages:
|
79
|
+
# IB::IBLogger.level = Logger::Severity::DEBUG
|
80
|
+
|
81
|
+
#
|
82
|
+
# Now, subscribe to HistoricalData incoming events. The code
|
83
|
+
# passed in the block will be executed when a message of that type is
|
84
|
+
# received, with the received message as its argument. In this case,
|
85
|
+
# we just print out the data.
|
86
|
+
#
|
87
|
+
# Note that we have to look the ticker id of each incoming message
|
88
|
+
# up in local memory to figure out what it's for.
|
89
|
+
#
|
90
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
91
|
+
# locally in forex.rb, and is just arbitrary text.)
|
92
|
+
|
93
|
+
|
94
|
+
ib.subscribe(IB::IncomingMessages::HistoricalData, lambda {|msg|
|
95
|
+
|
96
|
+
STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s + " items:" unless Quiet
|
97
|
+
|
98
|
+
msg.data[:history].each { |datum|
|
99
|
+
|
100
|
+
@last_msg_time = Time.now.to_i
|
101
|
+
|
102
|
+
STDERR.puts " " + datum.to_s unless Quiet
|
103
|
+
STDOUT.puts "#{datum.date},#{datum.open.to_digits},#{datum.high.to_digits},#{datum.low.to_digits},#{datum.close.to_digits},#{datum.volume}"
|
104
|
+
}
|
105
|
+
})
|
106
|
+
|
107
|
+
# Now we actually request historical data for the symbols we're
|
108
|
+
# interested in. TWS will respond with a HistoricalData message,
|
109
|
+
# which will be received by the code above.
|
110
|
+
|
111
|
+
@market.each_pair {|id, contract|
|
112
|
+
msg = IB::OutgoingMessages::RequestHistoricalData.new({
|
113
|
+
:ticker_id => id,
|
114
|
+
:contract => contract,
|
115
|
+
:end_date_time => Time.now.to_ib,
|
116
|
+
:duration => (60 * 60 * 24).to_s, # how long before end_date_time to request in seconds - this means 1 day
|
117
|
+
:bar_size => IB::OutgoingMessages::RequestHistoricalData::BarSizes.index(:five_minutes),
|
118
|
+
:what_to_show => :trades,
|
119
|
+
:use_RTH => 0,
|
120
|
+
:format_date => 2
|
121
|
+
})
|
122
|
+
ib.dispatch(msg)
|
123
|
+
}
|
124
|
+
|
125
|
+
|
126
|
+
while true
|
127
|
+
exit(0) if Time.now.to_i > @last_msg_time + Timeout
|
128
|
+
sleep 1
|
129
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007-8 Paul Legato. pjlegato at gmail dot com.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Lesser General Public License as
|
7
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful, but
|
11
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18
|
+
# 02110-1301 USA
|
19
|
+
#
|
20
|
+
# >> YOUR USE OF THIS PROGRAM IS ENTIRELY AT YOUR OWN RISK. <<
|
21
|
+
# >> IT MAY CONTAIN POTENTIALLY COSTLY BUGS, ERRORS, ETC., BOTH KNOWN AND UNKNOWN. <<
|
22
|
+
#
|
23
|
+
|
24
|
+
$:.push(File.dirname(__FILE__) + "/../")
|
25
|
+
|
26
|
+
# IB-Ruby libraries
|
27
|
+
require 'ib'
|
28
|
+
require 'datatypes'
|
29
|
+
require 'symbols/futures'
|
30
|
+
|
31
|
+
# Stdlib
|
32
|
+
require 'time' # for extended time parsing
|
33
|
+
|
34
|
+
# Gems - requires duration and getopt.
|
35
|
+
require 'rubygems'
|
36
|
+
require 'duration'
|
37
|
+
require 'getopt/long'
|
38
|
+
|
39
|
+
|
40
|
+
require "getopt/long"
|
41
|
+
include Getopt
|
42
|
+
opt = Getopt::Long.getopts(
|
43
|
+
["--help", BOOLEAN],
|
44
|
+
["--end", REQUIRED],
|
45
|
+
["--security", REQUIRED],
|
46
|
+
["--duration", REQUIRED],
|
47
|
+
["--barsize", REQUIRED],
|
48
|
+
["--header",BOOLEAN],
|
49
|
+
["--dateformat", REQUIRED],
|
50
|
+
["--nonregularhours", BOOLEAN],
|
51
|
+
["--verbose", BOOLEAN],
|
52
|
+
["--veryverbose", BOOLEAN]
|
53
|
+
)
|
54
|
+
|
55
|
+
if opt["help"] || opt["security"].nil? || opt["security"].empty?
|
56
|
+
puts <<ENDHELP
|
57
|
+
|
58
|
+
** RequestHistoricData.rb - Copyright (C) 2007-8 Paul Legato.
|
59
|
+
|
60
|
+
This library is free software; you can redistribute it and/or modify
|
61
|
+
it under the terms of the GNU Lesser General Public License as
|
62
|
+
published by the Free Software Foundation; either version 2.1 of the
|
63
|
+
License, or (at your option) any later version.
|
64
|
+
|
65
|
+
This library is distributed in the hope that it will be useful, but
|
66
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
67
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
68
|
+
Lesser General Public License for more details.
|
69
|
+
|
70
|
+
You should have received a copy of the GNU Lesser General Public
|
71
|
+
License along with this library; if not, write to the Free Software
|
72
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
73
|
+
02110-1301 USA
|
74
|
+
|
75
|
+
The author and this software are not connected with Interactive
|
76
|
+
Brokers in any way, nor do they endorse us.
|
77
|
+
|
78
|
+
************************************************************************************
|
79
|
+
|
80
|
+
>> YOUR USE OF THIS PROGRAM IS ENTIRELY AT YOUR OWN RISK. <<
|
81
|
+
>> IT MAY CONTAIN POTENTIALLY COSTLY BUGS, ERRORS, ETC., BOTH KNOWN AND UNKNOWN. <<
|
82
|
+
>> DO NOT USE THIS SOFTWARE IF YOU ARE UNWILLING TO ACCEPT ALL RISK IN DOING SO. <<
|
83
|
+
|
84
|
+
************************************************************************************
|
85
|
+
|
86
|
+
|
87
|
+
This program requires a TWS running on localhost on the standard port
|
88
|
+
that uses API protocol version 15 or higher. Any modern TWS should
|
89
|
+
work. (Patches to make it work on an arbitrary host/port are welcome.)
|
90
|
+
|
91
|
+
----------
|
92
|
+
|
93
|
+
One argument is required: --security, the security specification you want, in
|
94
|
+
"long serialized IB-Ruby" format. This is a colon-separated string of the format:
|
95
|
+
|
96
|
+
symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
97
|
+
|
98
|
+
Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
|
99
|
+
|
100
|
+
For example, to query the British pound futures contract trading on Globex expiring in September, 2008,
|
101
|
+
the correct command line is:
|
102
|
+
|
103
|
+
./RequestHistoricData.rb --security GBP:FUT:200809:::62500:GLOBEX::USD:
|
104
|
+
|
105
|
+
Consult datatypes.rb for allowed values, and see also the examples in the symbols/ directory (load them in
|
106
|
+
irb and run security#serialize_ib_ruby(ib_version) to see the appropriate string.)
|
107
|
+
|
108
|
+
***
|
109
|
+
|
110
|
+
Options:
|
111
|
+
|
112
|
+
--end is is the last time we want data for. The default is now.
|
113
|
+
This is eval'ed by Ruby, so you can use a Ruby expression, which must return a Time object.
|
114
|
+
|
115
|
+
|
116
|
+
--duration is how much historic data we want, in seconds, before --end's time.
|
117
|
+
The default is 86400 (seconds, which is 1 day.)
|
118
|
+
The TWS-imposed limit is 86400 (1 day per request.) Requests for more than 86400 seconds worth of historic data will fail.
|
119
|
+
|
120
|
+
--what determines what the data will be comprised of. This can be "trades", "midpoint", "bid", or "asked".
|
121
|
+
The default is "trades".
|
122
|
+
|
123
|
+
--barsize determines how long each bar will be.
|
124
|
+
|
125
|
+
Possible values (from the IB documentation):
|
126
|
+
|
127
|
+
1 = 1 sec
|
128
|
+
2 = 5 sec
|
129
|
+
3 = 15 sec
|
130
|
+
4 = 30 sec
|
131
|
+
5 = 1 minute
|
132
|
+
6 = 2 minutes
|
133
|
+
7 = 5 minutes
|
134
|
+
8 = 15 minutes
|
135
|
+
9 = 30 minutes
|
136
|
+
10 = 1 hour
|
137
|
+
11 = 1 day
|
138
|
+
|
139
|
+
Values less than 4 do not appear to work for some securities.
|
140
|
+
The default is 8, 15 minutes.
|
141
|
+
|
142
|
+
--nonregularhours :
|
143
|
+
Normally, only data from the instrument's regular trading hours is returned.
|
144
|
+
If --nonregularhours is given, all data available during the time
|
145
|
+
span requested is returned, even data bars covering time
|
146
|
+
intervals where the market in question was illiquid. If
|
147
|
+
|
148
|
+
|
149
|
+
--dateformat : a --dateformat of 1 will cause the dates in the returned
|
150
|
+
messages with the historic data to be in a text format, like
|
151
|
+
"20050307 11:32:16". If you set it to 2 instead, you
|
152
|
+
will get an offset in seconds from the beginning of 1970, which
|
153
|
+
is the same format as the UNIX epoch time.
|
154
|
+
|
155
|
+
The default is 1 (human-readable time.)
|
156
|
+
|
157
|
+
--header : if present, prints a 1 line CSV header describing the fields in the CSV.
|
158
|
+
|
159
|
+
--veryverbose : if present, prints very verbose debugging info.
|
160
|
+
--verbose : if present, prints all messages received from IB, and print the data in human-readable
|
161
|
+
format.
|
162
|
+
|
163
|
+
Otherwise, in the default mode, prints only the historic data (and any errors), and prints the
|
164
|
+
data in CSV format.
|
165
|
+
|
166
|
+
ENDHELP
|
167
|
+
#' <- fix broken syntax highlighting in Aquamacs
|
168
|
+
exit
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
### Parameters
|
173
|
+
|
174
|
+
# DURATION is how much historic data we want, in seconds, before END_DATE_TIME.
|
175
|
+
# (The 'duration' gem gives us methods like #hour on integers.)
|
176
|
+
DURATION = (opt["duration"] && opt["duration"].to_i) || 1.day
|
177
|
+
|
178
|
+
if DURATION > 86400
|
179
|
+
STDERR.puts("\nTWS does not accept a --duration longer than 86400 seconds (1 day.) Please try again with a smaller duration.\n\n")
|
180
|
+
exit(1)
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
# This is the last time we want data for.
|
185
|
+
END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib
|
186
|
+
|
187
|
+
|
188
|
+
# This can be :trades, :midpoint, :bid, or :asked
|
189
|
+
WHAT = (opt["what"] && opt["what"].to_sym) || :trades
|
190
|
+
|
191
|
+
# Possible bar size values:
|
192
|
+
# 1 = 1 sec
|
193
|
+
# 2 = 5 sec
|
194
|
+
# 3 = 15 sec
|
195
|
+
# 4 = 30 sec
|
196
|
+
# 5 = 1 minute
|
197
|
+
# 6 = 2 minutes
|
198
|
+
# 7 = 5 minutes
|
199
|
+
# 8 = 15 minutes
|
200
|
+
# 9 = 30 minutes
|
201
|
+
# 10 = 1 hour
|
202
|
+
# 11 = 1 day
|
203
|
+
#
|
204
|
+
# Values less than 4 do not appear to actually work; they are rejected by the server.
|
205
|
+
#
|
206
|
+
BAR_SIZE = (opt["barsize"] && opt["barsize"].to_i) || 8
|
207
|
+
|
208
|
+
# If REGULAR_HOURS_ONLY is set to 0, all data available during the time
|
209
|
+
# span requested is returned, even data bars covering time
|
210
|
+
# intervals where the market in question was illiquid. If useRTH
|
211
|
+
# has a non-zero value, only data within the "Regular Trading
|
212
|
+
# Hours" of the product in question is returned, even if the time
|
213
|
+
# span requested falls partially or completely outside of them.
|
214
|
+
|
215
|
+
REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1
|
216
|
+
|
217
|
+
# Using a DATE_FORMAT of 1 will cause the dates in the returned
|
218
|
+
# messages with the historic data to be in a text format, like
|
219
|
+
# "20050307 11:32:16". If you set :format_date to 2 instead, you
|
220
|
+
# will get an offset in seconds from the beginning of 1970, which
|
221
|
+
# is the same format as the UNIX epoch time.
|
222
|
+
|
223
|
+
DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1
|
224
|
+
|
225
|
+
VERYVERBOSE = !opt["veryverbose"].nil?
|
226
|
+
VERBOSE = !opt["verbose"].nil?
|
227
|
+
|
228
|
+
#
|
229
|
+
# Definition of what we want market data for. We have to keep track
|
230
|
+
# of what ticker id corresponds to what symbol ourselves, because the
|
231
|
+
# ticks don't include any other identifying information.
|
232
|
+
#
|
233
|
+
# The choice of ticker ids is, as far as I can tell, arbitrary.
|
234
|
+
#
|
235
|
+
# Note that as of 4/07 there is no historical data available for forex spot.
|
236
|
+
#
|
237
|
+
@market =
|
238
|
+
{
|
239
|
+
123 => opt["security"]
|
240
|
+
}
|
241
|
+
|
242
|
+
|
243
|
+
# First, connect to IB TWS.
|
244
|
+
ib = IB::IB.new
|
245
|
+
|
246
|
+
|
247
|
+
# Default level is quiet, only warnings printed.
|
248
|
+
IB::IBLogger.level = Logger::Severity::ERROR
|
249
|
+
|
250
|
+
# For verbose printing of each message:
|
251
|
+
IB::IBLogger.level = Logger::Severity::INFO if VERBOSE
|
252
|
+
|
253
|
+
# For very verbose debug messages:
|
254
|
+
IB::IBLogger.level = Logger::Severity::DEBUG if VERYVERBOSE
|
255
|
+
|
256
|
+
puts "datetime,open,high,low,close,volume,wap,has_gaps" if !opt["header"].nil?
|
257
|
+
|
258
|
+
lastMessageTime = Queue.new # for communicating with the reader thread.
|
259
|
+
|
260
|
+
#
|
261
|
+
# Subscribe to incoming HistoricalData events. The code passed in the
|
262
|
+
# block will be executed when a message of the subscribed type is
|
263
|
+
# received, with the received message as its argument. In this case,
|
264
|
+
# we just print out the data.
|
265
|
+
#
|
266
|
+
# Note that we have to look the ticker id of each incoming message
|
267
|
+
# up in local memory to figure out what security it relates to.
|
268
|
+
# The incoming message packet from TWS just identifies it by ticker id.
|
269
|
+
#
|
270
|
+
ib.subscribe(IB::IncomingMessages::HistoricalData, lambda {|msg|
|
271
|
+
STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s + " items:" if VERBOSE
|
272
|
+
|
273
|
+
msg.data[:history].each { |datum|
|
274
|
+
puts(if VERBOSE
|
275
|
+
datum.to_s
|
276
|
+
else
|
277
|
+
"#{datum.date},#{datum.open.to_digits},#{datum.high.to_digits},#{datum.low.to_digits}," +
|
278
|
+
"#{datum.close.to_digits},#{datum.volume},#{datum.wap.to_digits},#{datum.has_gaps}"
|
279
|
+
end
|
280
|
+
)
|
281
|
+
}
|
282
|
+
lastMessageTime.push(Time.now)
|
283
|
+
})
|
284
|
+
|
285
|
+
# Now we actually request historical data for the symbols we're
|
286
|
+
# interested in. TWS will respond with a HistoricalData message,
|
287
|
+
# which will be received by the code above.
|
288
|
+
|
289
|
+
@market.each_pair {|id, contract|
|
290
|
+
msg = IB::OutgoingMessages::RequestHistoricalData.new({
|
291
|
+
:ticker_id => id,
|
292
|
+
:contract => contract,
|
293
|
+
:end_date_time => END_DATE_TIME,
|
294
|
+
:duration => DURATION, # seconds == 1 hour
|
295
|
+
:bar_size => BAR_SIZE, # 1 minute bars
|
296
|
+
:what_to_show => WHAT,
|
297
|
+
:use_RTH => REGULAR_HOURS_ONLY,
|
298
|
+
:format_date => DATE_FORMAT
|
299
|
+
})
|
300
|
+
ib.dispatch(msg)
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
# A complication here is that IB does not send any indication when all historic data is done being delivered.
|
305
|
+
# So we have to guess - when there is no more new data for some period, we interpret that as "end of data" and exit.
|
306
|
+
|
307
|
+
while true
|
308
|
+
lastTime = lastMessageTime.pop # blocks until a message is ready on the queue
|
309
|
+
sleep 2 # .. wait ..
|
310
|
+
exit if lastMessageTime.empty? # if still no more messages after 2 more seconds, exit.
|
311
|
+
end
|
312
|
+
|