ib-ruby 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/HISTORY +4 -0
  2. data/README.md +25 -17
  3. data/VERSION +1 -1
  4. data/bin/account_info +1 -1
  5. data/bin/cancel_orders +2 -1
  6. data/bin/contract_details +3 -2
  7. data/bin/depth_of_market +1 -1
  8. data/bin/historic_data +1 -1
  9. data/bin/historic_data_cli +57 -104
  10. data/bin/list_orders +4 -3
  11. data/bin/market_data +1 -1
  12. data/bin/option_data +1 -1
  13. data/bin/place_combo_order +63 -0
  14. data/bin/place_order +2 -4
  15. data/bin/template +1 -1
  16. data/bin/{generic_data.rb → tick_data} +3 -1
  17. data/bin/time_and_sales +1 -1
  18. data/lib/ib-ruby.rb +1 -0
  19. data/lib/ib-ruby/connection.rb +68 -68
  20. data/lib/ib-ruby/errors.rb +28 -0
  21. data/lib/ib-ruby/extensions.rb +7 -0
  22. data/lib/ib-ruby/messages.rb +1 -0
  23. data/lib/ib-ruby/messages/abstract_message.rb +16 -11
  24. data/lib/ib-ruby/messages/incoming.rb +125 -329
  25. data/lib/ib-ruby/messages/incoming/open_order.rb +193 -0
  26. data/lib/ib-ruby/messages/incoming/ticks.rb +131 -0
  27. data/lib/ib-ruby/messages/outgoing.rb +44 -45
  28. data/lib/ib-ruby/models/combo_leg.rb +16 -1
  29. data/lib/ib-ruby/models/contract.rb +18 -10
  30. data/lib/ib-ruby/models/contract/bag.rb +1 -7
  31. data/lib/ib-ruby/models/execution.rb +2 -1
  32. data/lib/ib-ruby/models/model.rb +1 -1
  33. data/lib/ib-ruby/models/order.rb +116 -56
  34. data/lib/ib-ruby/socket.rb +24 -3
  35. data/spec/account_helper.rb +2 -1
  36. data/spec/ib-ruby/messages/outgoing_spec.rb +1 -1
  37. data/spec/ib-ruby/models/combo_leg_spec.rb +0 -1
  38. data/spec/integration/account_info_spec.rb +2 -2
  39. data/spec/integration/contract_info_spec.rb +4 -4
  40. data/spec/integration/depth_data_spec.rb +3 -3
  41. data/spec/integration/historic_data_spec.rb +1 -1
  42. data/spec/integration/market_data_spec.rb +4 -4
  43. data/spec/integration/option_data_spec.rb +1 -1
  44. data/spec/integration/orders/combo_spec.rb +51 -0
  45. data/spec/integration/orders/execution_spec.rb +15 -8
  46. data/spec/integration/orders/placement_spec.rb +46 -72
  47. data/spec/integration/orders/valid_ids_spec.rb +6 -6
  48. data/spec/integration_helper.rb +0 -79
  49. data/spec/order_helper.rb +153 -0
  50. metadata +13 -4
@@ -6,3 +6,10 @@ class Time
6
6
  end
7
7
  end # Time
8
8
 
9
+ ### Patching Object#error in ib-ruby/errors
10
+ # def error message, type=:standard
11
+
12
+ ### Patching Object#log, #default_logger= in ib-ruby/logger
13
+ # def default_logger
14
+ # def default_logger= logger
15
+ # def log *args
@@ -8,4 +8,5 @@ module IB
8
8
 
9
9
  IncomingMessages = Messages::Incoming # Legacy alias
10
10
  OutgoingMessages = Messages::Outgoing # Legacy alias
11
+
11
12
  end
@@ -16,14 +16,12 @@ module IB
16
16
  # @message_type - Symbol: message type (e.g. :OpenOrderEnd)
17
17
  #
18
18
  # Instance attributes (at least):
19
- # version - int: current version of message format.
19
+ # @version - int: current version of message format.
20
20
  # @data - Hash of actual data read from a stream.
21
- #
22
- # Override the load(socket) method in your subclass to do actual reading into @data.
23
21
  class AbstractMessage
24
22
 
25
23
  # Class methods
26
- def self.data_map # Data keys (with types?)
24
+ def self.data_map # Map for converting between structured message and raw data
27
25
  @data_map ||= []
28
26
  end
29
27
 
@@ -61,16 +59,18 @@ module IB
61
59
 
62
60
  end # class AbstractMessage
63
61
 
