ib-ruby 0.5.19 → 0.5.21

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/HISTORY +4 -0
  2. data/TODO +0 -3
  3. data/VERSION +1 -1
  4. data/bin/contract_details +3 -3
  5. data/bin/depth_of_market +1 -1
  6. data/bin/historic_data +5 -8
  7. data/bin/market_data +2 -2
  8. data/bin/option_data +2 -2
  9. data/lib/ib-ruby/connection.rb +1 -9
  10. data/lib/ib-ruby/extensions.rb +8 -0
  11. data/lib/ib-ruby/messages/abstract_message.rb +89 -0
  12. data/lib/ib-ruby/messages/incoming.rb +415 -487
  13. data/lib/ib-ruby/messages/outgoing.rb +241 -305
  14. data/lib/ib-ruby/models/bar.rb +3 -3
  15. data/lib/ib-ruby/models/contract/bag.rb +1 -5
  16. data/lib/ib-ruby/models/contract.rb +50 -33
  17. data/lib/ib-ruby/models/execution.rb +6 -3
  18. data/lib/ib-ruby/models/order.rb +7 -5
  19. data/lib/ib-ruby/socket.rb +13 -0
  20. data/lib/ib-ruby/symbols/forex.rb +7 -14
  21. data/lib/ib-ruby/symbols/futures.rb +16 -20
  22. data/lib/ib-ruby/symbols/options.rb +6 -4
  23. data/lib/ib-ruby/symbols/stocks.rb +1 -1
  24. data/lib/ib-ruby.rb +1 -0
  25. data/spec/README.md +34 -0
  26. data/spec/ib-ruby/connection_spec.rb +4 -4
  27. data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
  28. data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
  29. data/spec/ib-ruby/models/contract_spec.rb +27 -25
  30. data/spec/ib-ruby/models/order_spec.rb +56 -23
  31. data/spec/integration/account_info_spec.rb +85 -0
  32. data/spec/integration/contract_info_spec.rb +209 -0
  33. data/spec/integration/depth_data_spec.rb +46 -0
  34. data/spec/integration/historic_data_spec.rb +82 -0
  35. data/spec/integration/market_data_spec.rb +97 -0
  36. data/spec/integration/option_data_spec.rb +96 -0
  37. data/spec/integration/orders/execution_spec.rb +135 -0
  38. data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
  39. data/spec/integration/orders/placement_spec.rb +150 -0
  40. data/spec/integration/orders/valid_ids_spec.rb +84 -0
  41. data/spec/integration_helper.rb +110 -0
  42. data/spec/message_helper.rb +13 -18
  43. data/spec/spec_helper.rb +35 -26
  44. metadata +33 -17
  45. data/spec/ib-ruby/messages/README.md +0 -16
  46. data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
  47. data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
  48. data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
  49. data/spec/ib-ruby/messages/orders_spec.rb +0 -219
  50. data/spec/ib-ruby_spec.rb +0 -0
@@ -1,3 +1,5 @@
1
+ require 'ib-ruby/messages/abstract_message'
2
+
1
3
  # EClientSocket.java uses sendMax() rather than send() for a number of these.
2
4
  # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
3
5
  # These fields are initialized to this MAX_VALUE.
@@ -11,82 +13,64 @@ module IB
11
13
 
12
14
  # Incoming IB messages
13
15
  module Incoming
16
+ extend Messages # def_message macros
17
+
14
18
  Classes = Array.new
15
19
 
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 least):
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
20
+ class AbstractMessage < IB::Messages::AbstractMessage
28
21
 
29
22
  def self.inherited(by)
30
23
  super(by)
31
24
  Classes.push(by)
32
25
  end
33
26
 
34
- def self.message_id
35
- @message_id
27
+ def version # Per message, received messages may have the different versions
28
+ @data[:version]
36
29
  end
37
30
 
38
- def initialize socket
39
- raise Exception.new("Don't use AbstractMessage directly; use the subclass for your specific message type") if self.class.name == "AbstractMessage"
31
+ # Read incoming message from given socket or instantiate with given data
32
+ def initialize socket_or_data
40
33
  @created_at = Time.now
41
- @data = Hash.new
42
- @socket = socket
43
-
44
- self.load()
45
-
46
- @socket = nil
47
- end
48
-
49
- def to_human
50
- self.inspect
51
- end
52
-
53
- # Object#id is always defined, we cannot rely on method_missing
54
- def id
55
- @data.has_key?(:id) ? @data[:id] : super
56
- end
57
-
58
- def respond_to? method
59
- getter = method.to_s.sub(/=$/, '').to_sym
60
- @data.has_key?(method) || @data.has_key?(getter) || super
61
- end
62
-
63
- protected
64
-
65
- # TODO: method compilation instead of method_missing
66
- def method_missing method, *args
67
- getter = method.to_s.sub(/=$/, '').to_sym
68
- if @data.has_key? method
69
- @data[method]
70
- elsif @data.has_key? getter
71
- @data[getter] = *args
34
+ if socket_or_data.is_a?(Hash)
35
+ @data = socket_or_data
72
36
  else
73
- super method, *args
37
+ @data = {}
38
+ @socket = socket_or_data
39
+ self.load
40
+ @socket = nil
74
41
  end
75
42
  end
76
43
 
77
44
  # Every message loads received message version first
78
45
  def load
79
46
  @data[:version] = @socket.read_int
47
+
48
+ if @data[:version] != self.class.version
49
+ raise "Unsupported version #{@data[:version]} of #{self.class} received"
50
+ end
51
+
52
+ load_map *self.class.data_map
80
53
  end
81
54
 
82
55
  # Load @data from the socket according to the given map.
83
56
  #
84
- # map is a series of Arrays in the format [ [ :name, :type ] ],
57
+ # map is a series of Arrays in the format of
58
+ # [ [ :name, :type ],
59
+ # [ :group, :name, :type] ]
85
60
  # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
86
- # [:version, :int ] is loaded first, by default
87
- #
61
+ # group is used to lump together aggregates, such as Contract or Order fields
88
62
  def load_map(*map)
89
- map.each { |(name, type)| @data[name] = @socket.__send__("read_#{type}") }
63
+ map.each do |(m1, m2, m3)|
64
+ group, name, type = m3 ? [m1, m2, m3] : [nil, m1, m2]
65
+
66
+ data = @socket.__send__("read_#{type}")
67
+ if group
68
+ @data[group] ||= {}
69
+ @data[group][name] = data
70
+ else
71
+ @data[name] = data
72
+ end
73
+ end
90
74
  end
