ib-ruby 0.4.3 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +32 -0
  2. data/HISTORY +68 -0
  3. data/README.rdoc +9 -6
  4. data/VERSION +1 -1
  5. data/bin/account_info +29 -0
  6. data/bin/contract_details +37 -0
  7. data/bin/depth_of_market +43 -0
  8. data/bin/historic_data +62 -0
  9. data/bin/{RequestHistoricData → historic_data_cli} +46 -91
  10. data/bin/market_data +49 -0
  11. data/bin/option_data +45 -0
  12. data/bin/template +21 -0
  13. data/bin/time_and_sales +63 -0
  14. data/lib/ib-ruby/connection.rb +166 -0
  15. data/lib/ib-ruby/constants.rb +91 -0
  16. data/lib/ib-ruby/messages/incoming.rb +807 -0
  17. data/lib/ib-ruby/messages/outgoing.rb +573 -0
  18. data/lib/ib-ruby/messages.rb +8 -1445
  19. data/lib/ib-ruby/models/bar.rb +26 -0
  20. data/lib/ib-ruby/models/contract.rb +335 -0
  21. data/lib/ib-ruby/models/execution.rb +55 -0
  22. data/lib/ib-ruby/models/model.rb +20 -0
  23. data/lib/ib-ruby/models/order.rb +262 -0
  24. data/lib/ib-ruby/models.rb +11 -0
  25. data/lib/ib-ruby/socket.rb +50 -0
  26. data/lib/ib-ruby/symbols/forex.rb +32 -72
  27. data/lib/ib-ruby/symbols/futures.rb +47 -68
  28. data/lib/ib-ruby/symbols/options.rb +30 -0
  29. data/lib/ib-ruby/symbols/stocks.rb +23 -0
  30. data/lib/ib-ruby/symbols.rb +9 -0
  31. data/lib/ib-ruby.rb +7 -8
  32. data/lib/legacy/bin/account_info_old +36 -0
  33. data/lib/legacy/bin/historic_data_old +81 -0
  34. data/lib/legacy/bin/market_data_old +68 -0
  35. data/lib/legacy/datatypes.rb +485 -0
  36. data/lib/legacy/ib-ruby.rb +10 -0
  37. data/lib/legacy/ib.rb +226 -0
  38. data/lib/legacy/messages.rb +1458 -0
  39. data/lib/version.rb +2 -3
  40. data/spec/ib-ruby/models/contract_spec.rb +261 -0
  41. data/spec/ib-ruby/models/order_spec.rb +64 -0
  42. data/spec/ib-ruby_spec.rb +0 -131
  43. metadata +106 -76
  44. data/bin/AccountInfo +0 -67
  45. data/bin/HistoricToCSV +0 -111
  46. data/bin/RequestMarketData +0 -78
  47. data/bin/SimpleTimeAndSales +0 -98
  48. data/bin/ib-ruby +0 -8
  49. data/lib/ib-ruby/datatypes.rb +0 -400
  50. data/lib/ib-ruby/ib.rb +0 -242
