ib-ruby 0.7.4 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.gitignore +3 -0
  2. data/HISTORY +8 -0
  3. data/README.md +2 -2
  4. data/Rakefile +15 -0
  5. data/TODO +7 -2
  6. data/VERSION +1 -1
  7. data/bin/account_info +1 -1
  8. data/bin/cancel_orders +1 -1
  9. data/bin/contract_details +1 -1
  10. data/bin/depth_of_market +1 -1
  11. data/bin/fa_accounts +1 -1
  12. data/bin/fundamental_data +42 -0
  13. data/bin/historic_data +1 -1
  14. data/bin/historic_data_cli +1 -1
  15. data/bin/list_orders +1 -2
  16. data/bin/market_data +1 -1
  17. data/bin/option_data +1 -1
  18. data/bin/place_combo_order +1 -1
  19. data/bin/place_order +1 -1
  20. data/bin/template +1 -4
  21. data/bin/tick_data +2 -2
  22. data/bin/time_and_sales +1 -1
  23. data/lib/ib-ruby.rb +4 -0
  24. data/lib/ib-ruby/connection.rb +50 -34
  25. data/lib/ib-ruby/constants.rb +232 -37
  26. data/lib/ib-ruby/db.rb +25 -0
  27. data/lib/ib-ruby/extensions.rb +51 -1
  28. data/lib/ib-ruby/messages/abstract_message.rb +0 -8
  29. data/lib/ib-ruby/messages/incoming.rb +18 -493
  30. data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
  31. data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
  32. data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
  33. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
  34. data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
  35. data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
  36. data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
  37. data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
  38. data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
  39. data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
  40. data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
  41. data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
  42. data/lib/ib-ruby/messages/outgoing.rb +25 -223
  43. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
  44. data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
  45. data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
  46. data/lib/ib-ruby/models.rb +4 -0
  47. data/lib/ib-ruby/models/bar.rb +31 -14
  48. data/lib/ib-ruby/models/combo_leg.rb +48 -23
  49. data/lib/ib-ruby/models/contracts.rb +2 -2
  50. data/lib/ib-ruby/models/contracts/bag.rb +11 -7
  51. data/lib/ib-ruby/models/contracts/contract.rb +90 -66
  52. data/lib/ib-ruby/models/contracts/option.rb +16 -7
  53. data/lib/ib-ruby/models/execution.rb +34 -18
  54. data/lib/ib-ruby/models/model.rb +15 -7
  55. data/lib/ib-ruby/models/model_properties.rb +101 -44
  56. data/lib/ib-ruby/models/order.rb +176 -187
  57. data/lib/ib-ruby/models/order_state.rb +99 -0
  58. data/lib/ib-ruby/symbols/forex.rb +10 -10
  59. data/lib/ib-ruby/symbols/futures.rb +6 -6
  60. data/lib/ib-ruby/symbols/stocks.rb +3 -3
  61. data/spec/account_helper.rb +4 -5
  62. data/spec/combo_helper.rb +4 -4
  63. data/spec/db.rb +18 -0
  64. data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
  65. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
  66. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
  67. data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
  68. data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
  69. data/spec/ib-ruby/models/bag_spec.rb +97 -0
  70. data/spec/ib-ruby/models/bar_spec.rb +45 -0
  71. data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
  72. data/spec/ib-ruby/models/contract_spec.rb +134 -170
  73. data/spec/ib-ruby/models/execution_spec.rb +35 -50
  74. data/spec/ib-ruby/models/option_spec.rb +127 -0
  75. data/spec/ib-ruby/models/order_spec.rb +89 -68
  76. data/spec/ib-ruby/models/order_state_spec.rb +55 -0
  77. data/spec/integration/contract_info_spec.rb +4 -6
  78. data/spec/integration/fundamental_data_spec.rb +41 -0
  79. data/spec/integration/historic_data_spec.rb +4 -4
  80. data/spec/integration/market_data_spec.rb +1 -3
  81. data/spec/integration/orders/attached_spec.rb +8 -10
  82. data/spec/integration/orders/combo_spec.rb +2 -2
  83. data/spec/integration/orders/execution_spec.rb +0 -1
  84. data/spec/integration/orders/placement_spec.rb +1 -3
  85. data/spec/integration/orders/valid_ids_spec.rb +1 -2
  86. data/spec/message_helper.rb +1 -1
  87. data/spec/model_helper.rb +211 -0
  88. data/spec/order_helper.rb +44 -37
  89. data/spec/spec_helper.rb +36 -23
  90. data/spec/v.rb +7 -0
  91. data/tasks/doc.rake +1 -1
  92. metadata +116 -12
  93. data/spec/integration/orders/open_order +0 -98
@@ -5,41 +5,32 @@ module IB
5
5
 
6
6
  # Enumeration of bar size types for convenience.
7
7
  # Bar sizes less than 30 seconds do not work for some securities.