64
- # Macro that defines short message classes using a one-liner
65
- # id_version is either a [message_id, version] pair or just message_id (version 1)
62
+ # Macro that defines short message classes using a one-liner.
63
+ # First arg is either a [message_id, version] pair or just message_id (version 1)
66
64
  # data_map contains instructions for processing @data Hash. Format:
67
65
  # Incoming messages: [field, type] or [group, field, type]
68
- # Outgoing messages: [field, default] or [field, method, [args]]
69
- def def_message id_version, *data_map, &to_human
66
+ # Outgoing messages: field, [field, default] or [field, method, [args]]
67
+ def def_message message_id_version, *data_map, &to_human
70
68
  base = data_map.first.is_a?(Class) ? data_map.shift : self::AbstractMessage
71
- Class.new(base) do
72
- @message_id, @version = id_version
73
- @version ||= 1
69
+ message_id, version = message_id_version
70
+
71
+ # Define new message class
72
+ message_class = Class.new(base) do
73
+ @message_id, @version = message_id, version || 1
74
74
  @data_map = data_map
75
75
 
76
76
  @data_map.each do |(name, _, type_args)|
@@ -83,6 +83,11 @@ module IB
83
83
 
84
84
  define_method(:to_human, &to_human) if to_human
85
85
  end
86
+
87
+ # Add defined message class to Classes Hash keyed by its message_id
88
+ self::Classes[message_id] = message_class
89
+
90
+ message_class
86
91
  end
87
92
 
88
93
  end # module Messages
@@ -11,85 +11,106 @@ require 'ib-ruby/messages/abstract_message'
11
11
  module IB
12
12
  module Messages
13
13
 
14
- # Incoming IB messages
14
+ # Incoming IB messages (received from TWS/Gateway)
15
15
  module Incoming
16
16
  extend Messages # def_message macros
17
17
 
18
- Classes = Array.new
18
+ # Container for specific message classes, keyed by their message_ids
19
+ Classes = {}
19
20
 
20
21
  class AbstractMessage < IB::Messages::AbstractMessage
21
22
 
22
- def self.inherited(by)
23
- super(by)
24
- Classes.push(by)
25
- end
26
-
27
23
  def version # Per message, received messages may have the different versions
28
24
  @data[:version]
29
25
  end
30
26
 
31
- # Read incoming message from given socket or instantiate with given data
32
- def initialize socket_or_data
27
+ def check_version actual, expected
28
+ unless actual == expected || expected.is_a?(Array) && expected.include?(actual)
29
+ error "Unsupported version #{actual} received, expected #{expected}"
30
+ end
31
+ end
32
+
33
+ # Create incoming message from a given source (IB server or data Hash)
34
+ def initialize source
33
35
  @created_at = Time.now
34
- if socket_or_data.is_a?(Hash)
35
- @data = socket_or_data
36
- else
37
- @data = {}
38
- @socket = socket_or_data
39
- self.load
40
- @socket = nil
36
+ if source[:socket] # Source is a server
37
+ @server = source
38
+ @data = Hash.new
39
+ begin
40
+ self.load
41
+ rescue => e
42
+ error "Reading #{self.class}: #{e.class}: #{e.message}", :load, e.backtrace
43
+ ensure
44
+ @server = nil
45
+ end
46
+ else # Source is a @data Hash
47
+ @data = source
41
48
  end
42
49
  end
43
50
 
51
+ def socket
52
+ @server[:socket]
53
+ end
54
+
44
55
  # Every message loads received message version first
56
+ # Override the load method in your subclass to do actual reading into @data.
45
57
  def load
46
- @data[:version] = @socket.read_int
58
+ @data[:version] = socket.read_int
47
59
 
48
- if @data[:version] != self.class.version
49
- raise "Unsupported version #{@data[:version]} of #{self.class} received"
50
- end
60
+ check_version @data[:version], self.class.version
51
61
 
52
62
  load_map *self.class.data_map
53
63
  end
54
64
 
55
- # Load @data from the socket according to the given map.
65
+ # Load @data from the socket according to the given data map.
56
66
  #
57
67
  # map is a series of Arrays in the format of
58
- # [ [ :name, :type ],
59
- # [ :group, :name, :type] ]
68
+ # [ :name, :type ], [ :group, :name, :type]
60
69
  # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
61
70
  # group is used to lump together aggregates, such as Contract or Order fields
