ib-ruby 0.6.1 → 0.7.0

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/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
@@ -0,0 +1,193 @@
1
+ # OpenOrder is the longest message with complex processing logics, it is isolated here
2
+ module IB
3
+ module Messages
4
+ module Incoming
5
+
6
+ OpenOrder =
7
+ def_message [5, [23, 28]],
8
+ [:order, :order_id, :int],
9
+
10
+ [:contract, :con_id, :int],
11
+ [:contract, :symbol, :string],
12
+ [:contract, :sec_type, :string],
13
+ [:contract, :expiry, :string],
14
+ [:contract, :strike, :decimal],
15
+ [:contract, :right, :string],
16
+ [:contract, :exchange, :string],
17
+ [:contract, :currency, :string],
18
+ [:contract, :local_symbol, :string],
19
+
20
+ [:order, :action, :string],
21
+ [:order, :total_quantity, :int],
22
+ [:order, :order_type, :string],
23
+ [:order, :limit_price, :decimal_max],
24
+ [:order, :aux_price, :decimal_max],
25
+ [:order, :tif, :string],
26
+ [:order, :oca_group, :string],
27
+ [:order, :account, :string],
28
+ [:order, :open_close, :string],
29
+ [:order, :origin, :int],
30
+ [:order, :order_ref, :string],
31
+ [:order, :client_id, :int],
32
+ [:order, :perm_id, :int],
33
+ [:order, :outside_rth, :boolean], # (@socket.read_int == 1)
34
+ [:order, :hidden, :boolean], # (@socket.read_int == 1)
35
+ [:order, :discretionary_amount, :decimal],
36
+ [:order, :good_after_time, :string],
37
+ [:shares_allocation, :string], # deprecated! field
38
+
39
+ [:order, :fa_group, :string],
40
+ [:order, :fa_method, :string],
41
+ [:order, :fa_percentage, :string],
42
+ [:order, :fa_profile, :string],
43
+ [:order, :good_till_date, :string],
44
+ [:order, :rule_80a, :string],
45
+ [:order, :percent_offset, :decimal_max],
46
+ [:order, :settling_firm, :string],
47
+ [:order, :short_sale_slot, :int],
48
+ [:order, :designated_location, :string],
49
+ [:order, :exempt_code, :int], # skipped in ver 51?
50
+ [:order, :auction_strategy, :int],
51
+ [:order, :starting_price, :decimal_max],
52
+ [:order, :stock_ref_price, :decimal_max],
53
+ [:order, :delta, :decimal_max],
54
+ [:order, :stock_range_lower, :decimal_max],
55
+ [:order, :stock_range_upper, :decimal_max],
56
+ [:order, :display_size, :int],
57
+ #@order.rth_only = @socket.read_boolean
58
+ [:order, :block_order, :boolean],
59
+ [:order, :sweep_to_fill, :boolean],
60
+ [:order, :all_or_none, :boolean],
61
+ [:order, :min_quantity, :int_max],
62
+ [:order, :oca_type, :int],
63
+ [:order, :etrade_only, :boolean],
64
+ [:order, :firm_quote_only, :boolean],
65
+ [:order, :nbbo_price_cap, :decimal_max],
66
+ [:order, :parent_id, :int],
67
+ [:order, :trigger_method, :int],
68
+ [:order, :volatility, :decimal_max],
69
+ [:order, :volatility_type, :int],
70
+ [:order, :delta_neutral_order_type, :string],
71
+ [:order, :delta_neutral_aux_price, :decimal_max]
72
+
73
+
74
+ class OpenOrder
75
+
76
+ def load
77
+ super
78
+
79
+ load_map [27, [proc { | | filled?(@data[:order][:delta_neutral_order_type]) },
80
+ # As of client v.52, we receive delta... params in openOrder
81
+ [:order, :delta_neutral_con_id, :int],
82
+ [:order, :delta_neutral_settling_firm, :string],
83
+ [:order, :delta_neutral_clearing_account, :string],
84
+ [:order, :delta_neutral_clearing_intent, :string]]
85
+ ],
86
+ [:order, :continuous_update, :int],
87
+ [:order, :reference_price_type, :int],
88
+ [:order, :trail_stop_price, :decimal_max],
89
+
90
+ # As of client v.56, we receive trailing_percent in openOrder
91
+ [30, [:order, :trailing_percent, :decimal_max]], # Never! 28 currently
92
+
93
+ [:order, :basis_points, :decimal_max],
94
+ [:order, :basis_points_type, :int_max],
95
+ [:contract, :legs_description, :string],
96
+
97
+ # Never happens! 28 is the max supported version currently
98
+ # As of client v.55, we receive orderComboLegs (price) in openOrder
99
+ [29, [:contract, :legs, :array, proc do |_|
100
+ Models::ComboLeg.new :con_id => socket.read_int,
101
+ :ratio => socket.read_int,
102
+ :action => socket.read_string,
103
+ :exchange => socket.read_string,
104
+ :open_close => socket.read_int,
105
+ :short_sale_slot => socket.read_int,
106
+ :designated_location => socket.read_string,
107
+ :exempt_code => socket.read_int
108
+ end],
109
+
110
+ # Order keeps received leg prices in a separate Array for some reason ?!
111
+ [:order, :leg_prices, :array, proc { |_| socket.read_decimal_max }],
112
+ ],
113
+ # As of client v.51, we can receive smartComboRoutingParams in openOrder
114
+ [26, [:smart_combo_routing_params, :hash]],
115
+
116
+ [:order, :scale_init_level_size, :int_max],
117
+ [:order, :scale_subs_level_size, :int_max],
118
+ [:order, :scale_price_increment, :decimal_max],
119
+
120
+ # As of client v.54, we can receive scale order fields
121
+ [28, [proc { | | filled?(@data[:order][:scale_price_increment]) },
122
+ [:order, :scale_price_adjust_value, :decimal_max],
123
+ [:order, :scale_price_adjust_interval, :int_max],
124
+ [:order, :scale_profit_offset, :decimal_max],
125
+ [:order, :scale_auto_reset, :boolean],
126
+ [:order, :scale_init_position, :int_max],
127
+ [:order, :scale_init_position, :int_max],
128
+ [:order, :scale_init_fill_qty, :decimal_max],
129
+ [:order, :scale_random_percent, :boolean]]
130
+ ],
131
+
132
+ # As of client v.49/50, we can receive hedgeType, hedgeParam, optOutSmartRouting
133
+ [25,
134
+ [:order, :hedge_type, :string],
135
+ [proc { | | filled?(@data[:order][:hedge_type]) },
136
+ [:order, :hedge_param, :string],
137
+ ],
138
+ [:order, :opt_out_smart_routing, :boolean]
139
+ ],
140
+
141
+ [:order, :clearing_account, :string],
142
+ [:order, :clearing_intent, :string],
143
+ [:order, :not_held, :boolean],
144
+ [:contract, :under_comp, :boolean],
145
+
146
+ [proc { | | filled?(@data[:contract][:under_comp]) },
147
+ [:contract, :under_con_id, :int],
148
+ [:contract, :under_delta, :decimal],
149
+ [:contract, :under_price, :decimal]
150
+ ],
151
+
152
+ [:order, :algo_strategy, :string],
153
+
154
+ # TODO: Test Order with algo_params, scale and legs!
155
+ [proc { | | filled?(@data[:order][:algo_strategy]) },
156
+ [:order, :algo_params, :hash]
157
+ ],
158
+
159
+ [:order, :what_if, :boolean],
160
+ [:order, :status, :string],
161
+ [:order, :init_margin, :string],
162
+ [:order, :maint_margin, :string],
163
+ [:order, :equity_with_loan, :string],
164
+ [:order, :commission, :decimal_max], # May be nil!
165
+ [:order, :min_commission, :decimal_max], # May be nil!
166
+ [:order, :max_commission, :decimal_max], # May be nil!
167
+ [:order, :commission_currency, :string],
168
+ [:order, :warning_text, :string]
169
+
170
+ @order = Models::Order.new @data[:order]
171
+ @contract = Models::Contract.build @data[:contract]
172
+ end
173
+
174
+ # Check if given value was set by TWS to something vaguely "positive"
175
+ def filled? value
176
+ case value
177
+ when String
178
+ !value.empty?
179
+ when Float, Integer
180
+ value > 0
181
+ else
182
+ !!value # to_bool
183
+ end
184
+ end
185
+
186
+ def to_human
187
+ "<OpenOrder: #{@contract.to_human} #{@order.to_human}>"
188
+ end
189
+
190
+ end # class OpenOrder
191
+ end # module Incoming
192
+ end # module Messages
193
+ end # module IB
@@ -0,0 +1,131 @@
1
+ # All message classes related to ticks located here
2
+ module IB
3
+ module Messages
4
+ module Incoming
5
+
6
+ class AbstractTick < AbstractMessage
7
+ # Returns Symbol with a meaningful name for received tick type
8
+ def type
9
+ TICK_TYPES[@data[:tick_type]]
10
+ end
11
+
12
+ def to_human
13
+ "<#{self.message_type} #{type}:" +
14
+ @data.map do |key, value|
15
+ " #{key} #{value}" unless [:version, :ticker_id, :tick_type].include?(key)
16
+ end.compact.join(',') + " >"
17
+ end
18
+ end
19
+
20
+ # The IB code seems to dispatch up to two wrapped objects for this message, a tickPrice
21
+ # and sometimes a tickSize, which seems to be identical to the TICK_SIZE object.
22
+ #
23
+ # Important note from
24
+ # http://chuckcaplan.com/twsapi/index.php/void%20tickPrice%28%29 :
25
+ #
26
+ # "The low you get is NOT the low for the day as you'd expect it
27
+ # to be. It appears IB calculates the low based on all
28
+ # transactions after 4pm the previous day. The most inaccurate
29
+ # results occur when the stock moves up in the 4-6pm aftermarket
30
+ # on the previous day and then gaps open upward in the
31
+ # morning. The low you receive from TWS can be easily be several
32
+ # points different from the actual 9:30am-4pm low for the day in
33
+ # cases like this. If you require a correct traded low for the
34
+ # day, you can't get it from the TWS API. One possible source to
35
+ # help build the right data would be to compare against what Yahoo
36
+ # lists on finance.yahoo.com/q?s=ticker under the "Day's Range"
37
+ # statistics (be careful here, because Yahoo will use anti-Denial
38
+ # of Service techniques to hang your connection if you try to
39
+ # request too many bytes in a short period of time from them). For
40
+ # most purposes, a good enough approach would start by replacing
41
+ # the TWS low for the day with Yahoo's day low when you first
42
+ # start watching a stock ticker; let's call this time T. Then,
43
+ # update your internal low if the bid or ask tick you receive is
44
+ # lower than that for the remainder of the day. You should check
45
+ # against Yahoo again at time T+20min to handle the occasional
46
+ # case where the stock set a new low for the day in between
47
+ # T-20min (the real time your original quote was from, taking into
48
+ # account the delay) and time T. After that you should have a
49
+ # correct enough low for the rest of the day as long as you keep
50
+ # updating based on the bid/ask. It could still get slightly off
51
+ # in a case where a short transaction setting a new low appears in
52
+ # between ticks of data that TWS sends you. The high is probably
53
+ # distorted in the same way the low is, which would throw your
54
+ # results off if the stock traded after-hours and gapped down. It
55
+ # should be corrected in a similar way as described above if this
56
+ # is important to you."
57
+ #
58
+ # IB then emits at most 2 events on eWrapper:
59
+ # tickPrice( tickerId, tickType, price, canAutoExecute)
60
+ # tickSize( tickerId, sizeTickType, size)
61
+ TickPrice = def_message [1, 6], AbstractTick,
62
+ [:ticker_id, :int],
63
+ [:tick_type, :int],
64
+ [:price, :decimal],
65
+ [:size, :int],
66
+ [:can_auto_execute, :int]
67
+
68
+ TickSize = def_message [2, 6], AbstractTick,
69
+ [:ticker_id, :int],
70
+ [:tick_type, :int],
71
+ [:size, :int]
72
+
73
+ TickGeneric = def_message [45, 6], AbstractTick,
74
+ [:ticker_id, :int],
75
+ [:tick_type, :int],
76
+ [:value, :decimal]
77
+
78
+ TickString = def_message [46, 6], AbstractTick,
79
+ [:ticker_id, :int],
80
+ [:tick_type, :int],
81
+ [:value, :string]
82
+
83
+ TickEFP = def_message [47, 6], AbstractTick,
84
+ [:ticker_id, :int],
85
+ [:tick_type, :int],
86
+ [:basis_points, :decimal],
87
+ [:formatted_basis_points, :string],
88
+ [:implied_futures_price, :decimal],
89
+ [:hold_days, :int],
90
+ [:dividend_impact, :decimal],
91
+ [:dividends_to_expiry, :decimal]
92
+
93
+ # This message is received when the market in an option or its underlier moves.
94
+ # TWS�s option model volatilities, prices, and deltas, along with the present
95
+ # value of dividends expected on that options underlier are received.
96
+ # TickOption message contains following @data:
97
+ # :ticker_id - Id that was specified previously in the call to reqMktData()
98
+ # :tick_type - Specifies the type of option computation (see TICK_TYPES).
99
+ # :implied_volatility - The implied volatility calculated by the TWS option
100
+ # modeler, using the specified :tick_type value.
101
+ # :delta - The option delta value.
102
+ # :option_price - The option price.
103
+ # :pv_dividend - The present value of dividends expected on the options underlier
104
+ # :gamma - The option gamma value.
105
+ # :vega - The option vega value.
106
+ # :theta - The option theta value.
107
+ # :under_price - The price of the underlying.
108
+ TickOptionComputation = TickOption =
109
+ def_message([21, 6], AbstractTick,
110
+ [:ticker_id, :int],
111
+ [:tick_type, :int],
112
+ # What is the "not yet computed" indicator:
113
+ [:implied_volatility, :decimal_limit_1], # -1 and below
114
+ [:delta, :decimal_limit_2], # -2 and below
115
+ [:option_price, :decimal_limit_1], # -1 -"-
116
+ [:pv_dividend, :decimal_limit_1], # -1 -"-
117
+ [:gamma, :decimal_limit_2], # -2 -"-
118
+ [:vega, :decimal_limit_2], # -2 -"-
119
+ [:theta, :decimal_limit_2], # -2 -"-
120
+ [:under_price, :decimal_limit_1]) do
121
+
122
+ "<TickOption #{type} for #{:ticker_id}: underlying @ #{under_price}, "+
123
+ "option @ #{option_price}, IV #{implied_volatility}%, delta #{delta}, " +
124
+ "gamma #{gamma}, vega #{vega}, theta #{theta}, pv_dividend #{pv_dividend}>"
125
+ end
126
+
127
+ TickSnapshotEnd = def_message 57, [:ticker_id, :int]
128
+
129
+ end # module Incoming
130
+ end # module Messages
131
+ end # module IB
@@ -1,17 +1,17 @@
1
1
  require 'ib-ruby/messages/abstract_message'