8
- BAR_SIZES = {:sec1 => '1 sec',
9
- :sec5 => '5 secs',
10
- :sec15 => '15 secs',
11
- :sec30 => '30 secs',
12
- :min1 => '1 min',
13
- :min2 => '2 mins',
14
- :min3 => '3 mins',
15
- :min5 => '5 mins',
16
- :min15 => '15 mins',
17
- :min30 => '30 mins',
18
- :hour1 => '1 hour',
19
- :day1 => '1 day'}
8
+ BAR_SIZES = {'1 sec' => :sec1,
9
+ '5 secs' => :sec5,
10
+ '15 secs' =>:sec15,
11
+ '30 secs' =>:sec30,
12
+ '1 min' => :min1,
13
+ '2 mins' => :min2,
14
+ '3 mins' => :min3,
15
+ '5 mins' => :min5,
16
+ '15 mins' =>:min15,
17
+ '30 mins' =>:min30,
18
+ '1 hour' =>:hour1,
19
+ '1 day' => :day1
20
+ }.freeze
20
21
 
21
22
  # Enumeration of data types.
22
23
  # Determines the nature of data being extracted. Valid values:
23
- DATA_TYPES = {:trades => 'TRADES',
24
- :midpoint => 'MIDPOINT',
25
- :bid => 'BID',
26
- :ask => 'ASK',
27
- :bid_ask => 'BID_ASK',
28
- :historical_volatility => 'HISTORICAL_VOLATILITY',
29
- :option_implied_volatility => 'OPTION_IMPLIED_VOLATILITY',
30
- :option_volume => 'OPTION_VOLUME',
31
- :option_open_interest => 'OPTION_OPEN_INTEREST',
32
- }
33
-
34
- # Valid security types (sec_type attribute of IB::Contract)
35
- SECURITY_TYPES = {:stock => "STK",
36
- :option => "OPT",
37
- :future => "FUT",
38
- :index => "IND",
39
- :futures_option => "FOP",
40
- :forex => "CASH",
41
- :bond => "BOND",
42
- :bag => "BAG"}
24
+ DATA_TYPES = {'TRADES' => :trades,
25
+ 'MIDPOINT' => :midpoint,
26
+ 'BID' => :bid,
27
+ 'ASK' => :ask,
28
+ 'BID_ASK' => :bid_ask,
29
+ 'HISTORICAL_VOLATILITY' => :historical_volatility,
30
+ 'OPTION_IMPLIED_VOLATILITY' => :option_implied_volatility,
31
+ 'OPTION_VOLUME' => :option_volume,
32
+ 'OPTION_OPEN_INTEREST' => :option_open_interest
33
+ }.freeze
43
34
 
44
35
  ### These values are typically received from TWS in incoming messages
45
36
 
@@ -117,9 +108,17 @@ module IB
117
108
  }
118
109
 
119
110
  # Financial Advisor types (FaMsgTypeName)
120
- FA_TYPES = {1 => 'GROUPS',
121
- 2 => 'PROFILES',
122
- 3 =>'ALIASES'}
111
+ FA_TYPES = {
112
+ 1 => :groups,
113
+ 2 => :profiles,
114
+ 3 => :aliases}.freeze
115
+
116
+ # Received in new MarketDataType (58 incoming) message
117
+ MARKET_DATA_TYPES = {
118
+ 0 => :unknown,
119
+ 1 => :real_time,
120
+ 2 => :frozen,
121
+ }
123
122
 
124
123
  # Market depth messages contain these "operation" codes to tell you what to do with the data.
125
124
  # See also http://www.interactivebrokers.com/php/apiUsersGuide/apiguide/java/updatemktdepth.htm
@@ -127,10 +126,206 @@ module IB
127
126
  0 => :insert, # New order, insert into the row identified by :position
128
127
  1 => :update, # Update the existing order at the row identified by :position
129
128
  2 => :delete # Delete the existing order at the row identified by :position
130
- }
129
+ }.freeze
131
130
 