62
71
  def load_map(*map)
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
+ map.each do |instruction|
73
+ # We determine the function of the first element
74
+ head = instruction.first
75
+ case head
76
+ when Integer # >= Version condition: [ min_version, [map]]
77
+ load_map *instruction.drop(1) if version >= head
78
+
79
+ when Proc # Callable condition: [ condition, [map]]
80
+ load_map *instruction.drop(1) if head.call
81
+
82
+ when true # Pre-condition already succeeded!
83
+ load_map *instruction.drop(1)
84
+
85
+ when nil, false # Pre-condition already failed! Do nothing...
86
+
87
+ when Symbol # Normal map
88
+ group, name, type, block =
89
+ if instruction[2].nil? || instruction[2].is_a?(Proc)
90
+ [nil] + instruction # No group, [ :name, :type, (:block) ]
91
+ else
92
+ instruction # [ :group, :name, :type, (:block)]
93
+ end
94
+
95
+ data = socket.__send__("read_#{type}", &block)
96
+ if group
97
+ @data[group] ||= {}
98
+ @data[group][name] = data
99
+ else
100
+ @data[name] = data
101
+ end
102
+ else
103
+ error "Unrecognized instruction #{instruction}"
72
104
  end
73
105
  end
74
106
  end
75
- end # class AbstractMessage
76
-
77
- class AbstractTick < AbstractMessage
78
- # Returns Symbol with a meaningful name for received tick type
79
- def type
80
- TICK_TYPES[@data[:tick_type]]
81
- end
82
-
83
- def to_human
84
- "<#{self.message_type} #{type}:" +
85
- @data.map do |key, value|
86
- " #{key} #{value}" unless [:version, :ticker_id, :tick_type].include?(key)
87
- end.compact.join(',') + " >"
88
- end
89
107
  end
90
108
 
109
+ # class AbstractMessage
110
+
91
111
  ### Actual message classes (short definitions):
92
- #:status - String: Displays the order status. Possible values include:
112
+
113
+ # :status - String: Displays the order status. Possible values include:
93
114
  # � PendingSubmit - indicates that you have transmitted the order, but
94
115
  # have not yet received confirmation that it has been accepted by the
95
116
  # order destination. NOTE: This order status is NOT sent back by TWS
@@ -133,7 +154,6 @@ module IB
133
154
  " id/perm: #{order_id}/#{perm_id}>"
134
155
  end
135
156
 