91
75
  end # class AbstractMessage
92
76
 
@@ -97,28 +81,13 @@ module IB
97
81
  end
98
82
 
99
83
  def to_human
100
- "<#{self.class.to_s.split('::').last} #{type}:" +
84
+ "<#{self.message_type} #{type}:" +
101
85
  @data.map do |key, value|
102
- " #{key} #{value}" unless [:version, :id, :tick_type].include?(key)
86
+ " #{key} #{value}" unless [:version, :ticker_id, :tick_type].include?(key)
103
87
  end.compact.join(',') + " >"
104
88
  end
105
89
  end
106
90
 
107
- # Macro that defines short message classes using a one-liner
108
- def self.def_message message_id, *keys, &to_human
109
- base = keys.first.is_a?(Class) ? keys.shift : AbstractMessage
110
- Class.new(base) do
111
- @message_id = message_id
112
-
113
- define_method(:load) do
114
- super()
115
- load_map *keys
116
- end
117
-
118
- define_method(:to_human, &to_human) if to_human
119
- end
120
- end
121
-
122
91
  ### Actual message classes (short definitions):
123
92
  #:status - String: Displays the order status. Possible values include:
124
93
  # � PendingSubmit - indicates that you have transmitted the order, but
@@ -147,7 +116,7 @@ module IB
147
116
  # the order is inactive due to system, exchange or other issues.
148
117
  # :why_held - This field is used to identify an order held when TWS is trying to
149
118
  # locate shares for a short sell. The value used to indicate this is 'locate'.
150
- OrderStatus = def_message 3, [:id, :int],
119
+ OrderStatus = def_message [3, 6], [:order_id, :int],
151
120
  [:status, :string],
152
121
  [:filled, :int],
153
122
  [:remaining, :int],
@@ -161,28 +130,26 @@ module IB
161
130
  " @ last/avg: #{last_fill_price}/#{average_fill_price}" +
162
131
  (parent_id > 0 ? "parent_id: #{parent_id}" : "") +
163
132
  (why_held != "" ? "why_held: #{why_held}" : "") +
164
- " id/perm: #{id}/#{perm_id}>"
133
+ " id/perm: #{order_id}/#{perm_id}>"
165
134
  end
166
135
 
167
136
 