132
131
  MARKET_DEPTH_SIDES = {
133
132
  0 => :ask,
134
133
  1 => :bid
135
- }
134
+ }.freeze
135
+
136
+ ORDER_TYPES =
137
+ {'LMT' => :limit, # Limit Order
138
+ 'LIT' => :limit_if_touched, # Limit if Touched
139
+ 'LOC' => :limit_on_close, # Limit-on-Close LMTCLS ?
140
+ 'LOO' => :limit_on_open, # Limit-on-Open
141
+ 'MKT' => :market, # Market
142
+ 'MIT' => :market_if_touched, # Market-if-Touched
143
+ 'MOC' => :market_on_close, # Market-on-Close MKTCLSL ?
144
+ 'MOO' => :market_on_open, # Market-on-Open
145
+ 'MTL' => :market_to_limit, # Market-to-Limit
146
+ 'MKTPRT' => :market_protected, # Market with Protection
147
+ 'QUOTE' => :request_for_quote, # Request for Quote
148
+ 'STP' => :stop, # Stop
149
+ 'STPLMT' => :stop_limit, # Stop Limit
150
+ 'TRAIL' => :trailing_stop, # Trailing Stop
151
+ 'TRAIL LIMIT' => :trailing_limit, # Trailing Stop Limit
152
+ 'TRAIL LIT' => :trailing_limit_if_touched, # Trailing Limit if Touched
153
+ 'TRAIL MIT' => :trailing_market_if_touched, # Trailing Market If Touched
154
+ 'PEG MKT' => :pegged_to_market, # Pegged-to-Market
155
+ 'REL' => :relative, # Relative
156
+ 'BOX TOP' => :box_top, # Box Top
157
+ 'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
158
+ 'VWAP' => :vwap, # VWAP-Guaranteed
159
+ 'OCA' => :one_cancels_all, # One-Cancels-All
160
+ 'VOL' => :volatility, # Volatility
161
+ 'SCALE' => :scale, # Scale
162
+ 'NONE' => :no_order # Used to indicate no hedge in :delta_neutral_order_type
163
+ }.freeze
164
+
165
+ # Valid security types (sec_type attribute of IB::Contract)
166
+ SECURITY_TYPES =
167
+ {'STK' => :stock,
168
+ 'OPT' => :option,
169
+ 'FUT' => :future,
170
+ 'IND' => :index,
171
+ 'FOP' => :futures_option,
172
+ 'CASH' => :forex,
173
+ 'BOND' => :bond,
174
+ 'BAG' => :bag}.freeze
175
+
176
+ # Obtain symbolic value from given property code:
177
+ # VALUES[:side]['B'] -> :buy
178
+ VALUES = {
179
+ :sec_type => SECURITY_TYPES,
180
+ :order_type => ORDER_TYPES,
181
+ :delta_neutral_order_type => ORDER_TYPES,
182
+
183
+ :origin => {0 => :customer, 1 => :firm},
184
+ :volatility_type => {1 => :daily, 2 => :annual},
185
+ :reference_price_type => {1 => :average, 2 => :bid_or_ask},
186
+
187
+ # This property encodes differently for ComboLeg and Order objects,
188
+ # we use ComboLeg codes and transcode for Order codes as needed
189
+ :open_close =>
190
+ {0 => :same, # Default for Legs, same as the parent (combo) security.
191
+ 1 => :open, # Open. For Legs, this value is only used by institutions.
192
+ 2 => :close, # Close. For Legs, this value is only used by institutions.
193
+ 3 => :unknown}, # WTF
194
+
195
+ :right =>
196
+ {'' => :none, # Not an option
197
+ 'P' => :put,
198
+ 'C' => :call},
199
+
200
+ :side => # AKA action
201
+ {'B' => :buy, # or BOT
202
+ 'S' => :sell, # or SLD
203
+ 'T' => :short, # short
204
+ 'X' => :short_exempt # Short Sale Exempt action. This allows some orders
205
+ # to be exempt from the SEC recent changes to Regulation SHO, which
206
+ # eliminated the old uptick rule and replaced it with a new "circuit breaker"
207
+ # rule, and allows some orders to be exempt from the new rule.
208
+ },
209
+
210
+ :short_sale_slot =>
211
+ {0 => :default, # The only valid option for retail customers
212
+ 1 => :broker, # Shares are at your clearing broker, institutions
213
+ 2 => :third_party}, # Shares will be delivered from elsewhere, institutions
214
+
215
+ :oca_type =>
216
+ {0 => :none, # Not a member of OCA group
217
+ 1 => :cancel_with_block, # Cancel all remaining orders with block
218
+ 2 => :reduce_with_block, # Remaining orders are reduced in size with block
219
+ 3 => :reduce_no_block}, # Remaining orders are reduced in size with no block
220
+
221
+ :auction_strategy =>
222
+ {0 => :none, # Not a BOX order
223
+ 1 => :match,
224
+ 2 => :improvement,
225
+ 3 => :transparent},
226
+
227
+ :trigger_method =>
228
+ {0 => :default, # "double bid/ask" used for OTC/US options, "last" otherswise.
229
+ 1 => :double_bid_ask, # stops are triggered by 2 consecutive bid or ask prices.
230
+ 2 => :last, # stops are triggered based on the last price.
231
+ 3 => :double_last,
232
+ 4 => :bid_ask, # bid >= trigger price for buy orders, ask <= trigger for sell orders
233
+ 7 => :last_or_bid_ask, # bid OR last price >= trigger price for buy orders
234
+ 8 => :mid_point}, # midpoint >= trigger price for buy orders and the
235
+ # spread between the bid and ask must be less than 0.1% of the midpoint
236
+
237
+ :hedge_type =>
238
+ {'D' => :delta, # parent order is an option and the child order is a stock
239
+ 'B' => :beta, # offset market risk by entering into a position with
240
+ # another contract based on the system or user-defined beta
241
+ 'F' => :forex, # offset risk with currency different from your base currency
242
+ 'P' => :pair}, # trade a mis-valued pair of contracts and provide the
243
+ # ratio between the parent and hedging child order
244
+
245
+ :clearing_intent =>
246
+ {'' => :none,
247
+ 'IB' => :ib,
248
+ 'AWAY' => :away,
249
+ 'PTA' => :post_trade_allocation},
250
+
251
+ :delta_neutral_clearing_intent =>
252
+ {'' => :none,
253
+ 'IB' => :ib,
254
+ 'AWAY' => :away,
255
+ 'PTA' => :post_trade_allocation},
256
+
257
+ :tif =>
258
+ {'DAY' => :day,
259
+ 'GAT' => :good_after_time,
260
+ 'GTD' => :good_till_date,
261
+ 'GTC' => :good_till_cancelled,
262
+ 'IOC' => :immediate_or_cancel},
263
+
264
+ :rule_80a =>
265
+ {'I' => :individual,
266
+ 'A' => :agency,
267
+ 'W' => :agent_other_member,
268
+ 'J' => :individual_ptia,
269
+ 'U' => :agency_ptia,
270
+ 'M' => :agent_other_member_ptia,
271
+ 'K' => :individual_pt,
272
+ 'Y' => :agency_pt,
273
+ 'N' => :agent_other_member_pt},
274
+
275
+ :opt? => # TODO: unknown Order property, like OPT_BROKER_DEALER... in Order.java
276
+ {'?' => :unknown,
277
+ 'b' => :broker_dealer,
278
+ 'c' => :customer,
279
+ 'f' => :firm,
280
+ 'm' => :isemm,
281
+ 'n' => :farmm,
282
+ 'y' => :specialist},
283
+
284
+ }.freeze
285
+
286
+ # Obtain property code from given symbolic value:
287
+ # CODES[:side][:buy] -> 'B'
288
+ CODES = Hash[VALUES.map { |property, hash| [property, hash.invert] }]
289
+
290
+ # Most common property processors
291
+ PROPS = {:side =>
292
+ {:set => proc { |val| # BUY(BOT)/SELL(SLD)/SSHORT/SSHORTX
293
+ self[:side] = case val.to_s.upcase
294
+ when /SHORT.*X|^X$/
295
+ 'X'
296
+ when /SHORT|^T$/
297
+ 'T'
298
+ when /^B/
299
+ 'B'
300
+ when /^S/
301
+ 'S'
302
+ end },
303
+ :validate =>
304
+ {:format =>
305
+ {:with => /^buy$|^sell$|^short$|^short_exempt$/,
306
+ :message => "should be buy/sell/short"}
307
+ }
308
+ },
309
+
310
+ :open_close =>
311
+ {:set => proc { |val|
312
+ self[:open_close] = case val.to_s.upcase[0..0]
313
+ when 'S', '0' # SAME
314
+ 0
315
+ when 'O', '1' # OPEN
316
+ 1
317
+ when 'C', '2' # CLOSE
318
+ 2
319
+ when 'U', '3' # Unknown
320
+ 3
321
+ end
322
+ },
323
+ :validate =>
324
+ {:format =>
325
+ {:with => /^same$|^open$|^close$|^unknown$/,
326
+ :message => "should be same/open/close/unknown"}
327
+ },
328
+ }
329
+ }.freeze
330
+
136
331
  end # module IB
