ib-ruby 0.7.4 → 0.7.6

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 (93) hide show
  1. data/.gitignore +3 -0
  2. data/HISTORY +8 -0
  3. data/README.md +2 -2
  4. data/Rakefile +15 -0
  5. data/TODO +7 -2
  6. data/VERSION +1 -1
  7. data/bin/account_info +1 -1
  8. data/bin/cancel_orders +1 -1
  9. data/bin/contract_details +1 -1
  10. data/bin/depth_of_market +1 -1
  11. data/bin/fa_accounts +1 -1
  12. data/bin/fundamental_data +42 -0
  13. data/bin/historic_data +1 -1
  14. data/bin/historic_data_cli +1 -1
  15. data/bin/list_orders +1 -2
  16. data/bin/market_data +1 -1
  17. data/bin/option_data +1 -1
  18. data/bin/place_combo_order +1 -1
  19. data/bin/place_order +1 -1
  20. data/bin/template +1 -4
  21. data/bin/tick_data +2 -2
  22. data/bin/time_and_sales +1 -1
  23. data/lib/ib-ruby.rb +4 -0
  24. data/lib/ib-ruby/connection.rb +50 -34
  25. data/lib/ib-ruby/constants.rb +232 -37
  26. data/lib/ib-ruby/db.rb +25 -0
  27. data/lib/ib-ruby/extensions.rb +51 -1
  28. data/lib/ib-ruby/messages/abstract_message.rb +0 -8
  29. data/lib/ib-ruby/messages/incoming.rb +18 -493
  30. data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
  31. data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
  32. data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
  33. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
  34. data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
  35. data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
  36. data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
  37. data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
  38. data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
  39. data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
  40. data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
  41. data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
  42. data/lib/ib-ruby/messages/outgoing.rb +25 -223
  43. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
  44. data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
  45. data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
  46. data/lib/ib-ruby/models.rb +4 -0
  47. data/lib/ib-ruby/models/bar.rb +31 -14
  48. data/lib/ib-ruby/models/combo_leg.rb +48 -23
  49. data/lib/ib-ruby/models/contracts.rb +2 -2
  50. data/lib/ib-ruby/models/contracts/bag.rb +11 -7
  51. data/lib/ib-ruby/models/contracts/contract.rb +90 -66
  52. data/lib/ib-ruby/models/contracts/option.rb +16 -7
  53. data/lib/ib-ruby/models/execution.rb +34 -18
  54. data/lib/ib-ruby/models/model.rb +15 -7
  55. data/lib/ib-ruby/models/model_properties.rb +101 -44
  56. data/lib/ib-ruby/models/order.rb +176 -187
  57. data/lib/ib-ruby/models/order_state.rb +99 -0
  58. data/lib/ib-ruby/symbols/forex.rb +10 -10
  59. data/lib/ib-ruby/symbols/futures.rb +6 -6
  60. data/lib/ib-ruby/symbols/stocks.rb +3 -3
  61. data/spec/account_helper.rb +4 -5
  62. data/spec/combo_helper.rb +4 -4
  63. data/spec/db.rb +18 -0
  64. data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
  65. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
  66. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
  67. data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
  68. data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
  69. data/spec/ib-ruby/models/bag_spec.rb +97 -0
  70. data/spec/ib-ruby/models/bar_spec.rb +45 -0
  71. data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
  72. data/spec/ib-ruby/models/contract_spec.rb +134 -170
  73. data/spec/ib-ruby/models/execution_spec.rb +35 -50
  74. data/spec/ib-ruby/models/option_spec.rb +127 -0
  75. data/spec/ib-ruby/models/order_spec.rb +89 -68
  76. data/spec/ib-ruby/models/order_state_spec.rb +55 -0
  77. data/spec/integration/contract_info_spec.rb +4 -6
  78. data/spec/integration/fundamental_data_spec.rb +41 -0
  79. data/spec/integration/historic_data_spec.rb +4 -4
  80. data/spec/integration/market_data_spec.rb +1 -3
  81. data/spec/integration/orders/attached_spec.rb +8 -10
  82. data/spec/integration/orders/combo_spec.rb +2 -2
  83. data/spec/integration/orders/execution_spec.rb +0 -1
  84. data/spec/integration/orders/placement_spec.rb +1 -3
  85. data/spec/integration/orders/valid_ids_spec.rb +1 -2
  86. data/spec/message_helper.rb +1 -1
  87. data/spec/model_helper.rb +211 -0
  88. data/spec/order_helper.rb +44 -37
  89. data/spec/spec_helper.rb +36 -23
  90. data/spec/v.rb +7 -0
  91. data/tasks/doc.rake +1 -1
  92. metadata +116 -12
  93. data/spec/integration/orders/open_order +0 -98