2
2
 
3
- # EClientSocket.java uses sendMax() rather than send() for a number of these.
4
- # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
5
- # These fields are initialized to this MAX_VALUE.
6
- # This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
7
-
8
3
  # TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
9
4
 
10
5
  module IB
11
6
  module Messages
7
+
8
+ # Outgoing IB messages (sent to TWS/Gateway)
12
9
  module Outgoing
13
10
  extend Messages # def_message macros
14
11
 
12
+ # Container for specific message classes, keyed by their message_ids
13
+ Classes = {}
14
+
15
15
  class AbstractMessage < IB::Messages::AbstractMessage
16
16
 
17
17
  def initialize data={}
@@ -28,21 +28,16 @@ module IB
28
28
  # an Array of elements that ought to be sent to the server by calling to_s on
29
29
  # each one and postpending a '\0'.
30
30
  #
31
- def send_to(server)
32
- self.encode.flatten.each do |datum|
33
- # TWS wants to receive booleans as 1 or 0... rewrite as necessary.
34
- datum = "1" if datum == true
35
- datum = "0" if datum == false
36
-
37
- #p datum.to_s + EOL
38
- server[:socket].syswrite(datum.to_s + EOL)
31
+ def send_to server
32
+ self.encode(server).flatten.each do |datum|
33
+ server[:socket].write_data datum
39
34
  end