data/lib/ib-ruby/db.rb ADDED
@@ -0,0 +1,25 @@
1
+ # By requiring this file, we make all IB:Models database-backed ActiveRecord subclasses
2
+
3
+ require 'active_record'
4
+
5
+ module IB
6
+ module DB
7
+
8
+ def self.logger= logger
9
+ ActiveRecord::Base.logger = logger
10
+ end
11
+
12
+ # Establish DB connection and do other plumbing here
13
+ def self.connect config
14
+ #log.warn "Starting Database connection"
15
+ ActiveRecord::Base.establish_connection(config)
16
+ #ActiveRecord.colorize_logging = false
17
+
18
+ # Get rid of nasty conversion issues
19
+ ActiveRecord::Base.default_timezone = :utc
20
+ Time.zone = 'UTC'
21
+ end
22
+
23
+ # Load ActiveRecord::Schema ? where from ?
24
+ end # module DB
25
+ end
@@ -1,11 +1,61 @@
1
- # Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
2
1
  class Time
2
+ # Render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
3
3
  def to_ib
4
4
  "#{year}#{sprintf("%02d", month)}#{sprintf("%02d", day)} " +
5
5
  "#{sprintf("%02d", hour)}:#{sprintf("%02d", min)}:#{sprintf("%02d", sec)}"
6
6
  end
7
7
  end # Time
8
8
 