data/.gitignore CHANGED
@@ -24,12 +24,15 @@ coverage
24
24
  config
25
25
  data
26
26
  rdoc
27
+ doc
27
28
  pkg
28
29
  log
29
30
  tmp
31
+ #Gemfile.lock
30
32
 
31
33
  ## PROJECT::SPECIFIC
32
34
  *.db
35
+ *.sqlite3
33
36
 
34
37
  # Yard cache files - used for the autogenerated documentation
35
38
  .yardoc
data/HISTORY CHANGED
@@ -153,3 +153,11 @@
153
153
  == 0.7.4 / 2012-04-04
154
154
 
155
155
  * Order equality bug fixed
156
+
157
+ == 0.7.5 / 2012-04-16
158
+
159
+ * Optional DB backend for Models /ActiveRecord/
160
+
161
+ == 0.7.6 / 2012-04-18
162
+
163
+ * Bugfix for FundamentalData
data/README.md CHANGED
@@ -93,9 +93,9 @@ lines of code - and without sacrificing code readability or flexibility.
93
93
  ib.subscribe(:OpenOrder) { |msg| puts "Placed: #{msg.order}!" }
94
94
  ib.subscribe(:ExecutionData) { |msg| puts "Filled: #{msg.execution}!" }
95
95
  contract = IB::Contract.new :symbol => 'WFC', :exchange => 'NYSE',
96
- :currency => 'USD', :sec_type => 'STK'
96
+ :currency => 'USD', :sec_type => :stock
97
97
  buy_order = IB::Order.new :total_quantity => 100, :limit_price => 21.00,
98
- :action => 'BUY', :order_type => 'LMT'
98
+ :action => :buy, :order_type => :limit
99
99
  ib.place_order buy_order, contract
100
100
  ib.wait_for :ExecutionData
101
101
 
data/Rakefile CHANGED
@@ -23,3 +23,18 @@ CLASS_NAME = IB
23
23
  Dir['tasks/*.rake'].sort.each { |file| load file }
24
24
 
25
25
  # Project-specific tasks
26
+
27
+ ## Migrations
28
+
29
+ # rake db:new_migration name=FooBarMigration
30
+ # rake db:migrate
31
+ # rake db:migrate VERSION=20081220234130
32
+ # rake db:migrate:up VERSION=20081220234130
33
+ # rake db:migrate DB=osx-test
34
+ # rake db:rollback
35
+ # rake db:rollback STEP=3
36
+ begin
37
+ require 'tasks/standalone_migrations'
38
+ rescue LoadError => e
39
+ puts "gem install standalone_migrations to get db:migrate:* tasks! (Error: #{e})"
40
+ end
data/TODO CHANGED
@@ -1,6 +1,8 @@
1
1
  Plan:
2
2
 
3
- 1. Detailed message documentation
3
+ 0. Extract OrderState/UnderComp objects, for better record keeping
4
+
5
+ 1. Add ActiveRecord backend to all Models
4
6
 
5
7
  2. Make ActiveModel-like attributes Hash for easy attributes updating
6
8
 
@@ -14,10 +16,11 @@ http://finance.groups.yahoo.com/group/TWSAPI/message/25413
14
16
 
15
17
  6. @received_at timestamp in messages
16
18
 