40
35
  end
41
36
 
42
37
  # At minimum, Outgoing message contains message_id and version.
43
38
  # Most messages also contain (ticker, request or order) :id.
44
39
  # Then, content of @data Hash is encoded per instructions in data_map.
45
- def encode
40
+ def encode server
46
41
  [self.class.message_id,
47
42
  self.class.version,
48
43
  @data[:id] || @data[:ticker_id] || @data[:request_id]|| @data[:order_id] || [],
@@ -122,6 +117,11 @@ module IB
122
117
  RequestFA = def_message 18, :fa_data_type
123
118
  # data = { :fa_data_type => int, :xml => String }
124
119
  ReplaceFA = def_message 19, :fa_data_type, :xml
120
+ # data = { :market_data_type => int }
121
+ # The API can now receive frozen market data from Trader Workstation. Frozen
122
+ # market data is the last data recorded in our system. Use this method with
123
+ # :market_data_type = 1 for real-time streaming, 2 for frozen market data
124
+ RequestMarketDataType = def_message 59, :market_data_type
125
125
 
126
126
  # @data = { :subscribe => boolean,
127
127
  # :account_code => Advisor accounts only. Empty ('') for a standard account. }
@@ -292,50 +292,55 @@ module IB
292
292
  :instrument,
293
293
  :location_code,
294
294
  :scan_code,
295
- [:above_price, EOL],
296
- [:below_price, EOL],
297
- [:above_volume, EOL],
298
- [:market_cap_above, EOL],
299
- [:market_cap_below, EOL],
295
+ :above_price,
296
+ :below_price,
297
+ :above_volume,
298
+ :market_cap_above,
299
+ :market_cap_below,
300
300
  :moody_rating_above,
301
301
  :moody_rating_below,
302
302
  :sp_rating_above,
303
303
  :sp_rating_below,
304
304
  :maturity_date_above,
305
305
  :maturity_date_below,
306
- [:coupon_rate_above, EOL],
307
- [:coupon_rate_below, EOL],
306
+ :coupon_rate_above,
307
+ :coupon_rate_below,
308
308
  :exclude_convertible,
309
- [:average_option_volume_above, EOL], # ?
309
+ :average_option_volume_above, # ?
310
310
  :scanner_setting_pairs,
311
311
  :stock_type_filter)
