ib-ruby 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/HISTORY +4 -0
  2. data/README.md +25 -17
  3. data/VERSION +1 -1
  4. data/bin/account_info +1 -1
  5. data/bin/cancel_orders +2 -1
  6. data/bin/contract_details +3 -2
  7. data/bin/depth_of_market +1 -1
  8. data/bin/historic_data +1 -1
  9. data/bin/historic_data_cli +57 -104
  10. data/bin/list_orders +4 -3
  11. data/bin/market_data +1 -1
  12. data/bin/option_data +1 -1
  13. data/bin/place_combo_order +63 -0
  14. data/bin/place_order +2 -4
  15. data/bin/template +1 -1
  16. data/bin/{generic_data.rb → tick_data} +3 -1
  17. data/bin/time_and_sales +1 -1
  18. data/lib/ib-ruby.rb +1 -0
  19. data/lib/ib-ruby/connection.rb +68 -68
  20. data/lib/ib-ruby/errors.rb +28 -0
  21. data/lib/ib-ruby/extensions.rb +7 -0
  22. data/lib/ib-ruby/messages.rb +1 -0
  23. data/lib/ib-ruby/messages/abstract_message.rb +16 -11
  24. data/lib/ib-ruby/messages/incoming.rb +125 -329
  25. data/lib/ib-ruby/messages/incoming/open_order.rb +193 -0
  26. data/lib/ib-ruby/messages/incoming/ticks.rb +131 -0
  27. data/lib/ib-ruby/messages/outgoing.rb +44 -45
  28. data/lib/ib-ruby/models/combo_leg.rb +16 -1
  29. data/lib/ib-ruby/models/contract.rb +18 -10
  30. data/lib/ib-ruby/models/contract/bag.rb +1 -7
  31. data/lib/ib-ruby/models/execution.rb +2 -1
  32. data/lib/ib-ruby/models/model.rb +1 -1
  33. data/lib/ib-ruby/models/order.rb +116 -56
  34. data/lib/ib-ruby/socket.rb +24 -3
  35. data/spec/account_helper.rb +2 -1
  36. data/spec/ib-ruby/messages/outgoing_spec.rb +1 -1
  37. data/spec/ib-ruby/models/combo_leg_spec.rb +0 -1
  38. data/spec/integration/account_info_spec.rb +2 -2
  39. data/spec/integration/contract_info_spec.rb +4 -4
  40. data/spec/integration/depth_data_spec.rb +3 -3
  41. data/spec/integration/historic_data_spec.rb +1 -1
  42. data/spec/integration/market_data_spec.rb +4 -4
  43. data/spec/integration/option_data_spec.rb +1 -1
  44. data/spec/integration/orders/combo_spec.rb +51 -0
  45. data/spec/integration/orders/execution_spec.rb +15 -8
  46. data/spec/integration/orders/placement_spec.rb +46 -72
  47. data/spec/integration/orders/valid_ids_spec.rb +6 -6
  48. data/spec/integration_helper.rb +0 -79
  49. data/spec/order_helper.rb +153 -0
  50. metadata +13 -4
data/HISTORY CHANGED
@@ -133,3 +133,7 @@
133
133
  == 0.6.1 / 2012-03-14
134
134
 
135
135
  * Version bump
136
+
137
+ == 0.7.0 / 2012-03-22
138
+
139
+ * Support for API v.967
data/README.md CHANGED
@@ -1,27 +1,33 @@
1
1
  # ib-ruby
2
2
 
3
- Ruby Implementation of the Interactive Brokers Trader Workstation (TWS) API v.965.
3
+ Ruby Implementation of the Interactive Brokers Trader Workstation (TWS) API v.965-967.
4
4
 
5
- Copyright (C) 2006-2011 Paul Legato, Wes Devauld, and Ar Vicco.
5
+ Copyright (C) 2006-2012 Paul Legato, Wes Devauld, and Ar Vicco.
6
6
 
7
7
  https://github.com/ib-ruby/ib-ruby
8
8
 