17
- 7. Create integration tests for more use cases (spec/README)
19
+ 7. Detailed message documentation
18
20
 
19
21
  8. Move Float values to Decimal (roundoff errors showed in spec!)
20
22
 
23
+
21
24
  Done:
22
25
 
23
26
  1. Create integration tests for basic use cases
@@ -38,3 +41,5 @@ Ideas for future:
38
41
 
39
42
  2. Tweak IB::Message API for speed (use class methods)
40
43
 
44
+ 3. Create integration tests for more use cases (spec/README)
45
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.4
1
+ 0.7.6
data/bin/account_info CHANGED
@@ -4,8 +4,8 @@
4
4
  # messages received from IB (update every 3 minute or so)
5
5
 
6
6
  require 'rubygems'
7
- require 'pathname'
8
7
  require 'bundler/setup'
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
9
  require 'ib-ruby'
10
10
 
11
11
  # First, connect to IB TWS.
data/bin/cancel_orders CHANGED
@@ -5,8 +5,8 @@
5
5
  # robot goes crazy and opens gazillions of wrong limit orders.
6
6
 
7
7
  require 'rubygems'
8
- require 'pathname'
9
8
  require 'bundler/setup'
9
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
10
10
  require 'ib-ruby'
11
11
 
12
12
  # First, connect to IB TWS.
data/bin/contract_details CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script gets details for specific contract from IB
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # Definition of what we want market data for. We have to keep track of what ticker id
data/bin/depth_of_market CHANGED
@@ -4,8 +4,8 @@
4
4
  # specific symbols
5
5
 
6
6
  require 'rubygems'
7
- require 'pathname'
8
7
  require 'bundler/setup'
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
9
  require 'ib-ruby'
10
10
 
11
11
  # Definition of what we want L2 data for. We have to keep track of what ticker id
data/bin/fa_accounts CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script receives Financial Adviser and Managed Accounts info
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # First, connect to IB TWS.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script downloads Fundamental data for specific symbols from IB
4
+ # This only works IF you have Reuters data subscription!
5
+
6
+ require 'rubygems'
7
+ require 'bundler/setup'
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
+
10
+ require 'ib-ruby'
11
+ require 'xmlsimple'
12
+ require 'pp'
13
+
14
+
15
+ ib = IB::Connection.new :client_id => 1112 #, :port => 7496 # TWS
16
+
17
+ ib.subscribe(:Alert) { |msg| puts msg.to_human }
18
+
19
+ # Fundamental Data will arrive as XML. Need to parse it
20
+ ib.subscribe(:FundamentalData) do |msg|
21
+ puts 'Got fundamental data.'
22
+ @xml = XmlSimple.xml_in(msg.data)
23
+ pp @xml
24
+ @parsing_finished = true
25
+ end
26
+
27
+ ibm = IB::Contract.new :symbol => 'IBM',
28
+ :exchange => 'NYSE',
29
+ :currency => 'USD',
30
+ :sec_type => 'STK'
31
+
32
+ # Request Fundamental Data for IBM. Possible report types:
33
+ # 'estimates' - Estimates
34
+ # 'finstat' - Financial statements
35
+ # 'snapshot' - Summary
36
+ ib.send_message :RequestFundamentalData,
37
+ :id => 10,
38
+ :contract => ibm,
39
+ :report_type => 'snapshot'
40
+
41
+ # Needs some time to receive, parse and print XML. Standard timeout of 1 sec is too low.
42
+ ib.wait_for(40) { @parsing_finished }
data/bin/historic_data CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script downloads historic data for specific symbols from IB
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # Definition of what we want data for. We have to keep track of what ticker id
@@ -3,9 +3,9 @@
3
3
 
4
4
  require 'rubygems'
5
5
  require 'time'
6
- require 'pathname'
7
6
  require 'getopt/long'
8
7
  require 'bundler/setup'
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
9
  require 'ib-ruby'
10
10
 
11
11
  include Getopt
data/bin/list_orders CHANGED
@@ -2,10 +2,9 @@
2
2
  #