168
- AccountValue = def_message(6, [:key, :string],
137
+ AccountValue = def_message([6, 2], [:key, :string],
169
138
  [:value, :string],
170
139
  [:currency, :string],
171
140
  [:account_name, :string]) do
172
141
  "<AccountValue: #{account_name}, #{key}=#{value} #{currency}>"
173
142
  end
174
143
 
175
- AccountUpdateTime = def_message(8, [:time_stamp, :string]) do
176
- "<AccountUpdateTime: #{time_stamp}>"
177
- end
144
+ AccountUpdateTime = def_message 8, [:time_stamp, :string]
178
145
 
179
146
  # This message is always sent by TWS automatically at connect.
180
147
  # The IB::Connection class subscribes to it automatically and stores
181
148
  # the order id in its @next_order_id attribute.
182
- NextValidID = def_message(9, [:id, :int]) { "<NextValidID: #{id}>" }
149
+ NextValidID = def_message 9, [:order_id, :int]
183
150
 
184
151
  NewsBulletins =
185
- def_message 14, [:id, :int], # unique incrementing bulletin ID.
152
+ def_message 14, [:request_id, :int], # unique incrementing bulletin ID.
186
153
  [:type, :int], # Type of bulletin. Valid values include:
187
154
  # 1 = Regular news bulletin
188
155
  # 2 = Exchange no longer available for trading
@@ -200,8 +167,7 @@ module IB
200
167
  # 1 = GROUPS
201
168
  # 2 = PROFILE
202
169
  # 3 = ACCOUNT ALIASES
203
- [:xml, :string] # XML string containing the previously requested
204
- # FA configuration information.
170
+ [:xml, :string] # XML string with requested FA configuration information.
205
171
 
206
172
  # Receives an XML document that describes the valid parameters that a scanner
207
173
  # subscription can have (for outgoing RequestScannerSubscription message).
@@ -212,21 +178,18 @@ module IB
212
178
 
213
179
  # Receive Reuters global fundamental market data. There must be a subscription to
214
180
  # Reuters Fundamental set up in Account Management before you can receive this data.
215
- FundamentalData = def_message 50, [:id, :int], # request_id
181
+ FundamentalData = def_message 50, [:request_id, :int], # request_id
216
182
  [:data, :string]
217
183
 
218
- ContractDataEnd = def_message(52, [:id, :int]) { "<ContractDataEnd: #{id}>" } # request_id
219
-
220
- OpenOrderEnd = def_message(53) { "<OpenOrderEnd>" }
184
+ ContractDataEnd = def_message 52, [:request_id, :int] # request_id
221
185
 
222
- AccountDownloadEnd = def_message(54, [:account_name, :string]) do
223
- "<AccountDownloadEnd: #{account_name}}>"
224
- end # request_id
186
+ OpenOrderEnd = def_message 53
225
187
 
188
+ AccountDownloadEnd = def_message 54, [:account_name, :string]
226
189
 
227
- ExecutionDataEnd = def_message(55, [:id, :int]) { "<ExecutionDataEnd: #{id}>" } # request_id
190
+ ExecutionDataEnd = def_message 55, [:request_id, :int] # request_id
228
191
 
229
- TickSnapshotEnd = def_message(57, [:id, :int]) { "<TickSnapshotEnd: #{id}>" } # request_id
192
+ TickSnapshotEnd = def_message 57, [:ticker_id, :int]
230
193
 
231
194
  ### Actual message classes (long definitions):
232
195
 
@@ -271,30 +234,30 @@ module IB
271
234
  # IB then emits at most 2 events on eWrapper:
272
235
  # tickPrice( tickerId, tickType, price, canAutoExecute)
273
236
  # tickSize( tickerId, sizeTickType, size)
274
- TickPrice = def_message 1, AbstractTick,
275
- [:id, :int], # ticker_id
237
+ TickPrice = def_message [1, 6], AbstractTick,
238
+ [:ticker_id, :int],
276
239
  [:tick_type, :int],
277
240
  [:price, :decimal],
278
241
  [:size, :int],
279
242
  [:can_auto_execute, :int]
280
243
 
281
- TickSize = def_message 2, AbstractTick,
282
- [:id, :int], # ticker_id
244
+ TickSize = def_message [2, 6], AbstractTick,
245
+ [:ticker_id, :int],
283
246
  [:tick_type, :int],
284
247
  [:size, :int]
285
248
 
286
249
  TickGeneric = def_message 45, AbstractTick,
287
- [:id, :int], # ticker_id
250
+ [:ticker_id, :int],
288
251
  [:tick_type, :int],
289
252
  [:value, :decimal]
290
253
 
291
- TickString = def_message 46, AbstractTick,
292
- [:id, :int], # ticker_id
254
+ TickString = def_message [46, 6], AbstractTick,
255
+ [:ticker_id, :int],
293
256
  [:tick_type, :int],
294
257
  [:value, :string]
295
258
 
296
259
  TickEFP = def_message 47, AbstractTick,
297
- [:id, :int], # ticker_id
260
+ [:ticker_id, :int],
298
261
  [:tick_type, :int],
299
262
  [:basis_points, :decimal],
300
263
  [:formatted_basis_points, :string],
@@ -307,7 +270,7 @@ module IB
307
270
  # TWS�s option model volatilities, prices, and deltas, along with the present
308
271
  # value of dividends expected on that options underlier are received.
309
272
  # TickOption message contains following @data:
310
- # :id - Ticker Id that was specified previously in the call to reqMktData()
273
+ # :ticker_id - Id that was specified previously in the call to reqMktData()
311
274
  # :tick_type - Specifies the type of option computation (see TICK_TYPES).
312
275
  # :implied_volatility - The implied volatility calculated by the TWS option
313
276
  # modeler, using the specified :tick_type value.
@@ -318,42 +281,27 @@ module IB
318
281
  # :vega - The option vega value.
319
282
  # :theta - The option theta value.
320
283
  # :under_price - The price of the underlying.
321
- class TickOption < AbstractTick
322
- @message_id = 21
323
-
324
- # Read @data[key] if it was computed (received value above limit)
325
- # Leave @data[key] nil if received value below limit ("not yet computed")
326
- def read_computed key, limit
327
- value = @socket.read_decimal
328
- # limit is the "not yet computed" indicator
329
- @data[key] = value <= limit ? nil : value
330
- end
331
-
332
- def load
333
- super
334
-
335
- @data[:id] = @socket.read_int # ticker_id
336
- @data[:tick_type] = @socket.read_int
337
- read_computed :implied_volatility, -1 #-1 is the "not yet computed" indicator
338
- read_computed :delta, -2 # -2 is the "not yet computed" indicator
339
- read_computed :option_price, -1 # -1 is the "not yet computed" indicator
340
- read_computed :pv_dividend, -1 # -1 is the "not yet computed" indicator
341
- read_computed :gamma, -2 # -2 is the "not yet computed" indicator
342
- read_computed :vega, -2 # -2 is the "not yet computed" indicator
343
- read_computed :theta, -2 # -2 is the "not yet computed" indicator
344
- read_computed :under_price, -1 # -1 is the "not yet computed" indicator
345
- end
346
-
347
- def to_human
348
- "<TickOption #{type} for #{:id}: underlying @ #{under_price}, "+
349
- "option @ #{option_price}, IV #{implied_volatility}%, delta #{delta}, " +
350
- "gamma #{gamma}, vega #{vega}, theta #{theta}, pv_dividend #{pv_dividend}>"
351
- end
352
- end # TickOption
353
- TickOptionComputation = TickOption
284
+ TickOptionComputation = TickOption =
285
+ def_message([21, 6], AbstractTick,
286
+ [:ticker_id, :int],
287
+ [:tick_type, :int],
288
+ # What is the "not yet computed" indicator:
289
+ [:implied_volatility, :decimal_limit_1], # -1 and below
290
+ [:delta, :decimal_limit_2], # -2 and below
291
+ [:option_price, :decimal_limit_1], # -1 -"-
292
+ [:pv_dividend, :decimal_limit_1], # -1 -"-
293
+ [:gamma, :decimal_limit_2], # -2 -"-
294
+ [:vega, :decimal_limit_2], # -2 -"-
295
+ [:theta, :decimal_limit_2], # -2 -"-
296
+ [:under_price, :decimal_limit_1]) do
297
+
298
+ "<TickOption #{type} for #{:ticker_id}: underlying @ #{under_price}, "+
299
+ "option @ #{option_price}, IV #{implied_volatility}%, delta #{delta}, " +
300
+ "gamma #{gamma}, vega #{vega}, theta #{theta}, pv_dividend #{pv_dividend}>"
301
+ end
354
302
 
355
303
  MarketDepth =
356
- def_message 12, [:id, :int],
304
+ def_message 12, [:request_id, :int],
357
305
  [:position, :int], # The row Id of this market depth entry.
358
306
  [:operation, :int], # How it should be applied to the market depth:
359
307
  # 0 = insert this new order into the row identified by :position
@@ -363,7 +311,6 @@ module IB
363
311
  [:price, :decimal],
364
312
  [:size, :int]
365
313
  class MarketDepth
366
-
367
314
  def side
368
315
  @data[:side] == 0 ? :ask : :bid
369
316
  end
@@ -373,28 +320,27 @@ module IB
373
320
  end
374
321
 
375
322
  def to_human
376
- "<#{self.class.to_s.split(/::/).last}: #{operation} #{side} @ "+
323
+ "<#{self.message_type}: #{operation} #{side} @ "+
377
324
  "#{position} = #{price} x #{size}>"
378
325
  end
379
326
  end
380
327
 
381
328
  MarketDepthL2 =
382
- def_message 13, MarketDepth,
383
- [:id, :int],
384
- [:position, :int], # The row Id of this market depth entry.
329
+ def_message 13, MarketDepth, # Fields descriptions - see above
330
+ [:request_id, :int],
331
+ [:position, :int],
385
332
  [:market_maker, :string], # The exchange hosting this order.
386
- [:operation, :int], # How it should be applied to the market depth:
387
- # 0 = insert this new order into the row identified by :position
388
- # 1 = update the existing order in the row identified by :position
389
- # 2 = delete the existing order at the row identified by :position
390
- [:side, :int], # side of the book: 0 = ask, 1 = bid
333
+ [:operation, :int],
334
+ [:side, :int],
391
335
  [:price, :decimal],
392
336
  [:size, :int]
393
337
 
394
338
  # Called Error in Java code, but in fact this type of messages also
395
339
  # deliver system alerts and additional (non-error) info from TWS.
396
- # It has additional accessors: #code and #message, derived from @data
397
- Alert = def_message 4, [:id, :int], [:code, :int], [:message, :string]
340
+ ErrorMessage = Error = Alert = def_message([4, 2],
341
+ [:error_id, :int],
342
+ [:code, :int],
343
+ [:message, :string])
398
344
  class Alert
399
345
  # Is it an Error message?
400
346
  def error?
@@ -412,347 +358,202 @@ module IB
412
358
  end
413
359
 
414
360
  def to_human
415
- "TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'
416
- } Message #{code}: #{message}"
361
+ "TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'} #{code}: #{message}"
417
362
  end