312
312
 
313
313
  ### Even more complex Outgoing Message classes, overriding #encode method:
314
314
 
315
- # Data format is { :id => order_id (int),
315
+
316
+ # Data format is { :id => int: order_id,
316
317
  # :contract => Contract,
317
318
  # :order => Order }
318
- class PlaceOrder < AbstractMessage
319
- @message_id = 3
320
- @version = 31
319
+ PlaceOrder = def_message [3, 31] # 38 Need to set up Classes Hash properly
320
+
321
+ class PlaceOrder
322
+ def encode server
323
+
324
+ # Old server version supports no enhancements
325
+ @version = 31 if server[:server_version] <= 60
321
326
 
322
- def encode
323
327
  [super,
324
- @data[:order].serialize_with(@data[:contract])].flatten
328
+ @data[:order].serialize_with(server, @data[:contract])].flatten
325
329
  end
326
330
  end # PlaceOrder
327
331
 
328
- module DataParser
332
+ # Messages that request bar data have special processing of @data
333
+ class BarRequestMessage < AbstractMessage
329
334
  # Preprocessor for some data fields
330
335
  def parse data
331
336
  data_type = DATA_TYPES[data[:what_to_show]] || data[:what_to_show]
332
337
  unless DATA_TYPES.values.include?(data_type)