3
3
  # This script retrieves list of all Orders from TWS
4
4
 
5
- require 'pathname'
6
5
  require 'rubygems'
7
6
  require 'bundler/setup'
8
- require 'pp'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
8
  require 'ib-ruby'
10
9
 
11
10
  # Connect to IB as 0 (TWS) to retrieve all Orders, including TWS-generated ones
data/bin/market_data CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script connects to IB API and subscribes to market data for specific symbols
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # Definition of what we want market data for. We have to keep track of what ticker id
data/bin/option_data CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script subscribes to market data for a list of Options
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # Definition of what we want market data for. We have to keep track of what ticker id
@@ -3,8 +3,8 @@
3
3
  # This script places GOOG option butterfly combo order
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # Utility method that helps us build multi-legged (BAG) Orders
data/bin/place_order CHANGED
@@ -3,8 +3,8 @@
3
3
  # This script places WFC buy order for 100 lots
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  # First, connect to IB TWS. Arbitrary :client_id is used to identify your script
data/bin/template CHANGED
@@ -2,12 +2,9 @@
2
2
  #
3
3
  # Your script description here...
4
4
 
5
- require 'pathname'
6
- LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
7
- $LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
8
-
9
5
  require 'rubygems'
10
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
11
8
  require 'ib-ruby'
12
9
 
13
10
  # First, connect to IB TWS. Arbitrary :client_id is used to identify your script
data/bin/tick_data CHANGED
@@ -3,14 +3,14 @@
3
3
  # This script reproduces https://github.com/ib-ruby/ib-ruby/issues/12
4
4
 
5
5
  require 'rubygems'
6
- require 'pathname'
7
6
  require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
8
8
  require 'ib-ruby'
9
9
 
10
10
  contract = IB::Contract.new :symbol=> 'AAPL',
11
11
  :exchange=> "Smart",
12
12
  :currency=> "USD",
13
- :sec_type=> IB::SECURITY_TYPES[:stock],
13
+ :sec_type=> :stock,
14
14
  :description=> "Some stock"
15
15
 
16
16
  # First, connect to IB TWS. Arbitrary :client_id is used to identify your script
data/bin/time_and_sales CHANGED
@@ -4,8 +4,8 @@
4
4
  # It then prints out all trades that exceed certain size.
5
5
 
6
6
  require 'rubygems'
7
- require 'pathname'
8
7
  require 'bundler/setup'
8
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
9
9
  require 'ib-ruby'
10
10
 
11
11
  # Define the symbols we're interested in.
data/lib/ib-ruby.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  module IB
2
+ # IB Models can be either database-backed, or not
3
+ # By default there is no DB backend, unless specifically requested
4
+ # require 'ib-ruby/db' # to make all IB models database-backed
5
+ DB ||= false
2
6
 
3
7
  require 'ib-ruby/version'
4
8
  require 'ib-ruby/extensions'
@@ -33,6 +33,10 @@ module IB
33
33
  def initialize opts = {}
34
34
  @options = DEFAULT_OPTIONS.merge(opts)
35
35
 
36
+ # A couple of locks to avoid race conditions in JRuby
37
+ @subscribe_lock = Mutex.new
38
+ @receive_lock = Mutex.new
39
+
36
40
  self.default_logger = options[:logger] if options[:logger]
37
41
  @connected = false
38
42
  @next_order_id = nil
@@ -88,7 +92,7 @@ module IB
88
92
  end
89
93
  if connected?
90
94
  socket.close
91
- server = Hash.new
95
+ @server = Hash.new
92
96
  @connected = false
93
97
  end
94
98
  end
@@ -109,40 +113,44 @@ module IB
109
113
  # Listener will be called later with received message instance as its argument.
110
114
  # Returns subscriber id to allow unsubscribing
111
115
  def subscribe *args, &block