418
363
  end # class Alert
419
- Error = Alert
420
- ErrorMessage = Alert
421
-
422
- class OpenOrder < AbstractMessage
423
- @message_id = 5
424
-
425
- # TODO: Add id accessor to unify with OrderStatus message
426
- attr_accessor :order, :contract
427
-
428
- def load
429
- super
430
-
431
- @order = Models::Order.new :id => @socket.read_int
432
-
433
- @contract = Models::Contract.build :con_id => @socket.read_string,
434
- :symbol => @socket.read_string,
435
- :sec_type => @socket.read_string,
436
- :expiry => @socket.read_string,
437
- :strike => @socket.read_decimal,
438
- :right => @socket.read_string,
439
- :exchange => @socket.read_string,
440
- :currency => @socket.read_string,
441
- :local_symbol => @socket.read_string
442
-
443
- @order.action = @socket.read_string
444
- @order.total_quantity = @socket.read_int
445
- @order.order_type = @socket.read_string
446
- @order.limit_price = @socket.read_decimal
447
- @order.aux_price = @socket.read_decimal
448
- @order.tif = @socket.read_string
449
- @order.oca_group = @socket.read_string
450
- @order.account = @socket.read_string
451
- @order.open_close = @socket.read_string
452
- @order.origin = @socket.read_int
453
- @order.order_ref = @socket.read_string
454
- @order.client_id = @socket.read_int
455
- @order.perm_id = @socket.read_int
456
- @order.outside_rth = (@socket.read_int == 1)
457
- @order.hidden = (@socket.read_int == 1)
458
- @order.discretionary_amount = @socket.read_decimal
459
- @order.good_after_time = @socket.read_string
460
- @socket.read_string # skip deprecated sharesAllocation field
461
-
462
- @order.fa_group = @socket.read_string
463
- @order.fa_method = @socket.read_string
464
- @order.fa_percentage = @socket.read_string
465
- @order.fa_profile = @socket.read_string
466
- @order.good_till_date = @socket.read_string
467
- @order.rule_80a = @socket.read_string
468
- @order.percent_offset = @socket.read_decimal
469
- @order.settling_firm = @socket.read_string
470
- @order.short_sale_slot = @socket.read_int
471
- @order.designated_location = @socket.read_string
472
- @order.exempt_code = @socket.read_int # skipped in ver 51?
473
- @order.auction_strategy = @socket.read_int
474
- @order.starting_price = @socket.read_decimal
475
- @order.stock_ref_price = @socket.read_decimal
476
- @order.delta = @socket.read_decimal
477
- @order.stock_range_lower = @socket.read_decimal
478
- @order.stock_range_upper = @socket.read_decimal
479
- @order.display_size = @socket.read_int
480
- #@order.rth_only = @socket.read_boolean
481
- @order.block_order = @socket.read_boolean
482
- @order.sweep_to_fill = @socket.read_boolean
483
- @order.all_or_none = @socket.read_boolean
484
- @order.min_quantity = @socket.read_int
485
- @order.oca_type = @socket.read_int
486
- @order.etrade_only = @socket.read_boolean
487
- @order.firm_quote_only = @socket.read_boolean
488
- @order.nbbo_price_cap = @socket.read_decimal
489
- @order.parent_id = @socket.read_int
490
- @order.trigger_method = @socket.read_int
491
- @order.volatility = @socket.read_decimal
492
- @order.volatility_type = @socket.read_int
493
- @order.delta_neutral_order_type = @socket.read_string
494
- @order.delta_neutral_aux_price = @socket.read_decimal
495
-
496
- @order.continuous_update = @socket.read_int
497
- @order.reference_price_type = @socket.read_int
498
- @order.trail_stop_price = @socket.read_decimal
499
- @order.basis_points = @socket.read_decimal
500
- @order.basis_points_type = @socket.read_int
501
- @contract.legs_description = @socket.read_string
502
- @order.scale_init_level_size = @socket.read_int_max
503
- @order.scale_subs_level_size = @socket.read_int_max
504
- @order.scale_price_increment = @socket.read_decimal_max
505
- @order.clearing_account = @socket.read_string
506
- @order.clearing_intent = @socket.read_string
507
- @order.not_held = (@socket.read_int == 1)
508
-
509
- under_comp_present = (@socket.read_int == 1)
510
-
511
- if under_comp_present
512
- @contract.under_comp = true
513
- @contract.under_con_id = @socket.read_int
514
- @contract.under_delta = @socket.read_decimal
515
- @contract.under_price = @socket.read_decimal
516
- end
517
-
518
- @order.algo_strategy = @socket.read_string
519
-
520
- unless @order.algo_strategy.nil? || @order.algo_strategy.empty?
521
- algo_params_count = @socket.read_int
522
- if algo_params_count > 0
523
- @order.algo_params = Hash.new
524
- algo_params_count.times do
525
- tag = @socket.read_string
526
- value = @socket.read_string
527
- @order.algo_params[tag] = value
528
- end
529
- end
530
- end
531
-
532
- @order.what_if = (@socket.read_int == 1)
533
- @order.status = @socket.read_string
534
- @order.init_margin = @socket.read_string
535
- @order.maint_margin = @socket.read_string
536
- @order.equity_with_loan = @socket.read_string
537
- @order.commission = @socket.read_decimal_max # May be nil!
538
- @order.min_commission = @socket.read_decimal_max # May be nil!
539
- @order.max_commission = @socket.read_decimal_max # May be nil!
540
- @order.commission_currency = @socket.read_string
541
- @order.warning_text = @socket.read_string
542
- end
543
-
544
- def to_human
545
- "<OpenOrder: #{@contract.to_human} #{@order.to_human}>"
546
- end
547
- end # OpenOrder
548
-
549
- class PortfolioValue < AbstractMessage
550
- @message_id = 7
551
364
 