9
+ class Numeric
10
+ # Conversion 0/1 into true/false
11
+ def to_bool
12
+ self == 0 ? false : true
13
+ end
14
+ end
15
+
16
+ class TrueClass
17
+ def to_bool
18
+ self
19
+ end
20
+ end
21
+
22
+ class FalseClass
23
+ def to_bool
24
+ self
25
+ end
26
+ end
27
+
28
+ class String
29
+ def to_bool
30
+ case self.chomp.upcase
31
+ when 'TRUE', 'T'
32
+ true
33
+ when 'FALSE', 'F', ''
34
+ false
35
+ else
36
+ error "Unable to convert #{self} to bool"
37
+ end
38
+ end
39
+ end
40
+
41
+ class NilClass
42
+ def to_bool
43
+ false
44
+ end
45
+ end
46
+
47
+ class Symbol
48
+ def to_f
49
+ 0
50
+ end
51
+ end
52
+
53
+ class Object
54
+ def to_sup
55
+ self.to_s.upcase
56
+ end
57
+ end
58
+
9
59
  ### Patching Object#error in ib-ruby/errors
10
60
  # def error message, type=:standard
11
61
 
@@ -1,11 +1,3 @@
1
- # EClientSocket.java uses sendMax() rather than send() for a number of these.
2
- # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
3
- # These fields are initialized to this MAX_VALUE.
4
- # This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
5
-
6
- # TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
7
- # TODO: realize Message#fire method that raises EWrapper events
8
-
9
1
  module IB
10
2
  module Messages
11
3
 
@@ -1,4 +1,4 @@
1
- require 'ib-ruby/messages/abstract_message'
1
+ require 'ib-ruby/messages/incoming/abstract_message'
2
2
 
3
3
  # EClientSocket.java uses sendMax() rather than send() for a number of these.
4
4
  # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
@@ -15,144 +15,7 @@ module IB
15
15
  module Incoming
16
16
  extend Messages # def_message macros
17
17
 
18
- # Container for specific message classes, keyed by their message_ids
19
- Classes = {}
20
-
21
- class AbstractMessage < IB::Messages::AbstractMessage
22
-
23
- def version # Per message, received messages may have the different versions
24
- @data[:version]
25
- end
26
-
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
35
- @created_at = Time.now
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
48
- end
49
- end
50
-
51
- def socket
52
- @server[:socket]
53
- end
54
-
55
- # Every message loads received message version first
56
- # Override the load method in your subclass to do actual reading into @data.
57
- def load
58
- @data[:version] = socket.read_int
59
-
60
- check_version @data[:version], self.class.version
61
-
62
- load_map *self.class.data_map
63
- end
64
-
65
- # Load @data from the socket according to the given data map.
66
- #
67
- # map is a series of Arrays in the format of
68
- # [ :name, :type ], [ :group, :name, :type]
69
- # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
70
- # group is used to lump together aggregates, such as Contract or Order fields
71
- def load_map(*map)
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}"
104
- end
105
- end
106
- end
107
- end
108
-
109
- # class AbstractMessage
110
-
111
- ### Actual message classes (short definitions):
112
-
113
- # :status - String: Displays the order status. Possible values include:
114
- # � PendingSubmit - indicates that you have transmitted the order, but
115
- # have not yet received confirmation that it has been accepted by the
116
- # order destination. NOTE: This order status is NOT sent back by TWS
117
- # and should be explicitly set by YOU when an order is submitted.
118
- # � PendingCancel - indicates that you have sent a request to cancel
119
- # the order but have not yet received cancel confirmation from the
120
- # order destination. At this point, your order cancel is not confirmed.
121
- # You may still receive an execution while your cancellation request
122
- # is pending. NOTE: This order status is not sent back by TWS and
123
- # should be explicitly set by YOU when an order is canceled.
124
- # � PreSubmitted - indicates that a simulated order type has been
125
- # accepted by the IB system and that this order has yet to be elected.
126
- # The order is held in the IB system until the election criteria are
127
- # met. At that time the order is transmitted to the order destination
128
- # as specified.
129
- # � Submitted - indicates that your order has been accepted at the order
130
- # destination and is working.
131
- # � Cancelled - indicates that the balance of your order has been
132
- # confirmed canceled by the IB system. This could occur unexpectedly
133
- # when IB or the destination has rejected your order.
134
- # � Filled - indicates that the order has been completely filled.
135
- # � Inactive - indicates that the order has been accepted by the system
136
- # (simulated orders) or an exchange (native orders) but that currently
137
- # the order is inactive due to system, exchange or other issues.
138
- # :why_held - This field is used to identify an order held when TWS is trying to
139
- # locate shares for a short sell. The value used to indicate this is 'locate'.
140
- OrderStatus = def_message [3, 6], [:order_id, :int],
141
- [:status, :string],
142
- [:filled, :int],
143
- [:remaining, :int],
144
- [:average_fill_price, :decimal],
145
- [:perm_id, :int],
146
- [:parent_id, :int],
147
- [:last_fill_price, :decimal],
148
- [:client_id, :int],
149
- [:why_held, :string] do
150
- "<OrderStatus: #{status} filled: #{filled}/#{remaining + filled}" +
151
- " @ last/avg: #{last_fill_price}/#{average_fill_price}" +
152
- (parent_id > 0 ? " parent_id: #{parent_id}" : "") +
153
- (why_held != "" ? " why_held: #{why_held}" : "") +
154
- " id/perm: #{order_id}/#{perm_id}>"
155
- end
18
+ ### Define short message classes in-line:
156
19
 