112
- subscriber = args.last.respond_to?(:call) ? args.pop : block
113
- id = random_id
114
-
115
- error "Need subscriber proc or block", :args unless subscriber.is_a? Proc
116
-
117
- args.each do |what|
118
- message_classes =
119
- case
120
- when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
121
- [what]
122
- when what.is_a?(Symbol)
123
- [Messages::Incoming.const_get(what)]
124
- when what.is_a?(Regexp)
125
- Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
126
- else
127
- error "#{what} must represent incoming IB message class", :args
128
- end
129
- message_classes.flatten.each do |message_class|
130
- # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
131
- subscribers[message_class][id] = subscriber
116
+ @subscribe_lock.synchronize do
117
+ subscriber = args.last.respond_to?(:call) ? args.pop : block
118
+ id = random_id
119
+
120
+ error "Need subscriber proc or block", :args unless subscriber.is_a? Proc
121
+
122
+ args.each do |what|
123
+ message_classes =
124
+ case
125
+ when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
126
+ [what]
127
+ when what.is_a?(Symbol)
128
+ [Messages::Incoming.const_get(what)]
129
+ when what.is_a?(Regexp)
130
+ Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
131
+ else
132
+ error "#{what} must represent incoming IB message class", :args
133
+ end
134
+ message_classes.flatten.each do |message_class|
135
+ # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
136
+ subscribers[message_class][id] = subscriber
137
+ end
132
138
  end
139
+ id
133
140
  end
134
- id
135
141
  end
136
142
 
137
143
  # Remove all subscribers with specific subscriber id (TODO: multiple ids)
138
144
  def unsubscribe *ids
139
- removed = []
140
- ids.each do |id|
141
- removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
142
- error "No subscribers with id #{id}" if removed_at_id.empty?
143
- removed << removed_at_id
145
+ @subscribe_lock.synchronize do
146
+ removed = []
147
+ ids.each do |id|
148
+ removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
149
+ error "No subscribers with id #{id}" if removed_at_id.empty?
150
+ removed << removed_at_id
151
+ end
152
+ removed.flatten
144
153
  end
145
- removed.flatten
146
154
  end
147
155
 
148
156
  # Message subscribers. Key is the message class to listen for.
@@ -157,10 +165,12 @@ module IB
157
165
 
158
166
  # Clear received messages Hash
159
167
  def clear_received *message_types
160
- if message_types.empty?
161
- received.each { |message_type, container| container.clear }
162
- else
163
- message_types.each { |message_type| received[message_type].clear }
168
+ @receive_lock.synchronize do
169
+ if message_types.empty?
170
+ received.each { |message_type, container| container.clear }
171
+ else
172
+ message_types.each { |message_type| received[message_type].clear }
173
+ end
164
174
  end
165
175
  end
166
176
 
@@ -243,14 +253,19 @@ module IB
243
253
  # Create new instance of the appropriate message type,
244
254
  # and have it read the message from server.
245
255
  # NB: Failure here usually means unsupported message type received
256
+ error "Got unsupported message #{msg_id}" unless Messages::Incoming::Classes[msg_id]
246
257
  msg = Messages::Incoming::Classes[msg_id].new(server)
247
258
 
248
259
  # Deliver message to all registered subscribers, alert if no subscribers
249
- subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
260
+ @subscribe_lock.synchronize do
261
+ subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
262
+ end
250
263
  log.warn "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
251
264
 
252
265
  # Collect all received messages into a @received Hash
253
- received[msg.message_type] << msg if options[:received]
266
+ @receive_lock.synchronize do
267
+ received[msg.message_type] << msg if options[:received]
268
+ end
254
269
  end
255
270
 
256
271
  ### Sending Outgoing messages to IB
@@ -277,6 +292,7 @@ module IB
277
292
  # Place Order (convenience wrapper for send_message :PlaceOrder).
278
293
  # Assigns client_id and order_id fields to placed order. Returns assigned order_id.
279
294
  def place_order order, contract
295
+ error "Unable to place order, next_order_id not known" unless @next_order_id
280
296
  order.client_id = server[:client_id]
281
297
  order.order_id = @next_order_id
282
298
  @next_order_id += 1