552
- attr_accessor :contract
365
+ PortfolioValue = def_message [7, 7],
366
+ [:contract, :con_id, :int],
367
+ [:contract, :symbol, :string],
368
+ [:contract, :sec_type, :string],
369
+ [:contract, :expiry, :string],
370
+ [:contract, :strike, :decimal],
371
+ [:contract, :right, :string],
372
+ [:contract, :multiplier, :string],
373
+ [:contract, :primary_exchange, :string],
374
+ [:contract, :currency, :string],
375
+ [:contract, :local_symbol, :string],
376
+ [:position, :int],
377
+ [:market_price, :decimal],
378
+ [:market_value, :decimal],
379
+ [:average_cost, :decimal],
380
+ [:unrealized_pnl, :decimal_max], # May be nil!
381
+ [:realized_pnl, :decimal_max], # May be nil!
382
+ [:account_name, :string]
383
+ class PortfolioValue
553
384
 
554
385
  def load
555
386
  super
556
-
557
- @contract = Models::Contract.build :con_id => @socket.read_int,
558
- :symbol => @socket.read_string,
559
- :sec_type => @socket.read_string,
560
- :expiry => @socket.read_string,
561
- :strike => @socket.read_decimal,
562
- :right => @socket.read_string,
563
- :multiplier => @socket.read_string,
564
- :primary_exchange => @socket.read_string,
565
- :currency => @socket.read_string,
566
- :local_symbol => @socket.read_string
567
- load_map [:position, :int],
568
- [:market_price, :decimal],
569
- [:market_value, :decimal],
570
- [:average_cost, :decimal],
571
- [:unrealized_pnl, :decimal_max], # May be nil!
572
- [:realized_pnl, :decimal_max], # May be nil!
573
- [:account_name, :string]
387
+ @contract = Models::Contract.build @data[:contract]
574
388
  end
575
389
 
576
390
  def to_human
577
- "<PortfolioValue: #{@contract.to_human} (#{position}): Market #{market_price}" +
391
+ "<PortfolioValue: #{contract.to_human} (#{position}): Market #{market_price}" +
578
392
  " price #{market_value} value; PnL: #{unrealized_pnl} unrealized," +
579
393
  " #{realized_pnl} realized; account #{account_name}>"
580
394
  end
581
-
582
395
  end # PortfolioValue
583
396
 
584
- class ContractData < AbstractMessage
585
- @message_id = 10
586
-
587
- attr_accessor :contract
588
-
397
+ ContractDetails = ContractData =
398
+ def_message([10, 6],
399
+ [:request_id, :int], # request id
400
+ [:contract, :symbol, :string],
401
+ [:contract, :sec_type, :string],
402
+ [:contract, :expiry, :string],
403
+ [:contract, :strike, :decimal],
404
+ [:contract, :right, :string],
405
+ [:contract, :exchange, :string],
406
+ [:contract, :currency, :string],
407
+ [:contract, :local_symbol, :string],
408
+
409
+ [:contract, :market_name, :string], # extended
410
+ [:contract, :trading_class, :string],
411
+ [:contract, :con_id, :int],
412
+ [:contract, :min_tick, :decimal],
413
+ [:contract, :multiplier, :string],
414
+ [:contract, :order_types, :string],
415
+ [:contract, :valid_exchanges, :string],
416
+ [:contract, :price_magnifier, :int],
417
+ [:contract, :under_con_id, :int],
418
+ [:contract, :long_name, :string],
419
+ [:contract, :primary_exchange, :string],
420
+ [:contract, :contract_month, :string],
421
+ [:contract, :industry, :string],
422
+ [:contract, :category, :string],
423
+ [:contract, :subcategory, :string],
424
+ [:contract, :time_zone, :string],
425
+ [:contract, :trading_hours, :string],
426
+ [:contract, :liquid_hours, :string])
427
+
428
+ class ContractData
589
429
  def load
590
430
  super
591
- load_map [:id, :int] # request id
592
-
593
- @contract =
594
- Models::Contract.build :symbol => @socket.read_string,
595
- :sec_type => @socket.read_string,
596
- :expiry => @socket.read_string,
597
- :strike => @socket.read_decimal,
598
- :right => @socket.read_string,
599
- :exchange => @socket.read_string,
600
- :currency => @socket.read_string,
601
- :local_symbol => @socket.read_string,
602
-
603
- :market_name => @socket.read_string,
604
- :trading_class => @socket.read_string,
605
- :con_id => @socket.read_int,
606
- :min_tick => @socket.read_decimal,
607
- :multiplier => @socket.read_string,
608
- :order_types => @socket.read_string,
609
- :valid_exchanges => @socket.read_string,
610
- :price_magnifier => @socket.read_int,
611
-
612
- :under_con_id => @socket.read_int,
613
- :long_name => @socket.read_string,
614
- :primary_exchange => @socket.read_string,
615
- :contract_month => @socket.read_string,
616
- :industry => @socket.read_string,
617
- :category => @socket.read_string,
618
- :subcategory => @socket.read_string,
619
- :time_zone => @socket.read_string,
620
- :trading_hours => @socket.read_string,
621
- :liquid_hours => @socket.read_string
431
+ @contract = Models::Contract.build @data[:contract]
622
432
  end
623
433
  end # ContractData
624
- ContractDetails = ContractData
625
-
626
- class ExecutionData < AbstractMessage
627
- @message_id = 11
628
-
629
- attr_accessor :contract, :execution
630
434
 
435
+ ExecutionData =
436
+ def_message [11, 7],
437
+ # The reqID that was specified previously in the call to reqExecution()
438
+ [:request_id, :int],
439
+ [:execution, :order_id, :int],
440
+ [:contract, :con_id, :int],
441
+ [:contract, :symbol, :string],
442
+ [:contract, :sec_type, :string],
443
+ [:contract, :expiry, :string],
444
+ [:contract, :strike, :decimal],
445
+ [:contract, :right, :string],
446
+ [:contract, :exchange, :string],
447
+ [:contract, :currency, :string],
448
+ [:contract, :local_symbol, :string],
449
+
450
+ [:execution, :exec_id, :string], # Weird format
451
+ [:execution, :time, :string],
452
+ [:execution, :account_name, :string],
453
+ [:execution, :exchange, :string],
454
+ [:execution, :side, :string],
455
+ [:execution, :shares, :int],
456
+ [:execution, :price, :decimal],
457
+ [:execution, :perm_id, :int],
458
+ [:execution, :client_id, :int],
459
+ [:execution, :liquidation, :int],
460
+ [:execution, :cumulative_quantity, :int],
461
+ [:execution, :average_price, :decimal]
462
+
463
+ class ExecutionData
631
464
  def load