136
-
137
157
  AccountValue = def_message([6, 2], [:key, :string],
138
158
  [:value, :string],
139
159
  [:currency, :string],
@@ -178,127 +198,25 @@ module IB
178
198
 
179
199
  # Receive Reuters global fundamental market data. There must be a subscription to
180
200
  # Reuters Fundamental set up in Account Management before you can receive this data.
181
- FundamentalData = def_message 50, [:request_id, :int], # request_id
182
- [:data, :string]
201
+ FundamentalData = def_message 50, [:request_id, :int], [:data, :string]
183
202
 
184
- ContractDataEnd = def_message 52, [:request_id, :int] # request_id
203
+ ContractDataEnd = def_message 52, [:request_id, :int]
185
204
 
186
205
  OpenOrderEnd = def_message 53
187
206
 
188
207
  AccountDownloadEnd = def_message 54, [:account_name, :string]
189
208
 
190
- ExecutionDataEnd = def_message 55, [:request_id, :int] # request_id
191
-
192
- TickSnapshotEnd = def_message 57, [:ticker_id, :int]
193
-
194
- ### Actual message classes (long definitions):
195
-
196
- # The IB code seems to dispatch up to two wrapped objects for this message, a tickPrice
197
- # and sometimes a tickSize, which seems to be identical to the TICK_SIZE object.
198
- #
199
- # Important note from
200
- # http://chuckcaplan.com/twsapi/index.php/void%20tickPrice%28%29 :
201
- #
202
- # "The low you get is NOT the low for the day as you'd expect it
203
- # to be. It appears IB calculates the low based on all
204
- # transactions after 4pm the previous day. The most inaccurate
205
- # results occur when the stock moves up in the 4-6pm aftermarket
206
- # on the previous day and then gaps open upward in the
207
- # morning. The low you receive from TWS can be easily be several
208
- # points different from the actual 9:30am-4pm low for the day in
209
- # cases like this. If you require a correct traded low for the
210
- # day, you can't get it from the TWS API. One possible source to
211
- # help build the right data would be to compare against what Yahoo
212
- # lists on finance.yahoo.com/q?s=ticker under the "Day's Range"
213
- # statistics (be careful here, because Yahoo will use anti-Denial
214
- # of Service techniques to hang your connection if you try to
215
- # request too many bytes in a short period of time from them). For
216
- # most purposes, a good enough approach would start by replacing
217
- # the TWS low for the day with Yahoo's day low when you first
218
- # start watching a stock ticker; let's call this time T. Then,
219
- # update your internal low if the bid or ask tick you receive is
220
- # lower than that for the remainder of the day. You should check
221
- # against Yahoo again at time T+20min to handle the occasional
222
- # case where the stock set a new low for the day in between
223
- # T-20min (the real time your original quote was from, taking into
224
- # account the delay) and time T. After that you should have a
225
- # correct enough low for the rest of the day as long as you keep
226
- # updating based on the bid/ask. It could still get slightly off
227
- # in a case where a short transaction setting a new low appears in
228
- # between ticks of data that TWS sends you. The high is probably
229
- # distorted in the same way the low is, which would throw your
230
- # results off if the stock traded after-hours and gapped down. It
231
- # should be corrected in a similar way as described above if this
232
- # is important to you."
233
- #
234
- # IB then emits at most 2 events on eWrapper:
235
- # tickPrice( tickerId, tickType, price, canAutoExecute)
236
- # tickSize( tickerId, sizeTickType, size)
237
- TickPrice = def_message [1, 6], AbstractTick,
238
- [:ticker_id, :int],
239
- [:tick_type, :int],
240
- [:price, :decimal],
241
- [:size, :int],
242
- [:can_auto_execute, :int]
243
-
244
- TickSize = def_message [2, 6], AbstractTick,
245
- [:ticker_id, :int],
246
- [:tick_type, :int],
247
- [:size, :int]
248
-
249
- TickGeneric = def_message [45, 6], AbstractTick,
250
- [:ticker_id, :int],
251
- [:tick_type, :int],
252
- [:value, :decimal]
253
-
254
- TickString = def_message [46, 6], AbstractTick,
255
- [:ticker_id, :int],
256
- [:tick_type, :int],
257
- [:value, :string]
258
-
259
- TickEFP = def_message [47, 6], AbstractTick,
260
- [:ticker_id, :int],
261
- [:tick_type, :int],
262
- [:basis_points, :decimal],
263
- [:formatted_basis_points, :string],
264
- [:implied_futures_price, :decimal],
265
- [:hold_days, :int],
266
- [:dividend_impact, :decimal],
267
- [:dividends_to_expiry, :decimal]
268
-
269
- # This message is received when the market in an option or its underlier moves.
270
- # TWS�s option model volatilities, prices, and deltas, along with the present
271
- # value of dividends expected on that options underlier are received.
272
- # TickOption message contains following @data:
273
- # :ticker_id - Id that was specified previously in the call to reqMktData()
274
- # :tick_type - Specifies the type of option computation (see TICK_TYPES).
275
- # :implied_volatility - The implied volatility calculated by the TWS option
276
- # modeler, using the specified :tick_type value.
277
- # :delta - The option delta value.
278
- # :option_price - The option price.
279
- # :pv_dividend - The present value of dividends expected on the options underlier
280
- # :gamma - The option gamma value.
281
- # :vega - The option vega value.
282
- # :theta - The option theta value.
283
- # :under_price - The price of the underlying.
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
209
+ ExecutionDataEnd = def_message 55, [:request_id, :int]
210
+
211
+ MarketDataType = def_message 58, [:request_id, :int], [:market_data_type, :int]
212
+
213
+ CommissionReport =
214
+ def_message 59, [:exec_id, :int],
215
+ [:commission, :decimal], # Commission amount.
216
+ [:currency, :int], # Commission currency
217
+ [:realized_pnl, :decimal],
218
+ [:yield, :decimal],
219
+ [:yield_redemption_date, :int]
302
220
 
303
221
  MarketDepth =
304
222
  def_message 12, [:request_id, :int],
@@ -433,7 +351,7 @@ module IB
433
351
  end # ContractData
434
352
 
435
353
  ExecutionData =
436
- def_message [11, 7],
354
+ def_message [11, 8],
437
355
  # The reqID that was specified previously in the call to reqExecution()
438
356
  [:request_id, :int],
439
357
  [:execution, :order_id, :int],
@@ -463,6 +381,11 @@ module IB
463
381
  class ExecutionData
464
382
  def load
465
383
  super
384
+
385
+ # As of client v.53, we can receive orderRef in ExecutionData
386
+ load_map [proc { | | @server[:client_version] >= 53 },
387
+ [:execution, :order_ref, :string]
388
+ ]
466
389
  @contract = Models::Contract.build @data[:contract]
467
390
  @execution = Models::Execution.new @data[:execution]
468
391
  end
@@ -470,11 +393,12 @@ module IB
470
393
  def to_human
471
394
  "<ExecutionData #{request_id}: #{contract.to_human}, #{execution}>"
472
395
  end
396
+
473
397
  end # ExecutionData
474
398
 
475
399
  BondContractData =
476
400
  def_message [18, 4],
477
- [:request_id, :int], # request id
401
+ [:request_id, :int],
478
402
  [:contract, :symbol, :string],
479
403
  [:contract, :sec_type, :string],
480
404
  [:contract, :cusip, :string],
@@ -571,23 +495,23 @@ module IB
571
495
  def load
572
496
  super
573
497
 
574
- @results = Array.new(@data[:count]) do |index|
575
- {:rank => @socket.read_int,
576
- :contract => Contract.build(:con_id => @socket.read_int,
577
- :symbol => @socket.read_str,
578
- :sec_type => @socket.read_str,
579
- :expiry => @socket.read_str,
580
- :strike => @socket.read_decimal,
581
- :right => @socket.read_str,
582
- :exchange => @socket.read_str,
583
- :currency => @socket.read_str,
584
- :local_symbol => @socket.read_str,
585
- :market_name => @socket.read_str,
586
- :trading_class => @socket.read_str),
587
- :distance => @socket.read_str,
588
- :benchmark => @socket.read_str,
589
- :projection => @socket.read_str,
590
- :legs => @socket.read_str,
498
+ @results = Array.new(@data[:count]) do |_|
499
+ {:rank => socket.read_int,
500
+ :contract => Contract.build(:con_id => socket.read_int,
501
+ :symbol => socket.read_str,
502
+ :sec_type => socket.read_str,
503
+ :expiry => socket.read_str,
504
+ :strike => socket.read_decimal,
505
+ :right => socket.read_str,
506
+ :exchange => socket.read_str,
507
+ :currency => socket.read_str,
508
+ :local_symbol => socket.read_str,
509
+ :market_name => socket.read_str,
510
+ :trading_class => socket.read_str),
511
+ :distance => socket.read_str,
512
+ :benchmark => socket.read_str,
513
+ :projection => socket.read_str,
514
+ :legs => socket.read_str,
591
515
  }
592
516
  end
593
517
  end
@@ -624,16 +548,16 @@ module IB
624
548
  def load
625
549
  super
626
550
 
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
551
+ @results = Array.new(@data[:count]) do |_|
552
+ Models::Bar.new :time => socket.read_string,
553
+ :open => socket.read_decimal,
554
+ :high => socket.read_decimal,
555
+ :low => socket.read_decimal,
556
+ :close => socket.read_decimal,
557
+ :volume => socket.read_int,
558
+ :wap => socket.read_decimal,
559
+ :has_gaps => socket.read_string,
560
+ :trades => socket.read_int
637
561
  end
638
562
  end
639
563
 
@@ -642,147 +566,17 @@ module IB
642
566
  end
643
567
  end # HistoricalData
644
568
 
645
-
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
728
-
729
- def load
730
- super
731
-
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]
767
- end
768
-
769
- def to_human
770
- "<OpenOrder: #{@contract.to_human} #{@order.to_human}>"
771
- end
772
- end
773
-
774
- # OpenOrder
775
-
776
- Table = Hash.new
777
- Classes.each { |msg_class| Table[msg_class.message_id] = msg_class }
778
-
779
569
  end # module Incoming
780
570
  end # module Messages
781
571
  end # module IB
782
- __END__
783
572
 
573
+ # Require standalone message source files
574
+ require 'ib-ruby/messages/incoming/ticks'
575
+ require 'ib-ruby/messages/incoming/open_order'
576
+
577
+ __END__
784
578
  // incoming msg id's
785
- static final int TICK_PRICE = 1; * TODO: realize both events
579
+ static final int TICK_PRICE = 1; *
786
580
  static final int TICK_SIZE = 2; *
787
581
  static final int ORDER_STATUS = 3; *
788
582
  static final int ERR_MSG = 4; *
@@ -792,7 +586,7 @@ __END__
792
586
  static final int ACCT_UPDATE_TIME = 8; *
793
587
  static final int NEXT_VALID_ID = 9; *
794
588
  static final int CONTRACT_DATA = 10; *
795
- static final int EXECUTION_DATA = 11; *
589
+ static final int EXECUTION_DATA = 11; ?
796
590
  static final int MARKET_DEPTH = 12; *
797
591
  static final int MARKET_DEPTH_L2 = 13; *
798
592
  static final int NEWS_BULLETINS = 14; *
@@ -815,3 +609,5 @@ __END__
815
609
  static final int EXECUTION_DATA_END = 55; *
816
610
  static final int DELTA_NEUTRAL_VALIDATION = 56; *
817
611
  static final int TICK_SNAPSHOT_END = 57; *
612
+ static final int MARKET_DATA_TYPE = 58; ?
613
+ static final int COMMISSION_REPORT = 59; ?