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