157
20
  AccountValue = def_message([6, 2], [:key, :string],
158
21
  [:value, :string],
@@ -184,9 +47,7 @@ module IB
184
47
  ReceiveFA =
185
48
  def_message 16, [:type, :int], # type of Financial Advisor configuration data
186
49
  # being received from TWS. Valid values include:
187
- # 1 = GROUPS
188
- # 2 = PROFILE
189
- # 3 = ACCOUNT ALIASES
50
+ # 1 = GROUPS, 2 = PROFILE, 3 = ACCOUNT ALIASES
190
51
  [:xml, :string] # XML string with requested FA configuration information.
191
52
 
192
53
  # Receives an XML document that describes the valid parameters that a scanner
@@ -198,7 +59,7 @@ module IB
198
59
 
199
60
  # Receive Reuters global fundamental market data. There must be a subscription to
200
61
  # Reuters Fundamental set up in Account Management before you can receive this data.
201
- FundamentalData = def_message 50, [:request_id, :int], [:data, :string]
62
+ FundamentalData = def_message 51, [:request_id, :int], [:data, :string]
202
63
 
203
64
  ContractDataEnd = def_message 52, [:request_id, :int]
204
65
 
@@ -218,361 +79,25 @@ module IB
218
79
  [:yield, :decimal],
219
80
  [:yield_redemption_date, :int]
220
81
 
221
- MarketDepth =
222
- def_message 12, [:request_id, :int],
223
- [:position, :int], # The row Id of this market depth entry.
224
- [:operation, :int], # How it should be applied to the market depth:
225
- # 0 = insert this new order into the row identified by :position
226
- # 1 = update the existing order in the row identified by :position
227
- # 2 = delete the existing order at the row identified by :position
228
- [:side, :int], # side of the book: 0 = ask, 1 = bid
229
- [:price, :decimal],
230
- [:size, :int]
231
- class MarketDepth
232
- def side
233
- @data[:side] == 0 ? :ask : :bid
234
- end
235
-
236
- def operation
237
- @data[:operation] == 0 ? :insert : @data[:operation] == 1 ? :update : :delete
238
- end
239
-
240
- def to_human
241
- "<#{self.message_type}: #{operation} #{side} @ "+
242
- "#{position} = #{price} x #{size}>"
243
- end
244
- end
245
-
246
- MarketDepthL2 =
247
- def_message 13, MarketDepth, # Fields descriptions - see above
248
- [:request_id, :int],
249
- [:position, :int],
250
- [:market_maker, :string], # The exchange hosting this order.
251
- [:operation, :int],
252
- [:side, :int],
253
- [:price, :decimal],
254
- [:size, :int]
255
-
256
- # Called Error in Java code, but in fact this type of messages also
257
- # deliver system alerts and additional (non-error) info from TWS.
258
- ErrorMessage = Error = Alert = def_message([4, 2],
259
- [:error_id, :int],
260
- [:code, :int],
261
- [:message, :string])
262
- class Alert
263
- # Is it an Error message?
264
- def error?
265
- code < 1000
266
- end
267
-
268
- # Is it a System message?
269
- def system?
270
- code > 1000 && code < 2000
271
- end
272
-
273
- # Is it a Warning message?
274
- def warning?
275
- code > 2000
276
- end
277
-
278
- def to_human
279
- "TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'} #{code}: #{message}"
280
- end
281
- end # class Alert
282
-
283
- PortfolioValue = def_message [7, 7],
284
- [:contract, :con_id, :int],
285
- [:contract, :symbol, :string],
286
- [:contract, :sec_type, :string],
287
- [:contract, :expiry, :string],
288
- [:contract, :strike, :decimal],
289
- [:contract, :right, :string],
290
- [:contract, :multiplier, :string],
291
- [:contract, :primary_exchange, :string],
292
- [:contract, :currency, :string],
293
- [:contract, :local_symbol, :string],
294
- [:position, :int],
295
- [:market_price, :decimal],
296
- [:market_value, :decimal],
297
- [:average_cost, :decimal],
298
- [:unrealized_pnl, :decimal_max], # May be nil!
299
- [:realized_pnl, :decimal_max], # May be nil!
300
- [:account_name, :string]
301
- class PortfolioValue
302
-
303
- def load
304
- super
305
- @contract = IB::Contract.build @data[:contract]
306
- end
307
-
308
- def to_human
309
- "<PortfolioValue: #{contract.to_human} (#{position}): Market #{market_price}" +
310
- " price #{market_value} value; PnL: #{unrealized_pnl} unrealized," +
311
- " #{realized_pnl} realized; account #{account_name}>"
312
- end
313
- end # PortfolioValue
314
-
315
- ContractDetails = ContractData =
316
- def_message([10, 6],
317
- [:request_id, :int], # request id
318
- [:contract, :symbol, :string],
319
- [:contract, :sec_type, :string],
320
- [:contract, :expiry, :string],
321
- [:contract, :strike, :decimal],
322
- [:contract, :right, :string],
323
- [:contract, :exchange, :string],
324
- [:contract, :currency, :string],
325
- [:contract, :local_symbol, :string],
326
-
327
- [:contract, :market_name, :string], # extended
328
- [:contract, :trading_class, :string],
329
- [:contract, :con_id, :int],
330
- [:contract, :min_tick, :decimal],
331
- [:contract, :multiplier, :string],
332
- [:contract, :order_types, :string],
333
- [:contract, :valid_exchanges, :string],
334
- [:contract, :price_magnifier, :int],
335
- [:contract, :under_con_id, :int],
336
- [:contract, :long_name, :string],
337
- [:contract, :primary_exchange, :string],
338
- [:contract, :contract_month, :string],
339
- [:contract, :industry, :string],
340
- [:contract, :category, :string],
341
- [:contract, :subcategory, :string],
342
- [:contract, :time_zone, :string],
343
- [:contract, :trading_hours, :string],
344
- [:contract, :liquid_hours, :string])
345
-
346
- class ContractData
347
- def load
348
- super
349
- @contract = IB::Contract.build @data[:contract]
350
- end
351
- end # ContractData
352
-
353
- ExecutionData =
354
- def_message [11, 8],
355
- # The reqID that was specified previously in the call to reqExecution()
356
- [:request_id, :int],
357
- [:execution, :order_id, :int],
358
- [:contract, :con_id, :int],
359
- [:contract, :symbol, :string],
360
- [:contract, :sec_type, :string],
361
- [:contract, :expiry, :string],
362
- [:contract, :strike, :decimal],
363
- [:contract, :right, :string],
364
- [:contract, :exchange, :string],
365
- [:contract, :currency, :string],
366
- [:contract, :local_symbol, :string],
367
-
368
- [:execution, :exec_id, :string], # Weird format
369
- [:execution, :time, :string],
370
- [:execution, :account_name, :string],
371
- [:execution, :exchange, :string],
372
- [:execution, :side, :string],
373
- [:execution, :shares, :int],
374
- [:execution, :price, :decimal],
375
- [:execution, :perm_id, :int],
376
- [:execution, :client_id, :int],
377
- [:execution, :liquidation, :int],
378
- [:execution, :cumulative_quantity, :int],
379
- [:execution, :average_price, :decimal]
380
-
381
- class ExecutionData
382
- def load
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
- ]
389
- @contract = IB::Contract.build @data[:contract]
390
- @execution = IB::Execution.new @data[:execution]
391
- end
392
-
393
- def to_human
394
- "<ExecutionData #{request_id}: #{contract.to_human}, #{execution}>"
395
- end
396
-
397
- end # ExecutionData
398
-
399
- BondContractData =
400
- def_message [18, 4],
401
- [:request_id, :int],
402
- [:contract, :symbol, :string],
403
- [:contract, :sec_type, :string],
404
- [:contract, :cusip, :string],
405
- [:contract, :coupon, :decimal],
406
- [:contract, :maturity, :string],
407
- [:contract, :issue_date, :string],
408
- [:contract, :ratings, :string],
409
- [:contract, :bond_type, :string],
410
- [:contract, :coupon_type, :string],
411
- [:contract, :convertible, :boolean],
412
- [:contract, :callable, :boolean],
413
- [:contract, :puttable, :boolean],
414
- [:contract, :desc_append, :string],
415
- [:contract, :exchange, :string],
416
- [:contract, :currency, :string],
417
- [:contract, :market_name, :string], # extended
418
- [:contract, :trading_class, :string],
419
- [:contract, :con_id, :int],
420
- [:contract, :min_tick, :decimal],
421
- [:contract, :order_types, :string],
422
- [:contract, :valid_exchanges, :string],
423
- [:contract, :valid_next_option_date, :string],
424
- [:contract, :valid_next_option_type, :string],
425
- [:contract, :valid_next_option_partial, :string],
426
- [:contract, :notes, :string],
427
- [:contract, :long_name, :string]
428
-
429
- class BondContractData
430
- def load
431
- super
432
- @contract = IB::Contract.build @data[:contract]
433
- end
434
- end # BondContractData
435
-
436
- # The server sends this message upon accepting a Delta-Neutral DN RFQ
437
- # - see API Reference p. 26
438
- DeltaNeutralValidation = def_message 56,
439
- [:request_id, :int],
440
- [:contract, :under_con_id, :int],
441
- [:contract, :under_delta, :decimal],
442
- [:contract, :under_price, :decimal]
443
- class DeltaNeutralValidation
444
- def load
445
- super
446
- @contract = IB::Contract.build @data[:contract].merge(:under_comp => true)
447
- end
448
- end # DeltaNeutralValidation
449
-
450
- # RealTimeBar contains following @data:
451
- # :request_id - The ID of the *request* to which this is responding
452
- # :time - The date-time stamp of the start of the bar. The format is offset in
453
- # seconds from the beginning of 1970, same format as the UNIX epoch time
454
- # :bar - received RT Bar
455
- RealTimeBar = def_message 50,
456
- [:request_id, :int],
457
- [:bar, :time, :int],
458
- [:bar, :open, :decimal],
459
- [:bar, :high, :decimal],
460
- [:bar, :low, :decimal],
461
- [:bar, :close, :decimal],
462
- [:bar, :volume, :int],
463
- [:bar, :wap, :decimal],
464
- [:bar, :trades, :int]
465
- class RealTimeBar
466
- def load
467
- super
468
- @bar = IB::Bar.new @data[:bar]
469
- end
470
-
471
- def to_human
472
- "<RealTimeBar: #{request_id} #{time}, #{bar}>"
473
- end
474
- end # RealTimeBar
475
-
476
- ### Messages with really complicated message loading logics (cycles, conditions)
477
-
478
- # This method receives the requested market scanner data results.
479
- # ScannerData contains following @data:
480
- # :request_id - The ID of the request to which this row is responding
481
- # :count - Number of data points returned (size of :results).
482
- # :results - an Array of Hashes, each hash contains a set of
483
- # data about one scanned Contract:
484
- # :contract - a full description of the contract (details).
485
- # :distance - Varies based on query.
486
- # :benchmark - Varies based on query.
487
- # :projection - Varies based on query.
488
- # :legs - Describes combo legs when scan is returning EFP.
489
- ScannerData = def_message [20, 3],
490
- [:request_id, :int], # request id
491
- [:count, :int]
492
- class ScannerData
493
- attr_accessor :results
494
-
495
- def load
496
- super
497
-
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,
515
- }
516
- end
517
- end
518
- end # ScannerData
519
-
520
- # HistoricalData contains following @data:
521
- # General:
522
- # :request_id - The ID of the request to which this is responding
523
- # :count - Number of Historical data points returned (size of :results).
524
- # :results - an Array of Historical Data Bars
525
- # :start_date - beginning of returned Historical data period
526
- # :end_date - end of returned Historical data period
527
- # Each returned Bar in @data[:results] Array contains this data:
528
- # :date - The date-time stamp of the start of the bar. The format is
529
- # determined by the RequestHistoricalData formatDate parameter.
530
- # :open - The bar opening price.
531
- # :high - The high price during the time covered by the bar.
532
- # :low - The low price during the time covered by the bar.
533
- # :close - The bar closing price.
534
- # :volume - The volume during the time covered by the bar.
535
- # :trades - When TRADES historical data is returned, represents number of trades
536
- # that occurred during the time period the bar covers
537
- # :wap - The weighted average price during the time covered by the bar.
538
- # :has_gaps - Whether or not there are gaps in the data.
539
-
540
- HistoricalData = def_message [17, 3],
541
- [:request_id, :int],
542
- [:start_date, :string],
543
- [:end_date, :string],
544
- [:count, :int]
545
- class HistoricalData
546
- attr_accessor :results
547
-
548
- def load
549
- super
550
-
551
- @results = Array.new(@data[:count]) do |_|
552
- IB::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
561
- end
562
- end
563
-
564
- def to_human
565
- "<HistoricalData: #{request_id}, #{count} items, #{start_date} to #{end_date}>"
566
- end
567
- end # HistoricalData
82
+ ### Require standalone source files for more complex message classes:
83
+
84
+ require 'ib-ruby/messages/incoming/alert'
85
+ require 'ib-ruby/messages/incoming/contract_data'
86
+ require 'ib-ruby/messages/incoming/delta_neutral_validation'
87
+ require 'ib-ruby/messages/incoming/execution_data'
88
+ require 'ib-ruby/messages/incoming/historical_data'
89
+ require 'ib-ruby/messages/incoming/market_depths'
90
+ require 'ib-ruby/messages/incoming/open_order'
91
+ require 'ib-ruby/messages/incoming/order_status'
92
+ require 'ib-ruby/messages/incoming/portfolio_value'
93
+ require 'ib-ruby/messages/incoming/real_time_bar'
94
+ require 'ib-ruby/messages/incoming/scanner_data'
95
+ require 'ib-ruby/messages/incoming/ticks'
568
96
 
569
97
  end # module Incoming
570
98
  end # module Messages
571
99
  end # module IB
572
100
 
573
- # Require standalone message source files
574
- require 'ib-ruby/messages/incoming/ticks'
575
- require 'ib-ruby/messages/incoming/open_order'
576
101
 
577
102
  __END__
578
103
  // incoming msg id's