333
- raise ArgumentError.new(":what_to_show must be one of #{DATA_TYPES.inspect}.")
338
+ error ":what_to_show must be one of #{DATA_TYPES.inspect}", :args
334
339
  end
335
340
 
336
341
  bar_size = BAR_SIZES[data[:bar_size]] || data[:bar_size]
337
342
  unless BAR_SIZES.values.include?(bar_size)
338
- raise ArgumentError.new(":bar_size must be one of #{BAR_SIZES.inspect}.")
343
+ error ":bar_size must be one of #{BAR_SIZES.inspect}", :args
339
344
  end
340
345
 
341
346
  contract = data[:contract].is_a?(Models::Contract) ?
@@ -347,7 +352,7 @@ module IB
347
352
 
348
353
  # data = { :id => ticker_id (int),
349
354
  # :contract => Contract ,
350
- # :bar_size => int/Symbol? Currently only 5 second bars (2?) are supported,
355
+ # :bar_size => int/Symbol? Currently only 5 second bars are supported,
351
356
  # if any other value is used, an exception will be thrown.,
352
357
  # :what_to_show => Symbol: Determines the nature of data being extracted.
353
358
  # Valid values:
@@ -361,13 +366,10 @@ module IB
361
366
  # "Regular Trading Hours" of the product in question is returned,