9
9
  __WARNING:__ This software is provided __AS-IS__ with __NO WARRANTY__, express or
10
- implied. Your use of this software is at your own risk. It may contain
11
- any number of bugs, known or unknown, which might cause you to lose
12
- money if you use it. You've been warned.
10
+ implied. Your use of this software is at your own risk. It may contain any number
11
+ of bugs, known or unknown, which might cause you to lose money if you use it.
12
+ You've been warned.
13
13
 
14
- This code is not sanctioned or supported by Interactive Brokers
15
- This software is available under the LGPL. See the file LICENSE for full licensing details.
14
+ This code is not sanctioned or supported by Interactive Brokers.
16
15
 
17
16
  ## REQUIREMENTS:
18
17
 
19
- Either the Interactive Brokers
20
- [TWS](http://www.interactivebrokers.com/en/p.php?f=tws) or
21
- [IB Gateway](http://www.interactivebrokers.com/en/control/systemstandalone-ibGateway.php?os=unix&ib_entity=llc)
22
- software must be installed and configured to allow API connections
23
- from the computer you plan to run ib-ruby on, which is typically
24
- localhost if you're running ib-ruby on the same machine as TWS.
18
+ Either the Interactive Brokers [TWS](http://www.interactivebrokers.com/en/p.php?f=tws) or
19
+ [Gateway](http://www.interactivebrokers.com/en/p.php?f=programInterface&ib_entity=llc)
20
+ software must be installed and configured to allow API connections from the computer
21
+ you plan to run ib-ruby on, which is typically localhost if you're running ib-ruby on
22
+ the same machine as TWS.
23
+
24
+ As a rule of thumb, most recent version of ib-ruby gem only supports latest versions
25
+ of TWS/Gateway API. Older versions of API are supported by previous gem versions:
26
+
27
+ ib-ruby gem TWS version API version
28
+ 0.5.21 918-920 965
29
+ 0.6.1 921-923 966
30
+ 0.7.1 924+ 967
25
31
 
26
32
  ## INSTALLATION:
27
33
 
@@ -44,7 +50,7 @@ and :port options given to IB::Connection.new.
44
50
 
45
51
  require 'ib-ruby'
46
52
 
47
- ib = IB::Connection.new
53
+ ib = IB::Connection.new :port => 7496 # TWS on localhost
48
54
  ib.subscribe(:Alert, :AccountValue) { |msg| puts msg.to_human }
49
55
  ib.send_message :RequestAccountData
50
56
  ib.wait_for :AccountDownloadEnd
@@ -72,7 +78,7 @@ from TWS, with the received message as its argument.
72
78
 
73
79
  Then, you request specific data from TWS using `Connection#send_message` or place
74
80
  your order using `Connection#place_order`. TWS will respond with messages that you
75
- should have subscribed for, and these messages are be processed in a code block
81
+ should have subscribed for, and these messages will be processed in a code block
76
82
  given to `#subscribe`.
77
83
 
78
84
  In order to give TWS time to respond, you either run a message processing loop or
@@ -88,6 +94,8 @@ directory for more scenarios and examples of handling IB messages.
88
94
 
89
95
  ## LICENSE:
90
96
 
97
+ This software is available under the LGPL.
98
+
91
99
  This library is free software; you can redistribute it and/or modify
92
100
  it under the terms of the GNU Lesser General Public License as
93
101
  published by the Free Software Foundation; either version 2.1 of the
@@ -95,8 +103,8 @@ License, or (at your option) any later version.
95
103
 
96
104
  This library is distributed in the hope that it will be useful, but
97
105
  WITHOUT ANY WARRANTY; without even the implied warranty of
98
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
99
- Lesser General Public License for more details.
106
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file LICENSE
107
+ for full licensing details of GNU Lesser General Public License.
100
108
 
101
109
  You should have received a copy of the GNU Lesser General Public
102
110
  License along with this library; if not, write to the Free Software
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.7.0
data/bin/account_info CHANGED
@@ -12,7 +12,7 @@ require 'bundler/setup'
12
12
  require 'ib-ruby'
13
13
 
14
14
  # First, connect to IB TWS.
15
- ib = IB::Connection.new
15
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
16
16
 
17
17
  # Set log level
18
18
  log.level = Logger::FATAL
data/bin/cancel_orders CHANGED
@@ -13,7 +13,7 @@ require 'bundler/setup'
13
13
  require 'ib-ruby'
14
14
 
15
15
  # First, connect to IB TWS.
16
- ib = IB::Connection.new
16
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
17
17
 
18
18
  # Subscribe to TWS alerts/errors and order-related messages
19
19
  ib.subscribe(:Alert, :OpenOrder, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.to_human }
@@ -21,6 +21,7 @@ ib.subscribe(:Alert, :OpenOrder, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.t
21
21
  if ARGV.empty?
22
22
  ib.send_message :RequestGlobalCancel
23
23
  else
24
+ # Will only work for Orders placed under the same :client_id
24
25
  ib.cancel_order *ARGV
25
26
  end
26
27
 
data/bin/contract_details CHANGED
@@ -18,7 +18,7 @@ require 'ib-ruby'
18
18
  144 => IB::Symbols::Stocks[:wrong]}
19
19
 
20
20
  # Connect to IB TWS.
21
- ib = IB::Connection.new
21
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
22
22
 
23
23
  # Subscribe to TWS alerts/errors
24
24
  ib.subscribe(IB::Messages::Incoming::Alert) { |msg| puts msg.to_human }
@@ -34,4 +34,5 @@ ib.subscribe(:ContractData) { |msg| puts msg.contract.inspect }
34
34
  ib.send_message :RequestContractData, :id => id, :contract => contract
35
35
  end
36
36
 
37
- sleep 2 # Wait for IB to respond to our request
37
+ # Wait for IB to respond to our request
38
+ ib.wait_for :ContractDataEnd
data/bin/depth_of_market CHANGED
@@ -20,7 +20,7 @@ require 'ib-ruby'
20
20
  }
21
21
 
22
22
  # First, connect to IB TWS.
23
- ib = IB::Connection.new
23
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
24
24
 
25
25
  # Subscribe to TWS alerts/errors
26
26
  ib.subscribe(:Alert) { |msg| puts msg.to_human }
data/bin/historic_data CHANGED
@@ -19,7 +19,7 @@ require 'ib-ruby'
19
19
  }
20
20
 
21
21
  # Connect to IB TWS.
22
- ib = IB::Connection.new
22
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
23
23
 
24
24
  # Subscribe to TWS alerts/errors
25
25
  ib.subscribe(:Alert) { |msg| puts msg.to_human }
@@ -1,11 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
- #
3
2
  # This script connects to IB API, and downloads historic data
4
- # TODO: Fix the Historical command line client
5
3
 
6
4
  require 'rubygems'
7
5
  require 'time'
8
- require 'duration'
9
6
  require 'pathname'
10
7
  require 'getopt/long'
11
8
  require 'bundler/setup'
@@ -20,22 +17,19 @@ include Getopt
20
17
  opt = Getopt::Long.getopts(
21
18
  ["--help", BOOLEAN],
22
19
  ["--end", REQUIRED],
20
+ ["--port", REQUIRED],
23
21
  ["--security", REQUIRED],
24
22
  ["--duration", REQUIRED],
25
23
  ["--barsize", REQUIRED],
26
- ["--header", BOOLEAN],
24
+ ["--csv", BOOLEAN],
27
25
  ["--dateformat", REQUIRED],
28
- ["--nonregularhours", BOOLEAN],
26
+ ["--nonregularhours", BOOLEAN]
29
27
  )
30
28
 
31
29
  if opt["help"] || opt["security"].nil? || opt["security"].empty?
32
30
  puts <<ENDHELP
33
31
 
34
- ***
35
-
36
- This program requires a TWS running on localhost on the standard port
37
- that uses API protocol version 15 or higher. Any modern TWS should
38
- work. (Patches to make it work on an arbitrary host/port are welcome.)
32
+ This program requires a TWS or Gateway running on localhost.
39
33
 
40
34
  ----------
41
35
 
@@ -46,82 +40,65 @@ One argument is required: --security, the security specification you want, in
46
40
 
47
41
  Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
48
42
 
49
- For example, to query the British pound futures contract trading on Globex expiring in September, 2008,
50
- the correct command line is:
43
+ For example, to query the Apple 500 Strike Call expiring in January 2013, use:
51
44
 
52
- ./RequestHistoricData.rb --security GBP:FUT:200809:::62500:GLOBEX::USD:
45
+ $ historic_data_cli --security AAPL:OPT:201301:500:CALL::SMART::USD:
53
46
 
54
- Consult datatypes.rb for allowed values, and see also the examples in the symbols/ directory (load them in
55
- irb and run security#serialize_ib_ruby(ib_version) to see the appropriate string.)
47
+ Consult contract.rb for allowed values, and see also the examples in the symbols/ directory
48
+ (load them in irb and run security#serialize_ib_ruby to see the appropriate string.)
56
49
 
57
- ***
50
+ ----------
58
51
 
59
52
  Options:
60
53
 
61
54
  --end is is the last time we want data for. The default is now.
62
55
  This is eval'ed by Ruby, so you can use a Ruby expression, which must return a Time object.
63
56
 
57
+ --duration is time period before --end, in seconds. The default is 86400 sec (1 day).
58
+ TWS imposes limit of 86400 sec (1 day) worth of historic data per request.
64
59
 
65
- --duration is how much historic data we want, in seconds, before --end's time.
66
- The default is 86400 (seconds, which is 1 day.)
67
- The TWS-imposed limit is 86400 (1 day per request.) Requests for more than 86400 seconds worth of historic data will fail.
68
-
69
- --what determines what the data will be comprised of. This can be "trades", "midpoint", "bid", or "asked".
70
- The default is "trades".
60
+ --what determines what the data will be comprised of. This can be
61
+ "trades", "midpoint", "bid", or "ask". The default is "trades".
71
62
 
72
63
  --barsize determines how long each bar will be.
73
64
 
74
- Possible values (from the IB documentation):
75
-
76
- 1 = 1 sec
77
- 2 = 5 sec
78
- 3 = 15 sec
79
- 4 = 30 sec
80
- 5 = 1 minute
81
- 6 = 2 minutes
82
- 7 = 5 minutes
83
- 8 = 15 minutes
84
- 9 = 30 minutes
85
- 10 = 1 hour
86
- 11 = 1 day
65
+ Possible bar values (from the IB documentation):
66
+ Values less than 30 sec do not appear to work for some securities.
87
67
 
88
- Values less than 4 do not appear to work for some securities.
89
- The default is 8, 15 minutes.
68
+ sec1 = 1 sec
69
+ sec5 = 5 sec
70
+ sec15 = 15 sec
71
+ sec30 = 30 sec
72
+ min1 = 1 minute
73
+ min2 = 2 minutes
74
+ min5 = 5 minutes
75
+ min15 = 15 minutes (default)
76
+ min30 = 30 minutes
77
+ hour1 = 1 hour
78
+ day1 = 1 day
90
79
 
91
- --nonregularhours :
92
- Normally, only data from the instrument's regular trading hours is returned.
93
- If --nonregularhours is given, all data available during the time
94
- span requested is returned, even data bars covering time
95
- intervals where the market in question was illiquid. If
80
+ --nonregularhours : Normally, only data from the instrument's regular trading
81
+ hours is returned. If --nonregularhours is given, all data available during the time
82
+ span requested is returned, even for time intervals when the market was illiquid.
96
83
 
84
+ --dateformat : 1 (default) human-readable time/date format, like "20050307 11:32:16".
85
+ If you set it to 2 instead, you will get UNIX epoch offsets (seconds since Jan 1, 1970).
97
86
 
98
- --dateformat : a --dateformat of 1 will cause the dates in the returned
99
- messages with the historic data to be in a text format, like
100
- "20050307 11:32:16". If you set it to 2 instead, you
101
- will get an offset in seconds from the beginning of 1970, which
102
- is the same format as the UNIX epoch time.
87
+ --csv : print out the historic data in CSV format, with header.
103
88
 
104
- The default is 1 (human-readable time.)
105
-
106
- --header : if present, prints a 1 line CSV header describing the fields in the CSV.
107
-
108
- Otherwise, in the default mode, prints only the historic data (and any errors),
109
- and prints the data in CSV format.
89
+ --port : 4001 for Gateway (default), 7496 for TWS, or your custom port
110
90
 
111
91
  ENDHELP
112
-
113
92
  exit
114
-
115
93
  end
116
94
 
117
95
  ### Parameters
118
96
 
119
97
  # DURATION is how much historic data we want, in seconds, before END_DATE_TIME.
120
- # (The 'duration' gem gives us methods like #hour on integers.)
121
- DURATION = (opt["duration"] && opt["duration"].to_i) || 1.day
98
+ DURATION = (opt["duration"] && opt["duration"].to_i) || 86400
122
99
 
123
100
  if DURATION > 86400
124
- STDERR.puts("\nTWS does not accept a --duration longer than 86400 seconds (1 day.) Please try again with a smaller duration.\n\n")
101
+ STDERR.puts("\nTWS rejects --duration longer than 86400 seconds (1 day).\n")
125
102
  exit(1)
126
103
  end
127
104
 
@@ -131,22 +108,10 @@ END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib
131
108
  # This can be :trades, :midpoint, :bid, or :asked
132
109
  WHAT = (opt["what"] && opt["what"].to_sym) || :trades
133
110
 
134
- # Possible bar size values:
135
- # 1 = 1 sec
136
- # 2 = 5 sec
137
- # 3 = 15 sec
138
- # 4 = 30 sec
139
- # 5 = 1 minute
140
- # 6 = 2 minutes
141
- # 7 = 5 minutes
142
- # 8 = 15 minutes
143
- # 9 = 30 minutes
144
- # 10 = 1 hour
145
- # 11 = 1 day
146
- #
111
+
147
112
  # Values less than 4 do not appear to actually work; they are rejected by the server.
148
113
  #
149
- BAR_SIZE = (opt["barsize"] && opt["barsize"].to_i) || 8
114
+ BAR_SIZE = (opt["barsize"] && opt["barsize"].to_sym) || :min15
150
115
 
151
116
  # If REGULAR_HOURS_ONLY is set to 0, all data available during the time
152
117
  # span requested is returned, even data bars covering time
@@ -165,25 +130,16 @@ REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1
165
130
 
166
131
  DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1
167
132
 
168
- #
169
- # Definition of what we want market data for. We have to keep track
170
- # of what ticker id corresponds to what symbol ourselves, because the
171
- # ticks don't include any other identifying information.
172
- #
173
- # The choice of ticker ids is, as far as I can tell, arbitrary.
174
- #
175
- # Note that as of 4/07 there is no historical data available for forex spot.
176
- #
177
- @market = {123 => opt["security"]}
133
+ PORT = (opt["port"] && opt["port"]) || '4001'
178
134
 
179
135
  # First, connect to IB TWS.
180
- ib = IB::Connection.new
136
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
181
137
 
182
- puts "datetime,open,high,low,close,volume,wap,has_gaps" if !opt["header"].nil?
138
+ # Subscribe to TWS alerts/errors
139
+ ib.subscribe(:Alert) { |msg| puts msg.to_human }
183
140
 
184
141
  lastMessageTime = Queue.new # for communicating with the reader thread.
185
142
 
186
- #
187
143
  # Subscribe to incoming HistoricalData events. The code passed in the
188
144
  # block will be executed when a message of the subscribed type is
189
145
  # received, with the received message as its argument. In this case,
@@ -194,15 +150,15 @@ lastMessageTime = Queue.new # for communicating with the reader thread.
194
150
  # The incoming message packet from TWS just identifies it by ticker id.
195
151
  #
196
152
  ib.subscribe(:HistoricalData) do |msg|
197
- STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s("F") + " items:" if VERBOSE
198
-
199
- msg.data[:results].each do |datum|
200
- if VERBOSE
201
- puts datum.to_s
202
- else
203
- puts "#{datum.date},#{datum.open},#{datum.high},#{datum.low}," +
153
+ if opt["csv"]
154
+ puts "date,time,open,high,low,close,volume,wap,has_gaps"
155
+ msg.results.each do |datum|
156
+ puts "#{datum.time},#{datum.open},#{datum.high},#{datum.low}," +
204
157
  "#{datum.close},#{datum.volume},#{datum.wap},#{datum.has_gaps}"
205
158
  end
159
+ else
160
+ STDERR.puts "Received #{msg.count} items:"
161
+ msg.results.each { |datum| puts datum.to_s }
206
162
  end
207
163
  lastMessageTime.push(Time.now)
208
164
  end
@@ -210,18 +166,15 @@ end
210
166
  # Now we actually request historical data for the symbols we're
211
167
  # interested in. TWS will respond with a HistoricalData message,
212
168
  # which will be received by the code above.
213
- @market.each_pair { |id, contract|
214
- ib.send_message :RequestHistoricalData,
215
- :id => id,
216
- :contract => contract,
217
- :end_date_time => END_DATE_TIME,
218
- :duration => DURATION, # seconds == 1 hour
219
- :bar_size => BAR_SIZE, # 1 minute bars
220
- :what_to_show => WHAT,
221
- :use_RTH => REGULAR_HOURS_ONLY,
222
- :format_date => DATE_FORMAT
223
-
224
- }
169
+ ib.send_message :RequestHistoricalData,
170
+ :request_id => 123,
171
+ :contract => opt["security"],
172
+ :end_date_time => END_DATE_TIME,
173
+ :duration => DURATION, # seconds == 1 hour
174
+ :bar_size => BAR_SIZE, # 1 minute bars
175
+ :what_to_show => WHAT,
176
+ :use_RTH => REGULAR_HOURS_ONLY,
177
+ :format_date => DATE_FORMAT
225
178
 
226
179
  # A complication here is that IB does not send any indication when all historic data is done being delivered.
227
180
  # So we have to guess - when there is no more new data for some period, we interpret that as "end of data" and exit.
data/bin/list_orders CHANGED
@@ -12,12 +12,12 @@ require 'bundler/setup'
12
12
  require 'ib-ruby'
13
13
 
14
14
  # First, connect to IB TWS.
15
- ib = IB::Connection.new :client_id => 0
15
+ ib = IB::Connection.new :client_id => 0 # All Orders, including TWS-generated ones
16
16
 
17
17
  # Subscribe to TWS alerts/errors and order-related messages
18
18
  @counter = 0
19
19
 
20
- ib.subscribe(:Alert, :OrderStatus) { |msg| puts msg.to_human }
20
+ ib.subscribe(:Alert, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.to_human }
21
21
 
22
22
  ib.subscribe(:OpenOrder) do |msg|
23
23
  @counter += 1
@@ -26,4 +26,5 @@ end
26
26
 
27
27
  ib.send_message :RequestAllOpenOrders
28
28
 
29
- sleep 2
29
+ # Wait for IB to respond to our request
30
+ ib.wait_for :OpenOrderEnd
data/bin/market_data CHANGED
@@ -18,7 +18,7 @@ require 'ib-ruby'
18
18
  789 => IB::Symbols::Forex[:usdcad]}
19
19
 
20
20
  # First, connect to IB TWS.
21
- ib = IB::Connection.new
21
+ ib = IB::Connection.new :client_id => 1112 # Arbitrary id to identify your script
22
22
 
23
23
  ## Subscribe to TWS alerts/errors
24
24
  ib.subscribe(:Alert) { |msg| puts msg.to_human }