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
@@ -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; *