@@ -1,400 +0,0 @@
1
- #
2
- # Copyright (C) 2006 Blue Voodoo Magic LLC.
3
- #
4
- # This library is free software; you can redistribute it and/or modify
5
- # it under the terms of the GNU Lesser General Public License as
6
- # published by the Free Software Foundation; either version 2.1 of the
7
- # License, or (at your option) any later version.
8
- #
9
- # This library is distributed in the hope that it will be useful, but
10
- # WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
- # Lesser General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Lesser General Public
15
- # License along with this library; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
- # 02110-1301 USA
18
- #
19
-
20
- #
21
- # TODO: Implement equals() according to the criteria in IB's Java client.
22
- #
23
-
24
- module IB
25
-
26
- module Datatypes
27
- attr_reader :created_at
28
-
29
- class AbstractDatum
30
- def init
31
- @created_at = Time.now
32
- end
33
-
34
- # If a hash is given, keys are taken as attribute names, values as data.
35
- # The attrs of the instance are set automatically from the attributeHash.
36
- #
37
- # If no hash is given, #init is called in the instance. #init
38
- # should set the datum up in a generic state.
39
- #
40
- def initialize(attributeHash=nil)
41
- if attributeHash.nil?
42
- init
43
- else
44
- raise(ArgumentError.new("Argument must be a Hash")) unless attributeHash.is_a?(Hash)
45
- attributeHash.keys.each {|key|
46
- self.send((key.to_s + "=").to_sym, attributeHash[key])
47
- }
48
- end
49
- end
50
- end # AbstractDatum
51
-
52
-
53
- # This is used within HistoricData messages.
54
- # Instantiate with a Hash of attributes, to be auto-set via initialize in AbstractDatum.
55
- class Bar < AbstractDatum
56
- attr_accessor :date, :open, :high, :low, :close, :volume, :wap, :has_gaps
57
-
58
- def to_s
59
- "<Bar: #{@date}; OHLC: #{@open.to_s}, #{@high.to_s}, #{@low.to_s}, #{@close.to_s}; volume: #{@volume}; wap: #{@wap.to_s}; has_gaps: #{@has_gaps}>"
60
- end
61
-
62
- end # Bar
63
-
64
-
65
- class Order < AbstractDatum
66
- # Constants used in Order objects. Drawn from Order.java
67
- Origin_Customer = 0
68
- Origin_Firm = 1
69
-
70
- Opt_Unknown = '?'
71
- Opt_Broker_Dealer = 'b'
72
- Opt_Customer = 'c'
73
- Opt_Firm = 'f'
74
- Opt_Isemm = 'm'
75
- Opt_Farmm = 'n'
76
- Opt_Specialist = 'y'
77
-
78
- # Main order fields
79
- attr_accessor(:id, :client_id, :perm_id, :action, :total_quantity, :order_type, :limit_price,
80
- :aux_price, :shares_allocation)
81
-
82
- # Extended order fields
83
- attr_accessor(:tif, :oca_group, :account, :open_close, :origin, :order_ref,
84
- :transmit, # if false, order will be created but not transmitted.
85
- :parent_id, # Parent order id, to associate auto STP or TRAIL orders with the original order.
86
- :block_order,
87
- :sweep_to_fill,
88
- :display_size,
89
- :trigger_method,
90
- :ignore_rth,
91
- :hidden,
92
- :discretionary_amount,
93
- :good_after_time,
94
- :good_till_date)
95
-
96
- OCA_Cancel_with_block = 1
97
- OCA_Reduce_with_block = 2
98
- OCA_Reduce_non_block = 3
99
-
100
- # No idea what the fa_* attributes are for, nor many of the others.
101
- attr_accessor(:fa_group, :fa_profile, :fa_method, :fa_profile, :fa_method, :fa_percentage, :primary_exchange,
102
- :short_sale_slot, # 1 or 2, says Order.java. (No idea what the difference is.)
103
- :designated_location, # "when slot=2 only"
104
- :oca_type, # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK
105
- :rth_only, :override_percentage_constraints, :rule_80a, :settling_firm, :all_or_none,
106
- :min_quantity, :percent_offset, :etrade_only, :firm_quote_only, :nbbo_price_cap)
107
-
108
- # Box orders only:
109
- Box_Auction_Match = 1
110
- Box_Auction_Improvement = 2
111
- Box_Auction_Transparent = 3
112
- attr_accessor(:auction_strategy, # Box_* constants above
113
- :starting_price, :stock_ref_price, :delta, :stock_range_lower, :stock_range_upper)
114
-
115
- # Volatility orders only:
116
- Volatility_Type_Daily = 1
117
- Volatility_Type_Annual = 2
118
-
119
- Volatility_Ref_Price_Average = 1
120
- Volatility_Ref_Price_BidOrAsk = 2
121
-
122
- attr_accessor(:volatility,
123
- :volatility_type, # 1 = daily, 2 = annual, as above
124
- :continuous_update,
125
- :reference_price_type, # 1 = average, 2 = BidOrAsk
126
- :delta_neutral_order_type,
127
- :delta_neutral_aux_price)
128
-
129
- Max_value = 99999999 # I don't know why IB uses a very large number as the default for certain fields
130
- def init
131
- super
132
-
133
- @open_close = "0"
134
- @origin = Origin_Customer
135
- @transmit = true
136
- @primary_exchange = ''
137
- @designated_location = ''
138
- @min_quantity = Max_value
139
- @percent_offset = Max_value
140
- @nbba_price_cap = Max_value
141
- @starting_price = Max_value
142
- @stock_ref_price = Max_value
143
- @delta = Max_value
144
- @delta_neutral_order_type = ''
145
- @delta_neutral_aux_price = Max_value
146
- @reference_price_type = Max_value
147
- end # init
148
-
149
- end # class Order
150
-
151
-
152
- class Contract < AbstractDatum
153
-
154
- # Valid security types (sec_type attribute)
155
- SECURITY_TYPES =
156
- {
157
- :stock => "STK",
158
- :option => "OPT",
159
- :future => "FUT",
160
- :index => "IND",
161
- :futures_option => "FOP",
162
- :forex => "CASH",
163
- :bag => "BAG"
164
- }
165
-
166
- # note that the :description field is entirely local to ib-ruby, and not part of TWS.
167
- # You can use it to store whatever arbitrary data you want.
168
-
169
- attr_accessor(:symbol, :strike, :multiplier, :exchange, :currency,
170
- :local_symbol, :combo_legs, :description)
171
-
172
- # Bond values
173
- attr_accessor(:cusip, :ratings, :desc_append, :bond_type, :coupon_type, :callable, :puttable,
174
- :coupon, :convertible, :maturity, :issue_date)
175
-
176
- attr_reader :sec_type, :expiry, :right, :primary_exchange
177
-
178
-
179
-
180
- # some protective filters
181
-
182
- def primary_exchange=(x)
183
- x.upcase! if x.is_a?(String)
184
-
185
- # per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
186
- raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
187
-
188
- @primary_exchange = x
189
- end
190
-
191
- def right=(x)
192
- x.upcase! if x.is_a?(String)
193
- x = nil if !x.nil? && x.empty?
194
- raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || [ "PUT", "CALL", "P", "C", "0"].include?(x)
195
- @right = x
196
- end
197
-
198
- def expiry=(x)
199
- x = x.to_s
200
- if (x.nil? || ! (x =~ /\d{6,8}/)) and !x.empty? then
201
- raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
202
- end
203
- @expiry = x
204
- end
205
-
206
- def sec_type=(x)
207
- x = nil if !x.nil? && x.empty?
208
- raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
209
- @sec_type = x
210
- end
211
-
212
- def reset
213
- @combo_legs = Array.new
214
- @strike = 0
215
- end
216
-
217
- # Different messages serialize contracts differently. Go figure.
218
- def serialize_short(version)
219
- q = [ self.symbol,
220
- self.sec_type,
221
- self.expiry,
222
- self.strike,
223
- self.right ]
224
-
225
- q.push(self.multiplier) if version >= 15
226
- q.concat([
227
- self.exchange,
228
- self.currency,
229
- self.local_symbol
230
- ])
231
-
232
- q
233
- end # serialize
234
-
235
- # This returns an Array of data from the given contract, in standard format.
236
- # Note that it does not include the combo legs.
237
- def serialize_long(version)
238
- queue = [
239
- self.symbol,
240
- self.sec_type,
241
- self.expiry,
242
- self.strike,
243
- self.right
244
- ]
245
-
246
- queue.push(self.multiplier) if version >= 15
247
- queue.push(self.exchange)
248
- queue.push(self.primary_exchange) if version >= 14
249
- queue.push(self.currency)
250
- queue.push(self.local_symbol) if version >= 2
251
-
252
- queue
253
- end # serialize_long
254
-
255
- #
256
- # This produces a string uniquely identifying this contract, in the format used
257
- # for command line arguments in the IB-Ruby examples. The format is:
258
- #
259
- # symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
260
- #
261
- # Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
262
- #
263
- # For example, to query the British pound futures contract trading on Globex expiring in September, 2008,
264
- # the string is:
265
- #
266
- # GBP:FUT:200809:::62500:GLOBEX::USD:
267
- #
268
-
269
- def serialize_ib_ruby(version)
270
- serialize_long(version).join(":")
271
- end
272
-
273
- # This returns a Contract initialized from the serialize_ib_ruby format string.
274
- def self.from_ib_ruby(string)
275
- c = Contract.new
276
- c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier, c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
277
-
278
- c
279
- end
280
-
281
- # Some messages send open_close too, some don't. WTF.
282
- def serialize_combo_legs(include_open_close = false)
283
- if self.combo_legs.nil?
284
- [0]
285
- else
286
- [ self.combo_legs.size ].concat(self.combo_legs.serialize(include_open_close))
287
- end
288
- end
289
-
290
- def init
291
- super
292
-
293
- @combo_legs = Array.new
294
- @strike = 0
295
- @sec_type = ''
296
- end
297
-
298
- def to_human
299
- "<IB-Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + "}>"
300
- end
301
-
302
- def to_short
303
- "#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
304
- end
305
-
306
- def to_s
307
- to_human
308
- end
309
-
310
- end # class Contract
311
-
312
-
313
- class ContractDetails < AbstractDatum
314
- attr_accessor :summary, :market_name, :trading_class, :con_id, :min_tick, :multiplier, :price_magnifier, :order_types, :valid_exchanges
315
-
316
- def init
317
- super
318
-
319
- @summary = Contract.new
320
- @con_id = 0
321
- @min_tick = 0
322
- end
323
- end # class ContractDetails
324
-
325
-
326
- class Execution < AbstractDatum
327
- attr_accessor :order_id, :client_id, :exec_id, :time, :account_number, :exchange, :side, :shares, :price, :perm_id, :liquidation
328
-
329
- def init
330
- super
331
-
332
- @order_id = 0
333
- @client_id = 0
334
- @shares = 0
335
- @price = 0
336
- @perm_id = 0
337
- @liquidation =0
338
- end
339
- end # Execution
340
-
341
- # EClientSocket.java tells us: 'Note that the valid format for m_time is "yyyymmdd-hh:mm:ss"'
342
- class ExecutionFilter < AbstractDatum
343
- attr_accessor :client_id, :acct_code, :time, :symbol, :sec_type, :exchange, :side
344
-
345
- def init
346
- super
347
-
348
- @client_id = 0
349
- end
350
-
351
- end # ExecutionFilter
352
-
353
-
354
- class ComboLeg < AbstractDatum
355
- attr_accessor :con_id, :ratio, :action, :exchange, :open_close
356
-
357
- def init
358
- super
359
-
360
- @con_id = 0
361
- @ratio = 0
362
- @open_close = 0
363
- end
364
-
365
- # Some messages include open_close, some don't. wtf.
366
- def serialize(include_open_close = false)
367
- self.collect { |leg|
368
- [ leg.con_id, leg.ratio, leg.action, leg.exchange, (include_open_close ? leg.open_close : [] )]
369
- }.flatten
370
- end
371
- end # ComboLeg
372
-
373
-
374
- class ScannerSubscription < AbstractDatum
375
- attr_accessor :number_of_rows, :instrument, :location_code, :scan_code, :above_price, :below_price,
376
- :above_volume, :average_option_volume_above, :market_cap_above, :market_cap_below, :moody_rating_above,
377
- :moody_rating_below, :sp_rating_above, :sp_rating_below, :maturity_date_above, :maturity_date_below,
378
- :coupon_rate_above, :coupon_rate_below, :exclude_convertible, :scanner_setting_pairs, :stock_type_filter
379
-
380
- def init
381
- super
382
-
383
- @coupon_rate_above = @coupon_rate_below = @market_cap_below = @market_cap_above = @average_option_volume_above =
384
- @above_volume = @below_price = @above_price = nil
385
- @number_of_rows = -1 # none specified, per ScannerSubscription.java
386
- end
387
- end # ScannerSubscription
388
-
389
-
390
- # Just like a Hash, but throws an exception if you try to access a key that doesn't exist.
391
- class StringentHash < Hash
392
- def initialize(hash)
393
- super() {|hash,key| raise Exception.new("key #{key.inspect} not found!") }
394
- self.merge!(hash) unless hash.nil?
395
- end
396
- end
397
-
398
- end # module Datatypes
399
-
400
- end # module
data/lib/ib-ruby/ib.rb DELETED
@@ -1,242 +0,0 @@
1
- #
2
- # Copyright (C) 2006 Blue Voodoo Magic LLC.
3
- #
4
- # This library is free software; you can redistribute it and/or modify
5
- # it under the terms of the GNU Lesser General Public License as
6
- # published by the Free Software Foundation; either version 2.1 of the
7
- # License, or (at your option) any later version.
8
- #
9
- # This library is distributed in the hope that it will be useful, but
10
- # WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
- # Lesser General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Lesser General Public
15
- # License along with this library; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
- # 02110-1301 USA
18
- #
19
-
20
- require 'sha1'
21
- require 'socket'
22
- require 'logger'
23
- require 'bigdecimal'
24
- require 'bigdecimal/util'
25
-
26
- # Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
27
- class Time
28
- def to_ib
29
- "#{self.year}#{sprintf("%02d", self.month)}#{sprintf("%02d", self.day)} " +
30
- "#{sprintf("%02d", self.hour)}:#{sprintf("%02d", self.min)}:#{sprintf("%02d", self.sec)}"
31
- end
32
- end # Time
33
-
34
-
35
- module IB
36
-
37
- TWS_IP_ADDRESS = "127.0.0.1"
38
- TWS_PORT = "7496"
39
-
40
- #logger = Logger.new(STDERR)
41
-
42
- class IBSocket < TCPSocket
43
-
44
- # send nice null terminated binary data
45
- def send(data)
46
- self.syswrite(data.to_s + "\0")
47
- end
48
-
49
- def read_string
50
- self.gets("\0").chop
51
- end
52
-
53
- def read_int
54
- self.read_string.to_i
55
- end
56
-
57
- def read_boolean
58
- self.read_string.to_i != 0
59
- end
60
-
61
- # Floating-point numbers shouldn't be used to store money.
62
- def read_decimal
63
- self.read_string.to_d
64
- end
65
-
66
- end # class IBSocket
67
-
68
-
69
-
70
- class IB
71
- Tws_client_version = 27
72
-
73
- attr_reader :next_order_id
74
-
75
- def initialize(options_in = {})
76
- @options = {
77
- :ip => TWS_IP_ADDRESS,
78
- :port => TWS_PORT,
79
- }.merge(options_in)
80
-
81
- @connected = false
82
- @next_order_id = nil
83
- @server = Hash.new # information about server and server connection state
84
-
85
- # Message listeners.
86
- # Key is the message class to listen for.
87
- # Value is an Array of Procs. The proc will be called with the populated message instance as its argument when
88
- # a message of that type is received.
89
- @listeners = Hash.new { |hash, key|
90
- hash[key] = Array.new
91
- }
92
-
93
-
94
- #logger.debug("IB#init: Initializing...")
95
-
96
- self.open(@options)
97
-
98
- end # init
99
-
100
- def server_version
101
- @server[:version]
102
- end
103
-
104
-
105
- def open(options_in = {})
106
- raise Exception.new("Already connected!") if @connected
107
-
108
- opts = {
109
- :ip => "127.0.0.1",
110
- :port => "7496"
111
- }.merge(options_in)
112
-
113
-
114
- # Subscribe to the NextValidID message from TWS that is always
115
- # sent at connect, and save the id.
116
- self.subscribe(IncomingMessages::NextValidID, lambda {|msg|
117
- @next_order_id = msg.data[:order_id]
118
- #logger.info { "Got next valid order id #{@next_order_id}." }
119
- })
120
-
121
- @server[:socket] = IBSocket.open(@options[:ip], @options[:port])
122
- #logger.info("* TWS socket connected to #{@options[:ip]}:#{@options[:port]}.")
123
-
124
- # Sekrit handshake.
125
- #logger.debug("\tSending client version #{Tws_client_version}..")
126
-
127
- @server[:socket].send(Tws_client_version)
128
- @server[:version] = @server[:socket].read_int
129
- @@server_version = @server[:version]
130
- @server[:local_connect_time] = Time.now()
131
-
132
- #logger.debug("\tGot server version: #{@server[:version]}.")
133
-
134
- # Server version >= 20 sends the server time back.
135
- if @server[:version] >= 20
136
- @server[:remote_connect_time] = @server[:socket].read_string
137
- #logger.debug("\tServer connect time: #{@server[:remote_connect_time]}.")
138
- end
139
-
140
- # Server version >= 3 wants an arbitrary client ID at this point. This can be used
141
- # to identify subsequent communications.
142
- if @server[:version] >= 3
143
- @server[:client_id] = SHA1.digest(Time.now.to_s + $$.to_s).unpack("C*").join.to_i % 999999999
144
- @server[:socket].send(@server[:client_id])
145
- #logger.debug("\tSent client id # #{@server[:client_id]}.")
146
- end
147
-
148
- #logger.debug("Starting reader thread..")
149
- Thread.abort_on_exception = true
150
- @server[:reader_thread] = Thread.new {
151
- self.reader
152
- }
153
-
154
- @connected = true
155
- end
156
-
157
-
158
-
159
- def close
160
- @server[:reader_thread].kill # Thread uses blocking I/O, so join is useless.
161
- @server[:socket].close()
162
- @server = Hash.new
163
- @@server_version = nil
164
- @connected = false
165
- #logger.debug("Disconnected.")
166
- end # close
167
-
168
-
169
-
170
- def to_s
171
- "IB Connector: #{ @connected ? "connected." : "disconnected."}"
172
- end
173
-
174
-
175
-
176
- # Subscribe to incoming message events of type messageClass.
177
- # code is a Proc that will be called with the message instance as its argument.
178
- def subscribe(messageClass, code)
179
- raise(Exception.new("Invalid argument type (#{messageClass}, #{code.class}) - " +
180
- " must be (IncomingMessages::AbstractMessage, Proc)")) unless
181
- messageClass <= IncomingMessages::AbstractMessage && code.is_a?(Proc)
182
-
183
- @listeners[messageClass].push(code)
184
- end
185
-
186
-
187
-
188
- # Send an outgoing message.
189
- def dispatch(message)
190
- raise Exception.new("dispatch() must be given an OutgoingMessages::AbstractMessage subclass") unless
191
- message.is_a?(OutgoingMessages::AbstractMessage)
192
-
193
- #logger.info("Sending message " + message.inspect)
194
- message.send(@server)
195
- end
196
-
197
-
198
-
199
- protected
200
-
201
- def reader
202
- #logger.debug("Reader started.")
203
-
204
- while true
205
- msg_id = @server[:socket].read_int # this blocks, so Thread#join is useless.
206
- #logger.debug { "Reader: got message id #{msg_id}.\n" }
207
-
208
- # create a new instance of the appropriate message type, and have it read the message.
209
- msg = IncomingMessages::Table[msg_id].new(@server[:socket], @server[:version])
210
-
211
- @listeners[msg.class].each { |listener|
212
- listener.call(msg)
213
- }
214
-
215
- #logger.debug { " Listeners: " + @listeners.inspect + " inclusion: #{ @listeners.include?(msg.class)}" }
216
-
217
- # Log the message if it's an error.
218
- # Make an exception for the "successfully connected" messages, which, for some reason, come back from IB as errors.
219
- if msg.is_a?(IncomingMessages::Error)
220
- if msg.code == 2104 || msg.code == 2106 # connect strings
221
- #logger.info(msg.to_human)
222
- else
223
- #logger.error(msg.to_human)
224
- end
225
- else
226
- # Warn if nobody listened to a non-error incoming message.
227
- unless @listeners[msg.class].size > 0
228
- #logger.warn { " WARNING: Nobody listened to incoming message #{msg.class}" }
229
- end
230
- end
231
-
232
-
233
- # #logger.debug("Reader done with message id #{msg_id}.")
234
-
235
-
236
- end # while
237
-
238
- #logger.debug("Reader done.")
239
- end # reader
240
-
241
- end # class IB
242
- end # module IB