ib-ruby 0.7.4 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
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