ib-ruby 0.6.1 → 0.7.0

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.
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 }