ib-ruby 0.7.4 → 0.7.6

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 (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