ib-ruby 0.4.3 → 0.4.20

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/.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
@@ -0,0 +1,807 @@
1
+ # EClientSocket.java uses sendMax() rather than send() for a number of these.
2
+ # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
3
+ # These fields are initialized to this MAX_VALUE.
4
+ # This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
5
+
6
+ # TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
7
+ # TODO: realize Message#fire method that raises EWrapper events
8
+
9
+ module IB
10
+ module Messages
11
+
12
+ # Incoming IB messages
13
+ module Incoming
14
+ Classes = Array.new
15
+
16
+ # This is just a basic generic message from the server.
17
+ #
18
+ # Class variables:
19
+ # @message_id - int: message id.
20
+ # @version - int: current version of message format.
21
+ #
22
+ # Instance attributes (at minimum):
23
+ # @data - Hash of actual data read from a stream.
24
+ #
25
+ # Override the load(socket) method in your subclass to do actual reading into @data.
26
+ class AbstractMessage
27
+ attr_accessor :created_at, :data
28
+
29
+ def self.inherited(by)
30
+ super(by)
31
+ Classes.push(by)
32
+ end
33
+
34
+ def self.message_id
35
+ @message_id
36
+ end
37
+
38
+ def initialize(socket, server_version)
39
+ raise Exception.new("Don't use AbstractMessage directly; use the subclass for your specific message type") if self.class.name == "AbstractMessage"
40
+ @created_at = Time.now
41
+ @data = Hash.new
42
+ @socket = socket
43
+ @server_version = server_version
44
+
45
+ self.load()
46
+
47
+ @socket = nil
48
+ end
49
+
50
+ def to_human
51
+ self.inspect
52
+ end
53
+
54
+ protected
55
+
56
+ # Every message loads received message version first
57
+ def load
58
+ @data[:version] = @socket.read_int
59
+ end
60
+
61
+ # Load @data from the socket according to the given map.
62
+ #
63
+ # map is a series of Arrays in the format [ [ :name, :type ] ],
64
+ # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
65
+ # [:version, :int ] is loaded first, by default
66
+ #
67
+ #
68
+ def load_map(*map)
69
+ ##logger.debug("load_maping map: " + map.inspect)
70
+ map.each { |spec|
71
+ @data[spec[0]] = @socket.__send__(("read_" + spec[1].to_s).to_sym)
72
+ }
73
+ end
74
+ end # class AbstractMessage
75
+
76
+ class AbstractTick < AbstractMessage
77
+ # Returns Symbol with a meaningful name for received tick type
78
+ def type
79
+ TICK_TYPES[@data[:tick_type]]
80
+ end
81
+
82
+ def to_human
83
+ "<#{self.class.to_s.split('::').last} #{type}:" +
84
+ @data.map do |key, value|
85
+ " #{key} #{value}" unless [:version, :id, :tick_type].include?(key)
86
+ end.compact.join(',') + " >"
87
+ end
88
+ end
89
+
90
+ # Macro that defines short message classes using a one-liner
91
+ def self.def_message message_id, *keys, &human_block
92
+ base = keys.first.is_a?(Class) ? keys.shift : AbstractMessage
93
+ Class.new(base) do
94
+ @message_id = message_id
95
+
96
+ define_method(:load) do
97
+ super()
98
+ load_map *keys
99
+ end
100
+
101
+ define_method(:to_human, &human_block) if human_block
102
+ end
103
+ end
104
+
105
+ ### Actual message classes (short definitions):
106
+
107
+ OrderStatus = def_message 3, [:id, :int],
108
+ [:status, :string],
109
+ [:filled, :int],
110
+ [:remaining, :int],
111
+ [:average_fill_price, :decimal],
112
+ [:perm_id, :int],
113
+ [:parent_id, :int],
114
+ [:last_fill_price, :decimal],
115
+ [:client_id, :int],
116
+ [:why_held, :string]
117
+
118
+ AccountValue = def_message(6, [:key, :string],
119
+ [:value, :string],
120
+ [:currency, :string],
121
+ [:account_name, :string]) do
122
+ "<AccountValue: #{@data[:account_name]}, #{@data[:key]}=#{@data[:value]} #{@data[:currency]}>"
123
+ end
124
+
125
+ AccountUpdateTime = def_message(8, [:time_stamp, :string]) do
126
+ "<AccountUpdateTime: #{@data[:time_stamp]}>"
127
+ end
128
+
129
+ # This message is always sent by TWS automatically at connect.
130
+ # The IB::Connection class subscribes to it automatically and stores
131
+ # the order id in its @next_order_id attribute.
132
+ NextValidID = def_message 9, [:id, :int]
133
+
134
+ MarketDepth =
135
+ def_message 12, [:id, :int],
136
+ [:position, :int], # The row Id of this market depth entry.
137
+ [:operation, :int], # How it should be applied to the market depth:
138
+ # 0 = insert this new order into the row identified by :position
139
+ # 1 = update the existing order in the row identified by :position
140
+ # 2 = delete the existing order at the row identified by :position
141
+ [:side, :int], # side of the book: 0 = ask, 1 = bid
142
+ [:price, :decimal],
143
+ [:size, :int]
144
+
145
+ MarketDepthL2 =
146
+ def_message 13, [:id, :int],
147
+ [:position, :int], # The row Id of this market depth entry.
148
+ [:market_maker, :string], # The exchange hosting this order.
149
+ [:operation, :int], # How it should be applied to the market depth:
150
+ # 0 = insert this new order into the row identified by :position
151
+ # 1 = update the existing order in the row identified by :position
152
+ # 2 = delete the existing order at the row identified by :position
153
+ [:side, :int], # side of the book: 0 = ask, 1 = bid
154
+ [:price, :decimal],
155
+ [:size, :int]
156
+
157
+ NewsBulletins =
158
+ def_message 14, [:id, :int], # unique incrementing bulletin ID.
159
+ [:type, :int], # Type of bulletin. Valid values include:
160
+ # 1 = Reqular news bulletin
161
+ # 2 = Exchange no longer available for trading
162
+ # 3 = Exchange is available for trading
163
+ [:text, :string], # The bulletin's message text.
164
+ [:exchange, :string] # Exchange from which this message originated.
165
+
166
+ ManagedAccounts =
167
+ def_message 15, [:accounts_list, :string]
168
+
169
+ # Receives previously requested FA configuration information from TWS.
170
+ ReceiveFA =
171
+ def_message 16, [:type, :int], # type of Financial Advisor configuration data
172
+ # being received from TWS. Valid values include:
173
+ # 1 = GROUPS
174
+ # 2 = PROFILE
175
+ # 3 = ACCOUNT ALIASES
176
+ [:xml, :string] # XML string containing the previously requested
177
+ # FA configuration information.
178
+
179
+ # Receives an XML document that describes the valid parameters that a scanner
180
+ # subscription can have.
181
+ ScannerParameters = def_message 19, [:xml, :string]
182
+
183
+ # Receives the current system time on the server side.
184
+ CurrentTime = def_message 49, [:time, :int] # long!
185
+
186
+ # Receive Reuters global fundamental market data. There must be a subscription to
187
+ # Reuters Fundamental set up in Account Management before you can receive this data.
188
+ FundamentalData = def_message 50, [:id, :int], # request_id
189
+ [:data, :string]
190
+
191
+ ContractDataEnd = def_message 52, [:id, :int] # request_id
192
+
193
+ OpenOrderEnd = def_message 53
194
+
195
+ AccountDownloadEnd = def_message 54, [:account_name, :string]
196
+
197
+ ExecutionDataEnd = def_message 55, [:id, :int] # request_id
198
+
199
+ TickSnapshotEnd = def_message 57, [:id, :int] # request_id
200
+
201
+ ### Actual message classes (long definitions):
202
+
203
+ # The IB code seems to dispatch up to two wrapped objects for this message, a tickPrice
204
+ # and sometimes a tickSize, which seems to be identical to the TICK_SIZE object.
205
+ #
206
+ # Important note from
207
+ # http://chuckcaplan.com/twsapi/index.php/void%20tickPrice%28%29 :
208
+ #
209
+ # "The low you get is NOT the low for the day as you'd expect it
210
+ # to be. It appears IB calculates the low based on all
211
+ # transactions after 4pm the previous day. The most inaccurate
212
+ # results occur when the stock moves up in the 4-6pm aftermarket
213
+ # on the previous day and then gaps open upward in the
214
+ # morning. The low you receive from TWS can be easily be several
215
+ # points different from the actual 9:30am-4pm low for the day in
216
+ # cases like this. If you require a correct traded low for the
217
+ # day, you can't get it from the TWS API. One possible source to
218
+ # help build the right data would be to compare against what Yahoo
219
+ # lists on finance.yahoo.com/q?s=ticker under the "Day's Range"
220
+ # statistics (be careful here, because Yahoo will use anti-Denial
221
+ # of Service techniques to hang your connection if you try to
222
+ # request too many bytes in a short period of time from them). For
223
+ # most purposes, a good enough approach would start by replacing
224
+ # the TWS low for the day with Yahoo's day low when you first
225
+ # start watching a stock ticker; let's call this time T. Then,
226
+ # update your internal low if the bid or ask tick you receive is
227
+ # lower than that for the remainder of the day. You should check
228
+ # against Yahoo again at time T+20min to handle the occasional
229
+ # case where the stock set a new low for the day in between
230
+ # T-20min (the real time your original quote was from, taking into
231
+ # account the delay) and time T. After that you should have a
232
+ # correct enough low for the rest of the day as long as you keep
233
+ # updating based on the bid/ask. It could still get slightly off
234
+ # in a case where a short transaction setting a new low appears in
235
+ # between ticks of data that TWS sends you. The high is probably
236
+ # distorted in the same way the low is, which would throw your
237
+ # results off if the stock traded after-hours and gapped down. It
238
+ # should be corrected in a similar way as described above if this
239
+ # is important to you."
240
+ #
241
+ # IB then emits at most 2 events on eWrapper:
242
+ # tickPrice( tickerId, tickType, price, canAutoExecute)
243
+ # tickSize( tickerId, sizeTickType, size)
244
+ TickPrice = def_message 1, AbstractTick,
245
+ [:id, :int], # ticker_id
246
+ [:tick_type, :int],
247
+ [:price, :decimal],
248
+ [:size, :int],
249
+ [:can_auto_execute, :int]
250
+
251
+ TickSize = def_message 2, AbstractTick,
252
+ [:id, :int], # ticker_id
253
+ [:tick_type, :int],
254
+ [:size, :int]
255
+
256
+ TickGeneric = def_message 45, AbstractTick,
257
+ [:id, :int], # ticker_id
258
+ [:tick_type, :int],
259
+ [:value, :decimal]
260
+
261
+ TickString = def_message 46, AbstractTick,
262
+ [:id, :int], # ticker_id
263
+ [:tick_type, :int],
264
+ [:value, :string]
265
+
266
+ TickEFP = def_message 47, AbstractTick,
267
+ [:id, :int], # ticker_id
268
+ [:tick_type, :int],
269
+ [:basis_points, :decimal],
270
+ [:formatted_basis_points, :string],
271
+ [:implied_futures_price, :decimal],
272
+ [:hold_days, :int],
273
+ [:dividend_impact, :decimal],
274
+ [:dividends_to_expiry, :decimal]
275
+
276
+ # This message is received when the market in an option or its underlier moves.
277
+ # TWS�s option model volatilities, prices, and deltas, along with the present
278
+ # value of dividends expected on that options underlier are received.
279
+ # TickOption message contains following @data:
280
+ # :id - Ticker Id that was specified previously in the call to reqMktData()
281
+ # :tick_type - Specifies the type of option computation (see TICK_TYPES).
282
+ # :implied_volatility - The implied volatility calculated by the TWS option
283
+ # modeler, using the specified :tick_type value.
284
+ # :delta - The option delta value.
285
+ # :option_price - The option price.
286
+ # :pv_dividend - The present value of dividends expected on the options underlier
287
+ # :gamma - The option gamma value.
288
+ # :vega - The option vega value.
289
+ # :theta - The option theta value.
290
+ # :under_price - The price of the underlying.
291
+ class TickOption < AbstractTick
292
+ @message_id = 21
293
+
294
+ # Read @data[key] if it was computed (received value above limit)
295
+ # Leave @data[key] nil if received value below limit ("not yet computed" indicator)
296
+ def read_computed key, limit
297
+ value = @socket.read_decimal # limit-1 is the "not yet computed" indicator
298
+ @data[key] = value < limit ? nil : value
299
+ end
300
+
301
+ def load
302
+ super
303
+
304
+ @data[:id] = @socket.read_int # ticker_id
305
+ @data[:tick_type] = @socket.read_int
306
+ read_computed :implied_volatility, 0 #-1 is the "not yet computed" indicator
307
+ read_computed :delta, -1 # -2 is the "not yet computed" indicator
308
+ read_computed :option_price, 0 # -1 is the "not yet computed" indicator
309
+ read_computed :pv_dividend, 0 # -1 is the "not yet computed" indicator
310
+ read_computed :gamma, -1 # -2 is the "not yet computed" indicator
311
+ read_computed :vega, -1 # -2 is the "not yet computed" indicator
312
+ read_computed :theta, -1 # -2 is the "not yet computed" indicator
313
+ read_computed :under_price, 0 # -1 is the "not yet computed" indicator
314
+ end
315
+
316
+ def to_human
317
+ "<TickOption #{type} for #{@data[:id]}: underlying @ #{@data[:under_price]}, "+
318
+ "option @ #{@data[:option_price]}, IV #{@data[:implied_volatility]}%, " +
319
+ "delta #{@data[:delta]}, gamma #{@data[:gamma]}, vega #{@data[:vega]}, " +
320
+ "theta #{@data[:theta]}, pv_dividend #{@data[:pv_dividend]}>"
321
+ end
322
+ end # TickOption
323
+ TickOptionComputation = TickOption
324
+
325
+ # Called Error in Java code, but in fact this type of messages also
326
+ # deliver system alerts and additional (non-error) info from TWS.
327
+ # It has additional accessors: #code and #message, derived from @data
328
+ Alert = def_message 4, [:id, :int], [:code, :int], [:message, :string]
329
+ class Alert
330
+ def code
331
+ @data && @data[:code]
332
+ end
333
+
334
+ def message
335
+ @data && @data[:message]
336
+ end
337
+
338
+ # Is it an Error message?
339
+ def error?
340
+ code < 1000
341
+ end
342
+
343
+ # Is it a System message?
344
+ def system?
345
+ code > 1000 && code < 2000
346
+ end
347
+
348
+ # Is it a Warning message?
349
+ def warning?
350
+ code > 2000
351
+ end
352
+
353
+ def to_human
354
+ "TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'
355
+ } Message #{@data[:code]}: #{@data[:message]}"
356
+ end
357
+ end # class ErrorMessage
358
+ Error = Alert
359
+ ErrorMessage = Alert
360
+
361
+ class OpenOrder < AbstractMessage
362
+ @message_id = 5
363
+
364
+ attr_accessor :order, :contract
365
+
366
+ def load
367
+ super
368
+
369
+ @order = Models::Order.new :id => @socket.read_int
370
+
371
+ @contract = Models::Contract.new :symbol => @socket.read_string,
372
+ :sec_type => @socket.read_string,
373
+ :expiry => @socket.read_string,
374
+ :strike => @socket.read_decimal,
375
+ :right => @socket.read_string,
376
+ :exchange => @socket.read_string,
377
+ :currency => @socket.read_string,
378
+ :local_symbol => @socket.read_string
379
+
380
+ @order.action = @socket.read_string
381
+ @order.total_quantity = @socket.read_int
382
+ @order.order_type = @socket.read_string
383
+ @order.limit_price = @socket.read_decimal
384
+ @order.aux_price = @socket.read_decimal
385
+ @order.tif = @socket.read_string
386
+ @order.oca_group = @socket.read_string
387
+ @order.account = @socket.read_string
388
+ @order.open_close = @socket.read_string
389
+ @order.origin = @socket.read_int
390
+ @order.order_ref = @socket.read_string
391
+ @order.client_id = @socket.read_int
392
+ @order.perm_id = @socket.read_int
393
+ @order.outside_rth = (@socket.read_int == 1)
394
+ @order.hidden = (@socket.read_int == 1)
395
+ @order.discretionary_amount = @socket.read_decimal
396
+ @order.good_after_time = @socket.read_string
397
+ @socket.read_string # skip deprecated sharesAllocation field
398
+
399
+ @order.fa_group = @socket.read_string
400
+ @order.fa_method = @socket.read_string
401
+ @order.fa_percentage = @socket.read_string
402
+ @order.fa_profile = @socket.read_string
403
+ @order.good_till_date = @socket.read_string
404
+ @order.rule_80A = @socket.read_string
405
+ @order.percent_offset = @socket.read_decimal
406
+ @order.settling_firm = @socket.read_string
407
+ @order.short_sale_slot = @socket.read_int
408
+ @order.designated_location = @socket.read_string
409
+ @order.exempt_code = @socket.read_int # skipped in ver 51?
410
+ @order.auction_strategy = @socket.read_int
411
+ @order.starting_price = @socket.read_decimal
412
+ @order.stock_ref_price = @socket.read_decimal
413
+ @order.delta = @socket.read_decimal
414
+ @order.stock_range_lower = @socket.read_decimal
415
+ @order.stock_range_upper = @socket.read_decimal
416
+ @order.display_size = @socket.read_int
417
+ #@order.rth_only = @socket.read_boolean
418
+ @order.block_order = @socket.read_boolean
419
+ @order.sweep_to_fill = @socket.read_boolean
420
+ @order.all_or_none = @socket.read_boolean
421
+ @order.min_quantity = @socket.read_int
422
+ @order.oca_type = @socket.read_int
423
+ @order.etrade_only = @socket.read_boolean
424
+ @order.firm_quote_only = @socket.read_boolean
425
+ @order.nbbo_price_cap = @socket.read_decimal
426
+ @order.parent_id = @socket.read_int
427
+ @order.trigger_method = @socket.read_int
428
+ @order.volatility = @socket.read_decimal
429
+ @order.volatility_type = @socket.read_int
430
+ @order.delta_neutral_order_type = @socket.read_string
431
+ @order.delta_neutral_aux_price = @socket.read_decimal
432
+
433
+ @order.continuous_update = @socket.read_int
434
+ @order.reference_price_type = @socket.read_int
435
+ @order.trail_stop_price = @socket.read_decimal
436
+ @order.basis_points = @socket.read_decimal
437
+ @order.basis_points_type = @socket.read_int
438
+ @order.combo_legs_description = @socket.read_string
439
+ @order.scale_init_level_size = @socket.read_int_max
440
+ @order.scale_subs_level_size = @socket.read_int_max
441
+ @order.scale_price_increment = @socket.read_decimal_max
442
+ @order.clearing_account = @socket.read_string
443
+ @order.clearing_intent = @socket.read_string
444
+ @order.not_held = (@socket.read_int == 1)
445
+
446
+ under_comp_present = (@socket.read_int == 1)
447
+
448
+ if under_comp_present
449
+ @contract.under_comp =
450
+ Models::Contract::UnderComp.new :con_id => @socket.read_int,
451
+ :delta => @socket.read_decimal,
452
+ :price => @socket.read_decimal
453
+ end
454
+
455
+ @order.algo_strategy = @socket.read_string
456
+
457
+ unless @order.algo_strategy.nil? || @order.algo_strategy.empty?
458
+ algo_params_count = @socket.read_int
459
+ if algo_params_count > 0
460
+ @order.algo_params = Hash.new
461
+ algo_params_count.times do
462
+ tag = @socket.read_string
463
+ value = @socket.read_string
464
+ @order.algo_params[tag] = value
465
+ end
466
+ end
467
+ end
468
+
469
+ @order.what_if = (@socket.read_int == 1)
470
+ @order.status = @socket.read_string
471
+ @order.init_margin = @socket.read_string
472
+ @order.maint_margin = @socket.read_string
473
+ @order.equity_with_loan = @socket.read_string
474
+ @order.commission = @socket.read_decimal_max
475
+ @order.min_commission = @socket.read_decimal_max
476
+ @order.max_commission = @socket.read_decimal_max
477
+ @order.commission_currency = @socket.read_string
478
+ @order.warning_text = @socket.read_string
479
+ end
480
+ end # OpenOrder
481
+
482
+ class PortfolioValue < AbstractMessage
483
+ @message_id = 7
484
+
485
+ attr_accessor :contract
486
+
487
+ def load
488
+ super
489
+
490
+ @contract = Models::Contract.new :con_id => @socket.read_int,
491
+ :symbol => @socket.read_string,
492
+ :sec_type => @socket.read_string,
493
+ :expiry => @socket.read_string,
494
+ :strike => @socket.read_decimal,
495
+ :right => @socket.read_string,
496
+ :multiplier => @socket.read_string,
497
+ :primary_exchange => @socket.read_string,
498
+ :currency => @socket.read_string,
499
+ :local_symbol => @socket.read_string
500
+ load_map [:position, :int],
501
+ [:market_price, :decimal],
502
+ [:market_value, :decimal],
503
+ [:average_cost, :decimal],
504
+ [:unrealized_pnl, :decimal],
505
+ [:realized_pnl, :decimal],
506
+ [:account_name, :string]
507
+ end
508
+
509
+ def to_human
510
+ "<PortfolioValue: #{@contract.to_human} (#{@data[:position]}): Market #{@data[:market_price]}" +
511
+ " price #{@data[:market_value]} value; PnL: #{@data[:unrealized_pnl]} unrealized," +
512
+ " #{@data[:realized_pnl]} realized; account #{@data[:account_name]}>"
513
+ end
514
+
515
+ end # PortfolioValue
516
+
517
+ class ContractData < AbstractMessage
518
+ @message_id = 10
519
+
520
+ attr_accessor :contract
521
+
522
+ def load
523
+ super
524
+ load_map [:id, :int] # request id
525
+
526
+ @contract =
527
+ Models::Contract.new :symbol => @socket.read_string,
528
+ :sec_type => @socket.read_string,
529
+ :expiry => @socket.read_string,
530
+ :strike => @socket.read_decimal,
531
+ :right => @socket.read_string,
532
+ :exchange => @socket.read_string,
533
+ :currency => @socket.read_string,
534
+ :local_symbol => @socket.read_string,
535
+
536
+ :market_name => @socket.read_string,
537
+ :trading_class => @socket.read_string,
538
+ :con_id => @socket.read_int,
539
+ :min_tick => @socket.read_decimal,
540
+ :multiplier => @socket.read_string,
541
+ :order_types => @socket.read_string,
542
+ :valid_exchanges => @socket.read_string,
543
+ :price_magnifier => @socket.read_int,
544
+ :under_con_id => @socket.read_int,
545
+ :long_name => @socket.read_string,
546
+ :primary_exchange => @socket.read_string,
547
+ :contract_month => @socket.read_string,
548
+ :industry => @socket.read_string,
549
+ :category => @socket.read_string,
550
+ :subcategory => @socket.read_string,
551
+ :time_zone => @socket.read_string,
552
+ :trading_hours => @socket.read_string,
553
+ :liquid_hours => @socket.read_string
554
+ end
555
+ end # ContractData
556
+
557
+ class ExecutionData < AbstractMessage
558
+ @message_id = 11
559
+
560
+ attr_accessor :contract, :execution
561
+
562
+ def load
563
+ super
564
+ load_map [:id, :int], # request_id
565
+ [:order_id, :int]
566
+
567
+ @contract =
568
+ Models::Contract.new :con_id => @socket.read_int,
569
+ :symbol => @socket.read_string,
570
+ :sec_type => @socket.read_string,
571
+ :expiry => @socket.read_string,
572
+ :strike => @socket.read_decimal,
573
+ :right => @socket.read_string,
574
+ :exchange => @socket.read_string,
575
+ :currency => @socket.read_string,
576
+ :local_symbol => @socket.read_string
577
+ @execution =
578
+ Models::Execution.new :order_id => @data[:order_id],
579
+ :exec_id => @socket.read_string,
580
+ :time => @socket.read_string,
581
+ :account_number => @socket.read_string,
582
+ :exchange => @socket.read_string,
583
+ :side => @socket.read_string,
584
+ :shares => @socket.read_int,
585
+ :price => @socket.read_decimal,
586
+ :perm_id => @socket.read_int,
587
+ :client_id => @socket.read_int,
588
+ :liquidation => @socket.read_int,
589
+ :cumulative_quantity => @socket.read_int,
590
+ :average_price => @socket.read_decimal
591
+ end
592
+ end # ExecutionData
593
+
594
+ # HistoricalData contains following @data:
595
+ # :id - The ID of the request to which this is responding
596
+ # :count - Number of data points returned (size of :results).
597
+ # :results - an Array of Historical Data Bars
598
+ # :start_date
599
+ # :end_date
600
+ # :completed_indicator - string in stupid legacy format
601
+ class HistoricalData < AbstractMessage
602
+ @message_id = 17
603
+
604
+ def load
605
+ super
606
+ load_map [:id, :int],
607
+ [:start_date, :string],
608
+ [:end_date, :string],
609
+ [:count, :int]
610
+
611
+ @data[:completed_indicator] =
612
+ "finished-#{@data[:start_date]}-#{@data[:end_date]}"
613
+
614
+ @data[:results] = Array.new(@data[:count]) do |index|
615
+ Models::Bar.new :date => @socket.read_string,
616
+ :open => @socket.read_decimal,
617
+ :high => @socket.read_decimal,
618
+ :low => @socket.read_decimal,
619
+ :close => @socket.read_decimal,
620
+ :volume => @socket.read_int,
621
+ :wap => @socket.read_decimal,
622
+ :has_gaps => @socket.read_string,
623
+ :trades => @socket.read_int
624
+ end
625
+ end
626
+
627
+ def to_human
628
+ "<HistoricalData: req id #{@data[:id]}, #{@data[:item_count]} items, from #{@data[:start_date_str]} to #{@data[:end_date_str]}>"
629
+ end
630
+ end # HistoricalData
631
+
632
+ class BondContractData < AbstractMessage
633
+ @message_id = 18
634
+
635
+ attr_accessor :contract
636
+
637
+ def load
638
+ super
639
+ load_map [:id, :int] # request id
640
+
641
+ @contract =
642
+ Models::Contract.new :symbol => @socket.read_string,
643
+ :sec_type => @socket.read_string,
644
+ :cusip => @socket.read_string,
645
+ :coupon => @socket.read_decimal,
646
+ :maturity => @socket.read_string,
647
+ :issue_date => @socket.read_string,
648
+ :ratings => @socket.read_string,
649
+ :bond_type => @socket.read_string,
650
+ :coupon_type => @socket.read_string,
651
+ :convertible => @socket.read_boolean,
652
+ :callable => @socket.read_boolean,
653
+ :puttable => @socket.read_boolean,
654
+ :desc_append => @socket.read_string,
655
+ :exchange => @socket.read_string,
656
+ :currency => @socket.read_string,
657
+ :market_name => @socket.read_string,
658
+ :trading_class => @socket.read_string,
659
+ :con_id => @socket.read_int,
660
+ :min_tick => @socket.read_decimal,
661
+ :order_types => @socket.read_string,
662
+ :valid_exchanges => @socket.read_string,
663
+ :valid_next_option_date => @socket.read_string,
664
+ :valid_next_option_type => @socket.read_string,
665
+ :valid_next_option_partial => @socket.read_string,
666
+ :notes => @socket.read_string,
667
+ :long_name => @socket.read_string
668
+ end
669
+ end # BondContractData
670
+
671
+ # This method receives the requested market scanner data results.
672
+ # ScannerData contains following @data:
673
+ # :id - The ID of the request to which this row is responding
674
+ # :count - Number of data points returned (size of :results).
675
+ # :results - an Array of Hashes, each hash contains a set of
676
+ # data about one scanned Contract:
677
+ # :contract - a full description of the contract (details).
678
+ # :distance - Varies based on query.
679
+ # :benchmark - Varies based on query.
680
+ # :projection - Varies based on query.
681
+ # :legs - Describes combo legs when scan is returning EFP.
682
+ class ScannerData < AbstractMessage
683
+ @message_id = 20
684
+
685
+ def load
686
+ super
687
+ load_map [:id, :int],
688
+ [:count, :int]
689
+
690
+ @data[:results] = Array.new(@data[:count]) do |index|
691
+ {:rank => @socket.read_int,
692
+ :contract => Contract.new(:con_id => @socket.read_int,
693
+ :symbol => @socket.read_str,
694
+ :sec_type => @socket.read_str,
695
+ :expiry => @socket.read_str,
696
+ :strike => @socket.read_decimal,
697
+ :right => @socket.read_str,
698
+ :exchange => @socket.read_str,
699
+ :currency => @socket.read_str,
700
+ :local_symbol => @socket.read_str,
701
+ :market_name => @socket.read_str,
702
+ :trading_class => @socket.read_str),
703
+ :distance => @socket.read_str,
704
+ :benchmark => @socket.read_str,
705
+ :projection => @socket.read_str,
706
+ :legs => @socket.read_str,
707
+ }
708
+ #eWrapper().scannerData(tickerId, rank, contract, distance,
709
+ # benchmark, projection, legsStr);
710
+
711
+ end
712
+
713
+ #eWrapper().scannerDataEnd(tickerId);
714
+ end
715
+ end # ScannerData
716
+
717
+ # HistoricalData contains following @data:
718
+ # :id - The ID of the *request* to which this is responding
719
+ # :time - The date-time stamp of the start of the bar. The format is offset in
720
+ # seconds from the beginning of 1970, same format as the UNIX epoch time
721
+ # :bar - received RT Bar
722
+ class RealTimeBar < AbstractMessage
723
+ @message_id = 50
724
+
725
+ attr_accessor :bar
726
+
727
+ def load
728
+ super
729
+ load_map [:id, :int],
730
+ [:time, :int] # long!
731
+
732
+ @bar = Models::Bar.new :date => Time.at(@data[:time]),
733
+ :open => @socket.read_decimal,
734
+ :high => @socket.read_decimal,
735
+ :low => @socket.read_decimal,
736
+ :close => @socket.read_decimal,
737
+ :volume => @socket.read_int,
738
+ :wap => @socket.read_decimal,
739
+ :trades => @socket.read_int
740
+ end
741
+
742
+ def to_human
743
+ "<RealTimeBar: req id #{@data[:id]}, #{@bar}>"
744
+ end
745
+ end # RealTimeBar
746
+ RealTimeBars = RealTimeBar
747
+
748
+ # The server sends this message upon accepting a Delta-Neutral DN RFQ
749
+ # - see API Reference p. 26
750
+ class DeltaNeutralValidation < AbstractMessage
751
+ @message_id = 56
752
+
753
+ attr_accessor :contract
754
+
755
+ def load
756
+ super
757
+ load_map [:id, :int] # request id
758
+
759
+ @contract = Models::Contract.new :under_comp => true,
760
+ :under_con_id => @socket.read_int,
761
+ :under_delta => @socket.read_decimal,
762
+ :under_price => @socket.read_decimal
763
+ end
764
+ end # DeltaNeutralValidation
765
+
766
+ Table = Hash.new
767
+ Classes.each { |msg_class| Table[msg_class.message_id] = msg_class }
768
+
769
+ end # module Incoming
770
+ end # module Messages
771
+ end # module IB
772
+ __END__
773
+
774
+ // incoming msg id's
775
+ static final int TICK_PRICE = 1; * TODO: realize both events
776
+ static final int TICK_SIZE = 2; *
777
+ static final int ORDER_STATUS = 3; *
778
+ static final int ERR_MSG = 4; *
779
+ static final int OPEN_ORDER = 5; *
780
+ static final int ACCT_VALUE = 6; *
781
+ static final int PORTFOLIO_VALUE = 7; *
782
+ static final int ACCT_UPDATE_TIME = 8; *
783
+ static final int NEXT_VALID_ID = 9; *
784
+ static final int CONTRACT_DATA = 10; *
785
+ static final int EXECUTION_DATA = 11; *
786
+ static final int MARKET_DEPTH = 12; *
787
+ static final int MARKET_DEPTH_L2 = 13; *
788
+ static final int NEWS_BULLETINS = 14; *
789
+ static final int MANAGED_ACCTS = 15; *
790
+ static final int RECEIVE_FA = 16; *
791
+ static final int HISTORICAL_DATA = 17; *
792
+ static final int BOND_CONTRACT_DATA = 18; *
793
+ static final int SCANNER_PARAMETERS = 19; *
794
+ static final int SCANNER_DATA = 20; *
795
+ static final int TICK_OPTION_COMPUTATION = 21; *
796
+ static final int TICK_GENERIC = 45; *
797
+ static final int TICK_STRING = 46; *
798
+ static final int TICK_EFP = 47; *
799
+ static final int CURRENT_TIME = 49; *
800
+ static final int REAL_TIME_BARS = 50; *
801
+ static final int FUNDAMENTAL_DATA = 51; *
802
+ static final int CONTRACT_DATA_END = 52; *
803
+ static final int OPEN_ORDER_END = 53; *
804
+ static final int ACCT_DOWNLOAD_END = 54; *
805
+ static final int EXECUTION_DATA_END = 55; *
806
+ static final int DELTA_NEUTRAL_VALIDATION = 56; *
807
+ static final int TICK_SNAPSHOT_END = 57; *