632
465
  super
633
- load_map [:id, :int], # request_id
634
- [:order_id, :int]
635
-
636
- @contract =
637
- Models::Contract.build :con_id => @socket.read_int,
638
- :symbol => @socket.read_string,
639
- :sec_type => @socket.read_string,
640
- :expiry => @socket.read_string,
641
- :strike => @socket.read_decimal,
642
- :right => @socket.read_string,
643
- :exchange => @socket.read_string,
644
- :currency => @socket.read_string,
645
- :local_symbol => @socket.read_string
646
- @execution =
647
- Models::Execution.new :order_id => @data[:order_id],
648
- :exec_id => @socket.read_string,
649
- :time => @socket.read_string,
650
- :account_number => @socket.read_string,
651
- :exchange => @socket.read_string,
652
- :side => @socket.read_string,
653
- :shares => @socket.read_int,
654
- :price => @socket.read_decimal,
655
- :perm_id => @socket.read_int,
656
- :client_id => @socket.read_int,
657
- :liquidation => @socket.read_int,
658
- :cumulative_quantity => @socket.read_int,
659
- :average_price => @socket.read_decimal
466
+ @contract = Models::Contract.build @data[:contract]
467
+ @execution = Models::Execution.new @data[:execution]
660
468
  end
661
469
 
662
470
  def to_human
663
- "<ExecutionData: #{contract.to_human}, #{execution}>"
471
+ "<ExecutionData #{request_id}: #{contract.to_human}, #{execution}>"
664
472
  end
665
473
  end # ExecutionData
666
474
 
667
- # HistoricalData contains following @data:
668
- # General:
669
- # :id - The ID of the request to which this is responding
670
- # :count - Number of Historical data points returned (size of :results).
671
- # :results - an Array of Historical Data Bars
672
- # :start_date - beginning of returned Historical data period
673
- # :end_date - end of returned Historical data period
674
- # Each returned Bar in @data[:results] Array contains this data:
675
- # :date - The date-time stamp of the start of the bar. The format is
676
- # determined by the RequestHistoricalData formatDate parameter.
677
- # :open - The bar opening price.
678
- # :high - The high price during the time covered by the bar.
679
- # :low - The low price during the time covered by the bar.
680
- # :close - The bar closing price.
681
- # :volume - The volume during the time covered by the bar.
682
- # :trades - When TRADES historical data is returned, represents number of trades
683
- # that occurred during the time period the bar covers
684
- # :wap - The weighted average price during the time covered by the bar.
685
- # :has_gaps - Whether or not there are gaps in the data.
686
- class HistoricalData < AbstractMessage
687
- @message_id = 17
688
-
475
+ BondContractData =
476
+ def_message [18, 4],
477
+ [:request_id, :int], # request id
478
+ [:contract, :symbol, :string],
479
+ [:contract, :sec_type, :string],
480
+ [:contract, :cusip, :string],
481
+ [:contract, :coupon, :decimal],
482
+ [:contract, :maturity, :string],
483
+ [:contract, :issue_date, :string],
484
+ [:contract, :ratings, :string],
485
+ [:contract, :bond_type, :string],
486
+ [:contract, :coupon_type, :string],
487
+ [:contract, :convertible, :boolean],
488
+ [:contract, :callable, :boolean],
489
+ [:contract, :puttable, :boolean],
490
+ [:contract, :desc_append, :string],
491
+ [:contract, :exchange, :string],
492
+ [:contract, :currency, :string],
493
+ [:contract, :market_name, :string], # extended
494
+ [:contract, :trading_class, :string],
495
+ [:contract, :con_id, :int],
496
+ [:contract, :min_tick, :decimal],
497
+ [:contract, :order_types, :string],
498
+ [:contract, :valid_exchanges, :string],
499
+ [:contract, :valid_next_option_date, :string],
500
+ [:contract, :valid_next_option_type, :string],
501
+ [:contract, :valid_next_option_partial, :string],
502
+ [:contract, :notes, :string],
503
+ [:contract, :long_name, :string]
504
+
505
+ class BondContractData
689
506
  def load
690
507
  super
691
- load_map [:id, :int],
692
- [:start_date, :string],
693
- [:end_date, :string],
694
- [:count, :int]
695
-
696
- @data[:results] = Array.new(@data[:count]) do |index|
697
- Models::Bar.new :date => @socket.read_string,
698
- :open => @socket.read_decimal,
699
- :high => @socket.read_decimal,
700
- :low => @socket.read_decimal,
701
- :close => @socket.read_decimal,
702
- :volume => @socket.read_int,
703
- :wap => @socket.read_decimal,
704
- :has_gaps => @socket.read_string,
705
- :trades => @socket.read_int
706
- end
508
+ @contract = Models::Contract.build @data[:contract]
707
509
  end
510
+ end # BondContractData
708
511
 
709
- def to_human
710
- "<HistoricalData: req: #{id}, #{item_count} items, #{start_date} to #{end_date}>"
512
+ # The server sends this message upon accepting a Delta-Neutral DN RFQ
513
+ # - see API Reference p. 26
514
+ DeltaNeutralValidation = def_message 56,
515
+ [:request_id, :int],
516
+ [:contract, :under_con_id, :int],
517
+ [:contract, :under_delta, :decimal],
518
+ [:contract, :under_price, :decimal]
519
+ class DeltaNeutralValidation
520
+ def load
521
+ super
522
+ @contract = Models::Contract.build @data[:contract].merge(:under_comp => true)
711
523
  end
712
- end # HistoricalData
713
-
714
- class BondContractData < AbstractMessage
715
- @message_id = 18
716
-
717
- attr_accessor :contract
524
+ end # DeltaNeutralValidation
718
525
 
526
+ # RealTimeBar contains following @data:
527
+ # :request_id - The ID of the *request* to which this is responding
528
+ # :time - The date-time stamp of the start of the bar. The format is offset in
529
+ # seconds from the beginning of 1970, same format as the UNIX epoch time
530
+ # :bar - received RT Bar
531
+ RealTimeBar = def_message 50,
532
+ [:request_id, :int],
533
+ [:bar, :time, :int],
534
+ [:bar, :open, :decimal],
535
+ [:bar, :high, :decimal],
536
+ [:bar, :low, :decimal],
537
+ [:bar, :close, :decimal],
538
+ [:bar, :volume, :int],
539
+ [:bar, :wap, :decimal],
540
+ [:bar, :trades, :int]
541
+ class RealTimeBar
719
542
  def load