362
367
  # even if the time span requested falls partially or completely
363
368
  # outside of them.
364
- class RequestRealTimeBars < AbstractMessage
365
- @message_id = 50
366
- @version = 1 # ?
367
-
368
- include DataParser
369
+ RequestRealTimeBars = def_message 50, BarRequestMessage
369
370
 
370
- def encode
371
+ class RequestRealTimeBars
372
+ def encode server
371
373
  data_type, bar_size, contract = parse @data
372
374
 
373
375
  [super,
@@ -449,13 +451,10 @@ module IB
449
451
  # For backfill on futures data, you may need to leave the Primary
450
452
  # Exchange field of the Contract structure blank; see
451
453
  # http://www.interactivebrokers.com/discus/messages/2/28477.html?1114646754
452
- class RequestHistoricalData < AbstractMessage
453
- @message_id = 20
454
- @version = 4
454
+ RequestHistoricalData = def_message [20, 4], BarRequestMessage
455
455
 
456
- include DataParser
457
-
458
- def encode
456
+ class RequestHistoricalData
457
+ def encode server
459
458
  data_type, bar_size, contract = parse @data
460
459
 
461
460
  [super,
@@ -470,7 +469,6 @@ module IB
470
469
  end
471
470
  end # RequestHistoricalData
472
471
 
473
-
474
472
  end # module Outgoing
475
473
  end # module Messages
476
474
  end # module IB
@@ -512,3 +510,4 @@ __END__
512
510
  private static final int CANCEL_CALC_IMPLIED_VOLAT = 56;
513
511
  private static final int CANCEL_CALC_OPTION_PRICE = 57;
514
512
  private static final int REQ_GLOBAL_CANCEL = 58;
513
+ private static final int REQ_MARKET_DATA_TYPE = 59;