720
543
  super
721
- load_map [:id, :int] # request id
722
-
723
- @contract =
724
- Models::Contract.build :symbol => @socket.read_string,
725
- :sec_type => @socket.read_string,
726
- :cusip => @socket.read_string,
727
- :coupon => @socket.read_decimal,
728
- :maturity => @socket.read_string,
729
- :issue_date => @socket.read_string,
730
- :ratings => @socket.read_string,
731
- :bond_type => @socket.read_string,
732
- :coupon_type => @socket.read_string,
733
- :convertible => @socket.read_boolean,
734
- :callable => @socket.read_boolean,
735
- :puttable => @socket.read_boolean,
736
- :desc_append => @socket.read_string,
737
- :exchange => @socket.read_string,
738
- :currency => @socket.read_string,
739
- :market_name => @socket.read_string,
740
- :trading_class => @socket.read_string,
741
- :con_id => @socket.read_int,
742
- :min_tick => @socket.read_decimal,
743
- :order_types => @socket.read_string,
744
- :valid_exchanges => @socket.read_string,
745
- :valid_next_option_date => @socket.read_string,
746
- :valid_next_option_type => @socket.read_string,
747
- :valid_next_option_partial => @socket.read_string,
748
- :notes => @socket.read_string,
749
- :long_name => @socket.read_string
544
+ @bar = Models::Bar.new @data[:bar]
750
545
  end
751
- end # BondContractData
546
+
547
+ def to_human
548
+ "<RealTimeBar: #{request_id} #{time}, #{bar}>"
549
+ end
550
+ end # RealTimeBar
551
+
552
+ ### Messages with really complicated message loading logics (cycles, conditions)
752
553
 
753
554
  # This method receives the requested market scanner data results.
754
555
  # ScannerData contains following @data:
755
- # :id - The ID of the request to which this row is responding
556
+ # :request_id - The ID of the request to which this row is responding
756
557
  # :count - Number of data points returned (size of :results).
757
558
  # :results - an Array of Hashes, each hash contains a set of
758
559
  # data about one scanned Contract:
@@ -761,15 +562,16 @@ module IB
761
562
  # :benchmark - Varies based on query.
762
563
  # :projection - Varies based on query.
763
564
  # :legs - Describes combo legs when scan is returning EFP.
764
- class ScannerData < AbstractMessage
765
- @message_id = 20
565
+ ScannerData = def_message [20, 3],
566
+ [:request_id, :int], # request id
567
+ [:count, :int]
568
+ class ScannerData
569
+ attr_accessor :results
766
570
 
767
571
  def load
768
572
  super
769
- load_map [:id, :int],
770
- [:count, :int]
771
573
 
772
- @data[:results] = Array.new(@data[:count]) do |index|
574
+ @results = Array.new(@data[:count]) do |index|
773
575
  {:rank => @socket.read_int,
774
576
  :contract => Contract.build(:con_id => @socket.read_int,
775
577
  :symbol => @socket.read_str,
@@ -787,63 +589,189 @@ module IB
787
589
  :projection => @socket.read_str,
788
590
  :legs => @socket.read_str,
789
591
  }
790
- #eWrapper().scannerData(tickerId, rank, contract, distance,
791
- # benchmark, projection, legsStr);
792
-
793
592
  end
794
-
795
- #eWrapper().scannerDataEnd(tickerId);
796
593
  end
797
594
  end # ScannerData
798
595
 
799
596
  # HistoricalData contains following @data:
800
- # :id - The ID of the *request* to which this is responding
801
- # :time - The date-time stamp of the start of the bar. The format is offset in
802
- # seconds from the beginning of 1970, same format as the UNIX epoch time
803
- # :bar - received RT Bar
804
- class RealTimeBar < AbstractMessage
805
- @message_id = 50
597
+ # General:
598
+ # :request_id - The ID of the request to which this is responding
599
+ # :count - Number of Historical data points returned (size of :results).
600
+ # :results - an Array of Historical Data Bars
601
+ # :start_date - beginning of returned Historical data period
602
+ # :end_date - end of returned Historical data period
603
+ # Each returned Bar in @data[:results] Array contains this data:
604
+ # :date - The date-time stamp of the start of the bar. The format is
605
+ # determined by the RequestHistoricalData formatDate parameter.
606
+ # :open - The bar opening price.
607
+ # :high - The high price during the time covered by the bar.
608
+ # :low - The low price during the time covered by the bar.
609
+ # :close - The bar closing price.
610
+ # :volume - The volume during the time covered by the bar.
611
+ # :trades - When TRADES historical data is returned, represents number of trades
612
+ # that occurred during the time period the bar covers
613
+ # :wap - The weighted average price during the time covered by the bar.
614
+ # :has_gaps - Whether or not there are gaps in the data.
806
615
 
807
- attr_accessor :bar
616
+ HistoricalData = def_message [17, 3],
617
+ [:request_id, :int],
618
+ [:start_date, :string],
619
+ [:end_date, :string],
620
+ [:count, :int]
621
+ class HistoricalData
622
+ attr_accessor :results
808
623
 
809
624
  def load
810
625
  super
811
- load_map [:id, :int],
812
- [:time, :int] # long!
813
-
814
- @bar = Models::Bar.new :date => Time.at(@data[:time]),
815
- :open => @socket.read_decimal,
816
- :high => @socket.read_decimal,
817
- :low => @socket.read_decimal,
818
- :close => @socket.read_decimal,
819
- :volume => @socket.read_int,
820
- :wap => @socket.read_decimal,
821
- :trades => @socket.read_int
626
+
627
+ @results = Array.new(@data[:count]) do |index|
628
+ Models::Bar.new :time => @socket.read_string,
629
+ :open => @socket.read_decimal,
630
+ :high => @socket.read_decimal,
631
+ :low => @socket.read_decimal,
632
+ :close => @socket.read_decimal,
633
+ :volume => @socket.read_int,
634
+ :wap => @socket.read_decimal,
635
+ :has_gaps => @socket.read_string,
636
+ :trades => @socket.read_int
637
+ end
822
638
  end
823
639
 
824
640
  def to_human
825
- "<RealTimeBar: req: #{id}, #{bar}>"
641
+ "<HistoricalData: #{request_id}, #{count} items, #{start_date} to #{end_date}>"
826
642
  end
827
- end # RealTimeBar
828
- RealTimeBars = RealTimeBar
643
+ end # HistoricalData
829
644
 
830
- # The server sends this message upon accepting a Delta-Neutral DN RFQ
831
- # - see API Reference p. 26
832
- class DeltaNeutralValidation < AbstractMessage
833
- @message_id = 56
834
645
 
835
- attr_accessor :contract
646
+ OpenOrder =
647
+ def_message [5, 23],
648
+ # The reqID that was specified previously in the call to reqExecution()
649
+ [:order, :order_id, :int],
650
+
651
+ [:contract, :con_id, :int],
652
+ [:contract, :symbol, :string],
653
+ [:contract, :sec_type, :string],
654
+ [:contract, :expiry, :string],
655
+ [:contract, :strike, :decimal],
656
+ [:contract, :right, :string],
657
+ [:contract, :exchange, :string],
658
+ [:contract, :currency, :string],
659
+ [:contract, :local_symbol, :string],
660
+
661
+ [:order, :action, :string],
662
+ [:order, :total_quantity, :int],
663
+ [:order, :order_type, :string],
664
+ [:order, :limit_price, :decimal],
665
+ [:order, :aux_price, :decimal],
666
+ [:order, :tif, :string],
667
+ [:order, :oca_group, :string],
668
+ [:order, :account, :string],
669
+ [:order, :open_close, :string],
670
+ [:order, :origin, :int],
671
+ [:order, :order_ref, :string],
672
+ [:order, :client_id, :int],
673
+ [:order, :perm_id, :int],
674
+ [:order, :outside_rth, :boolean], # (@socket.read_int == 1)
675
+ [:order, :hidden, :boolean], # (@socket.read_int == 1)
676
+ [:order, :discretionary_amount, :decimal],
677
+ [:order, :good_after_time, :string],
678
+ [:skip, :string], # skip deprecated sharesAllocation field
679
+
680
+ [:order, :fa_group, :string],
681
+ [:order, :fa_method, :string],
682
+ [:order, :fa_percentage, :string],
683
+ [:order, :fa_profile, :string],
684
+ [:order, :good_till_date, :string],
685
+ [:order, :rule_80a, :string],
686
+ [:order, :percent_offset, :decimal],
687
+ [:order, :settling_firm, :string],
688
+ [:order, :short_sale_slot, :int],
689
+ [:order, :designated_location, :string],
690
+ [:order, :exempt_code, :int], # skipped in ver 51?
691
+ [:order, :auction_strategy, :int],
692
+ [:order, :starting_price, :decimal],
693
+ [:order, :stock_ref_price, :decimal],
694
+ [:order, :delta, :decimal],
695
+ [:order, :stock_range_lower, :decimal],
696
+ [:order, :stock_range_upper, :decimal],
697
+ [:order, :display_size, :int],
698
+ #@order.rth_only = @socket.read_boolean
699
+ [:order, :block_order, :boolean],
700
+ [:order, :sweep_to_fill, :boolean],
701
+ [:order, :all_or_none, :boolean],
702
+ [:order, :min_quantity, :int],
703
+ [:order, :oca_type, :int],
704
+ [:order, :etrade_only, :boolean],
705
+ [:order, :firm_quote_only, :boolean],
706
+ [:order, :nbbo_price_cap, :decimal],
707
+ [:order, :parent_id, :int],
708
+ [:order, :trigger_method, :int],
709
+ [:order, :volatility, :decimal],
710
+ [:order, :volatility_type, :int],
711
+ [:order, :delta_neutral_order_type, :string],
712
+ [:order, :delta_neutral_aux_price, :decimal],
713
+
714
+ [:order, :continuous_update, :int],
715
+ [:order, :reference_price_type, :int],
716
+ [:order, :trail_stop_price, :decimal],
717
+ [:order, :basis_points, :decimal],
718
+ [:order, :basis_points_type, :int],
719
+ [:contract, :legs_description, :string],
720
+ [:order, :scale_init_level_size, :int_max],
721
+ [:order, :scale_subs_level_size, :int_max],
722
+ [:order, :scale_price_increment, :decimal_max],
723
+ [:order, :clearing_account, :string],
724
+ [:order, :clearing_intent, :string],
725
+ [:order, :not_held, :boolean] # (@socket.read_int == 1)
726
+
727
+ class OpenOrder
836
728
 
837
729
  def load
838
730
  super
839
- load_map [:id, :int] # request id
840
731
 
841
- @contract = Models::Contract.build :under_comp => true,
842
- :under_con_id => @socket.read_int,
843
- :under_delta => @socket.read_decimal,
844
- :under_price => @socket.read_decimal
732
+ load_map [:contract, :under_comp, :boolean] # (@socket.read_int == 1)
733
+
734
+ if @data[:contract][:under_comp]
735
+ load_map [:contract, :under_con_id, :int],
736
+ [:contract, :under_delta, :decimal],
737
+ [:contract, :under_price, :decimal]
738
+ end
739
+
740
+ load_map [:order, :algo_strategy, :string]
741
+
742
+ unless @data[:order][:algo_strategy].nil? || @data[:order][:algo_strategy].empty?
743
+ load_map [:algo_params_count, :int]
744
+ if @data[:algo_params_count] > 0
745
+ @data[:order][:algo_params] = Hash.new
746
+ @data[:algo_params_count].times do
747
+ tag = @socket.read_string
748
+ value = @socket.read_string
749
+ @data[:order][:algo_params][tag] = value
750
+ end
751
+ end
752
+ end
753
+
754
+ load_map [:order, :what_if, :boolean], # (@socket.read_int == 1)
755
+ [:order, :status, :string],
756
+ [:order, :init_margin, :string],
757
+ [:order, :maint_margin, :string],
758
+ [:order, :equity_with_loan, :string],
759
+ [:order, :commission, :decimal_max], # May be nil!
760
+ [:order, :min_commission, :decimal_max], # May be nil!
761
+ [:order, :max_commission, :decimal_max], # May be nil!
762
+ [:order, :commission_currency, :string],
763
+ [:order, :warning_text, :string]
764
+
765
+ @order = Models::Order.new @data[:order]
766
+ @contract = Models::Contract.build @data[:contract]
845
767
  end
846
- end # DeltaNeutralValidation
768
+
769
+ def to_human
770
+ "<OpenOrder: #{@contract.to_human} #{@order.to_human}>"
771
+ end
772
+ end
773
+
774
+ # OpenOrder
847
775
 
848
776
  Table = Hash.new
849
777
  Classes.each { |msg_class| Table[msg_class.message_id] = msg_class }