ib-ruby 0.4.3 → 0.4.20
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.
- data/.gitignore +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,807 @@
|
|
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
|
+
module IB
|
10
|
+
module Messages
|
11
|
+
|
12
|
+
# Incoming IB messages
|
13
|
+
module Incoming
|
14
|
+
Classes = Array.new
|
15
|
+
|
16
|
+
# This is just a basic generic message from the server.
|
17
|
+
#
|
18
|
+
# Class variables:
|
19
|
+
# @message_id - int: message id.
|
20
|
+
# @version - int: current version of message format.
|
21
|
+
#
|
22
|
+
# Instance attributes (at minimum):
|
23
|
+
# @data - Hash of actual data read from a stream.
|
24
|
+
#
|
25
|
+
# Override the load(socket) method in your subclass to do actual reading into @data.
|
26
|
+
class AbstractMessage
|
27
|
+
attr_accessor :created_at, :data
|
28
|
+
|
29
|
+
def self.inherited(by)
|
30
|
+
super(by)
|
31
|
+
Classes.push(by)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.message_id
|
35
|
+
@message_id
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(socket, server_version)
|
39
|
+
raise Exception.new("Don't use AbstractMessage directly; use the subclass for your specific message type") if self.class.name == "AbstractMessage"
|
40
|
+
@created_at = Time.now
|
41
|
+
@data = Hash.new
|
42
|
+
@socket = socket
|
43
|
+
@server_version = server_version
|
44
|
+
|
45
|
+
self.load()
|
46
|
+
|
47
|
+
@socket = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_human
|
51
|
+
self.inspect
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Every message loads received message version first
|
57
|
+
def load
|
58
|
+
@data[:version] = @socket.read_int
|
59
|
+
end
|
60
|
+
|
61
|
+
# Load @data from the socket according to the given map.
|
62
|
+
#
|
63
|
+
# map is a series of Arrays in the format [ [ :name, :type ] ],
|
64
|
+
# type identifiers must have a corresponding read_type method on socket (read_int, etc.).
|
65
|
+
# [:version, :int ] is loaded first, by default
|
66
|
+
#
|
67
|
+
#
|
68
|
+
def load_map(*map)
|
69
|
+
##logger.debug("load_maping map: " + map.inspect)
|
70
|
+
map.each { |spec|
|
71
|
+
@data[spec[0]] = @socket.__send__(("read_" + spec[1].to_s).to_sym)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end # class AbstractMessage
|
75
|
+
|
76
|
+
class AbstractTick < AbstractMessage
|
77
|
+
# Returns Symbol with a meaningful name for received tick type
|
78
|
+
def type
|
79
|
+
TICK_TYPES[@data[:tick_type]]
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_human
|
83
|
+
"<#{self.class.to_s.split('::').last} #{type}:" +
|
84
|
+
@data.map do |key, value|
|
85
|
+
" #{key} #{value}" unless [:version, :id, :tick_type].include?(key)
|
86
|
+
end.compact.join(',') + " >"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Macro that defines short message classes using a one-liner
|
91
|
+
def self.def_message message_id, *keys, &human_block
|
92
|
+
base = keys.first.is_a?(Class) ? keys.shift : AbstractMessage
|
93
|
+
Class.new(base) do
|
94
|
+
@message_id = message_id
|
95
|
+
|
96
|
+
define_method(:load) do
|
97
|
+
super()
|
98
|
+
load_map *keys
|
99
|
+
end
|
100
|
+
|
101
|
+
define_method(:to_human, &human_block) if human_block
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
### Actual message classes (short definitions):
|
106
|
+
|
107
|
+
OrderStatus = def_message 3, [:id, :int],
|
108
|
+
[:status, :string],
|
109
|
+
[:filled, :int],
|
110
|
+
[:remaining, :int],
|
111
|
+
[:average_fill_price, :decimal],
|
112
|
+
[:perm_id, :int],
|
113
|
+
[:parent_id, :int],
|
114
|
+
[:last_fill_price, :decimal],
|
115
|
+
[:client_id, :int],
|
116
|
+
[:why_held, :string]
|
117
|
+
|
118
|
+
AccountValue = def_message(6, [:key, :string],
|
119
|
+
[:value, :string],
|
120
|
+
[:currency, :string],
|
121
|
+
[:account_name, :string]) do
|
122
|
+
"<AccountValue: #{@data[:account_name]}, #{@data[:key]}=#{@data[:value]} #{@data[:currency]}>"
|
123
|
+
end
|
124
|
+
|
125
|
+
AccountUpdateTime = def_message(8, [:time_stamp, :string]) do
|
126
|
+
"<AccountUpdateTime: #{@data[:time_stamp]}>"
|
127
|
+
end
|
128
|
+
|
129
|
+
# This message is always sent by TWS automatically at connect.
|
130
|
+
# The IB::Connection class subscribes to it automatically and stores
|
131
|
+
# the order id in its @next_order_id attribute.
|
132
|
+
NextValidID = def_message 9, [:id, :int]
|
133
|
+
|
134
|
+
MarketDepth =
|
135
|
+
def_message 12, [:id, :int],
|
136
|
+
[:position, :int], # The row Id of this market depth entry.
|
137
|
+
[:operation, :int], # How it should be applied to the market depth:
|
138
|
+
# 0 = insert this new order into the row identified by :position
|
139
|
+
# 1 = update the existing order in the row identified by :position
|
140
|
+
# 2 = delete the existing order at the row identified by :position
|
141
|
+
[:side, :int], # side of the book: 0 = ask, 1 = bid
|
142
|
+
[:price, :decimal],
|
143
|
+
[:size, :int]
|
144
|
+
|
145
|
+
MarketDepthL2 =
|
146
|
+
def_message 13, [:id, :int],
|
147
|
+
[:position, :int], # The row Id of this market depth entry.
|
148
|
+
[:market_maker, :string], # The exchange hosting this order.
|
149
|
+
[:operation, :int], # How it should be applied to the market depth:
|
150
|
+
# 0 = insert this new order into the row identified by :position
|
151
|
+
# 1 = update the existing order in the row identified by :position
|
152
|
+
# 2 = delete the existing order at the row identified by :position
|
153
|
+
[:side, :int], # side of the book: 0 = ask, 1 = bid
|
154
|
+
[:price, :decimal],
|
155
|
+
[:size, :int]
|
156
|
+
|
157
|
+
NewsBulletins =
|
158
|
+
def_message 14, [:id, :int], # unique incrementing bulletin ID.
|
159
|
+
[:type, :int], # Type of bulletin. Valid values include:
|
160
|
+
# 1 = Reqular news bulletin
|
161
|
+
# 2 = Exchange no longer available for trading
|
162
|
+
# 3 = Exchange is available for trading
|
163
|
+
[:text, :string], # The bulletin's message text.
|
164
|
+
[:exchange, :string] # Exchange from which this message originated.
|
165
|
+
|
166
|
+
ManagedAccounts =
|
167
|
+
def_message 15, [:accounts_list, :string]
|
168
|
+
|
169
|
+
# Receives previously requested FA configuration information from TWS.
|
170
|
+
ReceiveFA =
|
171
|
+
def_message 16, [:type, :int], # type of Financial Advisor configuration data
|
172
|
+
# being received from TWS. Valid values include:
|
173
|
+
# 1 = GROUPS
|
174
|
+
# 2 = PROFILE
|
175
|
+
# 3 = ACCOUNT ALIASES
|
176
|
+
[:xml, :string] # XML string containing the previously requested
|
177
|
+
# FA configuration information.
|
178
|
+
|
179
|
+
# Receives an XML document that describes the valid parameters that a scanner
|
180
|
+
# subscription can have.
|
181
|
+
ScannerParameters = def_message 19, [:xml, :string]
|
182
|
+
|
183
|
+
# Receives the current system time on the server side.
|
184
|
+
CurrentTime = def_message 49, [:time, :int] # long!
|
185
|
+
|
186
|
+
# Receive Reuters global fundamental market data. There must be a subscription to
|
187
|
+
# Reuters Fundamental set up in Account Management before you can receive this data.
|
188
|
+
FundamentalData = def_message 50, [:id, :int], # request_id
|
189
|
+
[:data, :string]
|
190
|
+
|
191
|
+
ContractDataEnd = def_message 52, [:id, :int] # request_id
|
192
|
+
|
193
|
+
OpenOrderEnd = def_message 53
|
194
|
+
|
195
|
+
AccountDownloadEnd = def_message 54, [:account_name, :string]
|
196
|
+
|
197
|
+
ExecutionDataEnd = def_message 55, [:id, :int] # request_id
|
198
|
+
|
199
|
+
TickSnapshotEnd = def_message 57, [:id, :int] # request_id
|
200
|
+
|
201
|
+
### Actual message classes (long definitions):
|
202
|
+
|
203
|
+
# The IB code seems to dispatch up to two wrapped objects for this message, a tickPrice
|
204
|
+
# and sometimes a tickSize, which seems to be identical to the TICK_SIZE object.
|
205
|
+
#
|
206
|
+
# Important note from
|
207
|
+
# http://chuckcaplan.com/twsapi/index.php/void%20tickPrice%28%29 :
|
208
|
+
#
|
209
|
+
# "The low you get is NOT the low for the day as you'd expect it
|
210
|
+
# to be. It appears IB calculates the low based on all
|
211
|
+
# transactions after 4pm the previous day. The most inaccurate
|
212
|
+
# results occur when the stock moves up in the 4-6pm aftermarket
|
213
|
+
# on the previous day and then gaps open upward in the
|
214
|
+
# morning. The low you receive from TWS can be easily be several
|
215
|
+
# points different from the actual 9:30am-4pm low for the day in
|
216
|
+
# cases like this. If you require a correct traded low for the
|
217
|
+
# day, you can't get it from the TWS API. One possible source to
|
218
|
+
# help build the right data would be to compare against what Yahoo
|
219
|
+
# lists on finance.yahoo.com/q?s=ticker under the "Day's Range"
|
220
|
+
# statistics (be careful here, because Yahoo will use anti-Denial
|
221
|
+
# of Service techniques to hang your connection if you try to
|
222
|
+
# request too many bytes in a short period of time from them). For
|
223
|
+
# most purposes, a good enough approach would start by replacing
|
224
|
+
# the TWS low for the day with Yahoo's day low when you first
|
225
|
+
# start watching a stock ticker; let's call this time T. Then,
|
226
|
+
# update your internal low if the bid or ask tick you receive is
|
227
|
+
# lower than that for the remainder of the day. You should check
|
228
|
+
# against Yahoo again at time T+20min to handle the occasional
|
229
|
+
# case where the stock set a new low for the day in between
|
230
|
+
# T-20min (the real time your original quote was from, taking into
|
231
|
+
# account the delay) and time T. After that you should have a
|
232
|
+
# correct enough low for the rest of the day as long as you keep
|
233
|
+
# updating based on the bid/ask. It could still get slightly off
|
234
|
+
# in a case where a short transaction setting a new low appears in
|
235
|
+
# between ticks of data that TWS sends you. The high is probably
|
236
|
+
# distorted in the same way the low is, which would throw your
|
237
|
+
# results off if the stock traded after-hours and gapped down. It
|
238
|
+
# should be corrected in a similar way as described above if this
|
239
|
+
# is important to you."
|
240
|
+
#
|
241
|
+
# IB then emits at most 2 events on eWrapper:
|
242
|
+
# tickPrice( tickerId, tickType, price, canAutoExecute)
|
243
|
+
# tickSize( tickerId, sizeTickType, size)
|
244
|
+
TickPrice = def_message 1, AbstractTick,
|
245
|
+
[:id, :int], # ticker_id
|
246
|
+
[:tick_type, :int],
|
247
|
+
[:price, :decimal],
|
248
|
+
[:size, :int],
|
249
|
+
[:can_auto_execute, :int]
|
250
|
+
|
251
|
+
TickSize = def_message 2, AbstractTick,
|
252
|
+
[:id, :int], # ticker_id
|
253
|
+
[:tick_type, :int],
|
254
|
+
[:size, :int]
|
255
|
+
|
256
|
+
TickGeneric = def_message 45, AbstractTick,
|
257
|
+
[:id, :int], # ticker_id
|
258
|
+
[:tick_type, :int],
|
259
|
+
[:value, :decimal]
|
260
|
+
|
261
|
+
TickString = def_message 46, AbstractTick,
|
262
|
+
[:id, :int], # ticker_id
|
263
|
+
[:tick_type, :int],
|
264
|
+
[:value, :string]
|
265
|
+
|
266
|
+
TickEFP = def_message 47, AbstractTick,
|
267
|
+
[:id, :int], # ticker_id
|
268
|
+
[:tick_type, :int],
|
269
|
+
[:basis_points, :decimal],
|
270
|
+
[:formatted_basis_points, :string],
|
271
|
+
[:implied_futures_price, :decimal],
|
272
|
+
[:hold_days, :int],
|
273
|
+
[:dividend_impact, :decimal],
|
274
|
+
[:dividends_to_expiry, :decimal]
|
275
|
+
|
276
|
+
# This message is received when the market in an option or its underlier moves.
|
277
|
+
# TWS�s option model volatilities, prices, and deltas, along with the present
|
278
|
+
# value of dividends expected on that options underlier are received.
|
279
|
+
# TickOption message contains following @data:
|
280
|
+
# :id - Ticker Id that was specified previously in the call to reqMktData()
|
281
|
+
# :tick_type - Specifies the type of option computation (see TICK_TYPES).
|
282
|
+
# :implied_volatility - The implied volatility calculated by the TWS option
|
283
|
+
# modeler, using the specified :tick_type value.
|
284
|
+
# :delta - The option delta value.
|
285
|
+
# :option_price - The option price.
|
286
|
+
# :pv_dividend - The present value of dividends expected on the options underlier
|
287
|
+
# :gamma - The option gamma value.
|
288
|
+
# :vega - The option vega value.
|
289
|
+
# :theta - The option theta value.
|
290
|
+
# :under_price - The price of the underlying.
|
291
|
+
class TickOption < AbstractTick
|
292
|
+
@message_id = 21
|
293
|
+
|
294
|
+
# Read @data[key] if it was computed (received value above limit)
|
295
|
+
# Leave @data[key] nil if received value below limit ("not yet computed" indicator)
|
296
|
+
def read_computed key, limit
|
297
|
+
value = @socket.read_decimal # limit-1 is the "not yet computed" indicator
|
298
|
+
@data[key] = value < limit ? nil : value
|
299
|
+
end
|
300
|
+
|
301
|
+
def load
|
302
|
+
super
|
303
|
+
|
304
|
+
@data[:id] = @socket.read_int # ticker_id
|
305
|
+
@data[:tick_type] = @socket.read_int
|
306
|
+
read_computed :implied_volatility, 0 #-1 is the "not yet computed" indicator
|
307
|
+
read_computed :delta, -1 # -2 is the "not yet computed" indicator
|
308
|
+
read_computed :option_price, 0 # -1 is the "not yet computed" indicator
|
309
|
+
read_computed :pv_dividend, 0 # -1 is the "not yet computed" indicator
|
310
|
+
read_computed :gamma, -1 # -2 is the "not yet computed" indicator
|
311
|
+
read_computed :vega, -1 # -2 is the "not yet computed" indicator
|
312
|
+
read_computed :theta, -1 # -2 is the "not yet computed" indicator
|
313
|
+
read_computed :under_price, 0 # -1 is the "not yet computed" indicator
|
314
|
+
end
|
315
|
+
|
316
|
+
def to_human
|
317
|
+
"<TickOption #{type} for #{@data[:id]}: underlying @ #{@data[:under_price]}, "+
|
318
|
+
"option @ #{@data[:option_price]}, IV #{@data[:implied_volatility]}%, " +
|
319
|
+
"delta #{@data[:delta]}, gamma #{@data[:gamma]}, vega #{@data[:vega]}, " +
|
320
|
+
"theta #{@data[:theta]}, pv_dividend #{@data[:pv_dividend]}>"
|
321
|
+
end
|
322
|
+
end # TickOption
|
323
|
+
TickOptionComputation = TickOption
|
324
|
+
|
325
|
+
# Called Error in Java code, but in fact this type of messages also
|
326
|
+
# deliver system alerts and additional (non-error) info from TWS.
|
327
|
+
# It has additional accessors: #code and #message, derived from @data
|
328
|
+
Alert = def_message 4, [:id, :int], [:code, :int], [:message, :string]
|
329
|
+
class Alert
|
330
|
+
def code
|
331
|
+
@data && @data[:code]
|
332
|
+
end
|
333
|
+
|
334
|
+
def message
|
335
|
+
@data && @data[:message]
|
336
|
+
end
|
337
|
+
|
338
|
+
# Is it an Error message?
|
339
|
+
def error?
|
340
|
+
code < 1000
|
341
|
+
end
|
342
|
+
|
343
|
+
# Is it a System message?
|
344
|
+
def system?
|
345
|
+
code > 1000 && code < 2000
|
346
|
+
end
|
347
|
+
|
348
|
+
# Is it a Warning message?
|
349
|
+
def warning?
|
350
|
+
code > 2000
|
351
|
+
end
|
352
|
+
|
353
|
+
def to_human
|
354
|
+
"TWS #{ error? ? 'Error' : system? ? 'System' : 'Warning'
|
355
|
+
} Message #{@data[:code]}: #{@data[:message]}"
|
356
|
+
end
|
357
|
+
end # class ErrorMessage
|
358
|
+
Error = Alert
|
359
|
+
ErrorMessage = Alert
|
360
|
+
|
361
|
+
class OpenOrder < AbstractMessage
|
362
|
+
@message_id = 5
|
363
|
+
|
364
|
+
attr_accessor :order, :contract
|
365
|
+
|
366
|
+
def load
|
367
|
+
super
|
368
|
+
|
369
|
+
@order = Models::Order.new :id => @socket.read_int
|
370
|
+
|
371
|
+
@contract = Models::Contract.new :symbol => @socket.read_string,
|
372
|
+
:sec_type => @socket.read_string,
|
373
|
+
:expiry => @socket.read_string,
|
374
|
+
:strike => @socket.read_decimal,
|
375
|
+
:right => @socket.read_string,
|
376
|
+
:exchange => @socket.read_string,
|
377
|
+
:currency => @socket.read_string,
|
378
|
+
:local_symbol => @socket.read_string
|
379
|
+
|
380
|
+
@order.action = @socket.read_string
|
381
|
+
@order.total_quantity = @socket.read_int
|
382
|
+
@order.order_type = @socket.read_string
|
383
|
+
@order.limit_price = @socket.read_decimal
|
384
|
+
@order.aux_price = @socket.read_decimal
|
385
|
+
@order.tif = @socket.read_string
|
386
|
+
@order.oca_group = @socket.read_string
|
387
|
+
@order.account = @socket.read_string
|
388
|
+
@order.open_close = @socket.read_string
|
389
|
+
@order.origin = @socket.read_int
|
390
|
+
@order.order_ref = @socket.read_string
|
391
|
+
@order.client_id = @socket.read_int
|
392
|
+
@order.perm_id = @socket.read_int
|
393
|
+
@order.outside_rth = (@socket.read_int == 1)
|
394
|
+
@order.hidden = (@socket.read_int == 1)
|
395
|
+
@order.discretionary_amount = @socket.read_decimal
|
396
|
+
@order.good_after_time = @socket.read_string
|
397
|
+
@socket.read_string # skip deprecated sharesAllocation field
|
398
|
+
|
399
|
+
@order.fa_group = @socket.read_string
|
400
|
+
@order.fa_method = @socket.read_string
|
401
|
+
@order.fa_percentage = @socket.read_string
|
402
|
+
@order.fa_profile = @socket.read_string
|
403
|
+
@order.good_till_date = @socket.read_string
|
404
|
+
@order.rule_80A = @socket.read_string
|
405
|
+
@order.percent_offset = @socket.read_decimal
|
406
|
+
@order.settling_firm = @socket.read_string
|
407
|
+
@order.short_sale_slot = @socket.read_int
|
408
|
+
@order.designated_location = @socket.read_string
|
409
|
+
@order.exempt_code = @socket.read_int # skipped in ver 51?
|
410
|
+
@order.auction_strategy = @socket.read_int
|
411
|
+
@order.starting_price = @socket.read_decimal
|
412
|
+
@order.stock_ref_price = @socket.read_decimal
|
413
|
+
@order.delta = @socket.read_decimal
|
414
|
+
@order.stock_range_lower = @socket.read_decimal
|
415
|
+
@order.stock_range_upper = @socket.read_decimal
|
416
|
+
@order.display_size = @socket.read_int
|
417
|
+
#@order.rth_only = @socket.read_boolean
|
418
|
+
@order.block_order = @socket.read_boolean
|
419
|
+
@order.sweep_to_fill = @socket.read_boolean
|
420
|
+
@order.all_or_none = @socket.read_boolean
|
421
|
+
@order.min_quantity = @socket.read_int
|
422
|
+
@order.oca_type = @socket.read_int
|
423
|
+
@order.etrade_only = @socket.read_boolean
|
424
|
+
@order.firm_quote_only = @socket.read_boolean
|
425
|
+
@order.nbbo_price_cap = @socket.read_decimal
|
426
|
+
@order.parent_id = @socket.read_int
|
427
|
+
@order.trigger_method = @socket.read_int
|
428
|
+
@order.volatility = @socket.read_decimal
|
429
|
+
@order.volatility_type = @socket.read_int
|
430
|
+
@order.delta_neutral_order_type = @socket.read_string
|
431
|
+
@order.delta_neutral_aux_price = @socket.read_decimal
|
432
|
+
|
433
|
+
@order.continuous_update = @socket.read_int
|
434
|
+
@order.reference_price_type = @socket.read_int
|
435
|
+
@order.trail_stop_price = @socket.read_decimal
|
436
|
+
@order.basis_points = @socket.read_decimal
|
437
|
+
@order.basis_points_type = @socket.read_int
|
438
|
+
@order.combo_legs_description = @socket.read_string
|
439
|
+
@order.scale_init_level_size = @socket.read_int_max
|
440
|
+
@order.scale_subs_level_size = @socket.read_int_max
|
441
|
+
@order.scale_price_increment = @socket.read_decimal_max
|
442
|
+
@order.clearing_account = @socket.read_string
|
443
|
+
@order.clearing_intent = @socket.read_string
|
444
|
+
@order.not_held = (@socket.read_int == 1)
|
445
|
+
|
446
|
+
under_comp_present = (@socket.read_int == 1)
|
447
|
+
|
448
|
+
if under_comp_present
|
449
|
+
@contract.under_comp =
|
450
|
+
Models::Contract::UnderComp.new :con_id => @socket.read_int,
|
451
|
+
:delta => @socket.read_decimal,
|
452
|
+
:price => @socket.read_decimal
|
453
|
+
end
|
454
|
+
|
455
|
+
@order.algo_strategy = @socket.read_string
|
456
|
+
|
457
|
+
unless @order.algo_strategy.nil? || @order.algo_strategy.empty?
|
458
|
+
algo_params_count = @socket.read_int
|
459
|
+
if algo_params_count > 0
|
460
|
+
@order.algo_params = Hash.new
|
461
|
+
algo_params_count.times do
|
462
|
+
tag = @socket.read_string
|
463
|
+
value = @socket.read_string
|
464
|
+
@order.algo_params[tag] = value
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
@order.what_if = (@socket.read_int == 1)
|
470
|
+
@order.status = @socket.read_string
|
471
|
+
@order.init_margin = @socket.read_string
|
472
|
+
@order.maint_margin = @socket.read_string
|
473
|
+
@order.equity_with_loan = @socket.read_string
|
474
|
+
@order.commission = @socket.read_decimal_max
|
475
|
+
@order.min_commission = @socket.read_decimal_max
|
476
|
+
@order.max_commission = @socket.read_decimal_max
|
477
|
+
@order.commission_currency = @socket.read_string
|
478
|
+
@order.warning_text = @socket.read_string
|
479
|
+
end
|
480
|
+
end # OpenOrder
|
481
|
+
|
482
|
+
class PortfolioValue < AbstractMessage
|
483
|
+
@message_id = 7
|
484
|
+
|
485
|
+
attr_accessor :contract
|
486
|
+
|
487
|
+
def load
|
488
|
+
super
|
489
|
+
|
490
|
+
@contract = Models::Contract.new :con_id => @socket.read_int,
|
491
|
+
:symbol => @socket.read_string,
|
492
|
+
:sec_type => @socket.read_string,
|
493
|
+
:expiry => @socket.read_string,
|
494
|
+
:strike => @socket.read_decimal,
|
495
|
+
:right => @socket.read_string,
|
496
|
+
:multiplier => @socket.read_string,
|
497
|
+
:primary_exchange => @socket.read_string,
|
498
|
+
:currency => @socket.read_string,
|
499
|
+
:local_symbol => @socket.read_string
|
500
|
+
load_map [:position, :int],
|
501
|
+
[:market_price, :decimal],
|
502
|
+
[:market_value, :decimal],
|
503
|
+
[:average_cost, :decimal],
|
504
|
+
[:unrealized_pnl, :decimal],
|
505
|
+
[:realized_pnl, :decimal],
|
506
|
+
[:account_name, :string]
|
507
|
+
end
|
508
|
+
|
509
|
+
def to_human
|
510
|
+
"<PortfolioValue: #{@contract.to_human} (#{@data[:position]}): Market #{@data[:market_price]}" +
|
511
|
+
" price #{@data[:market_value]} value; PnL: #{@data[:unrealized_pnl]} unrealized," +
|
512
|
+
" #{@data[:realized_pnl]} realized; account #{@data[:account_name]}>"
|
513
|
+
end
|
514
|
+
|
515
|
+
end # PortfolioValue
|
516
|
+
|
517
|
+
class ContractData < AbstractMessage
|
518
|
+
@message_id = 10
|
519
|
+
|
520
|
+
attr_accessor :contract
|
521
|
+
|
522
|
+
def load
|
523
|
+
super
|
524
|
+
load_map [:id, :int] # request id
|
525
|
+
|
526
|
+
@contract =
|
527
|
+
Models::Contract.new :symbol => @socket.read_string,
|
528
|
+
:sec_type => @socket.read_string,
|
529
|
+
:expiry => @socket.read_string,
|
530
|
+
:strike => @socket.read_decimal,
|
531
|
+
:right => @socket.read_string,
|
532
|
+
:exchange => @socket.read_string,
|
533
|
+
:currency => @socket.read_string,
|
534
|
+
:local_symbol => @socket.read_string,
|
535
|
+
|
536
|
+
:market_name => @socket.read_string,
|
537
|
+
:trading_class => @socket.read_string,
|
538
|
+
:con_id => @socket.read_int,
|
539
|
+
:min_tick => @socket.read_decimal,
|
540
|
+
:multiplier => @socket.read_string,
|
541
|
+
:order_types => @socket.read_string,
|
542
|
+
:valid_exchanges => @socket.read_string,
|
543
|
+
:price_magnifier => @socket.read_int,
|
544
|
+
:under_con_id => @socket.read_int,
|
545
|
+
:long_name => @socket.read_string,
|
546
|
+
:primary_exchange => @socket.read_string,
|
547
|
+
:contract_month => @socket.read_string,
|
548
|
+
:industry => @socket.read_string,
|
549
|
+
:category => @socket.read_string,
|
550
|
+
:subcategory => @socket.read_string,
|
551
|
+
:time_zone => @socket.read_string,
|
552
|
+
:trading_hours => @socket.read_string,
|
553
|
+
:liquid_hours => @socket.read_string
|
554
|
+
end
|
555
|
+
end # ContractData
|
556
|
+
|
557
|
+
class ExecutionData < AbstractMessage
|
558
|
+
@message_id = 11
|
559
|
+
|
560
|
+
attr_accessor :contract, :execution
|
561
|
+
|
562
|
+
def load
|
563
|
+
super
|
564
|
+
load_map [:id, :int], # request_id
|
565
|
+
[:order_id, :int]
|
566
|
+
|
567
|
+
@contract =
|
568
|
+
Models::Contract.new :con_id => @socket.read_int,
|
569
|
+
:symbol => @socket.read_string,
|
570
|
+
:sec_type => @socket.read_string,
|
571
|
+
:expiry => @socket.read_string,
|
572
|
+
:strike => @socket.read_decimal,
|
573
|
+
:right => @socket.read_string,
|
574
|
+
:exchange => @socket.read_string,
|
575
|
+
:currency => @socket.read_string,
|
576
|
+
:local_symbol => @socket.read_string
|
577
|
+
@execution =
|
578
|
+
Models::Execution.new :order_id => @data[:order_id],
|
579
|
+
:exec_id => @socket.read_string,
|
580
|
+
:time => @socket.read_string,
|
581
|
+
:account_number => @socket.read_string,
|
582
|
+
:exchange => @socket.read_string,
|
583
|
+
:side => @socket.read_string,
|
584
|
+
:shares => @socket.read_int,
|
585
|
+
:price => @socket.read_decimal,
|
586
|
+
:perm_id => @socket.read_int,
|
587
|
+
:client_id => @socket.read_int,
|
588
|
+
:liquidation => @socket.read_int,
|
589
|
+
:cumulative_quantity => @socket.read_int,
|
590
|
+
:average_price => @socket.read_decimal
|
591
|
+
end
|
592
|
+
end # ExecutionData
|
593
|
+
|
594
|
+
# HistoricalData contains following @data:
|
595
|
+
# :id - The ID of the request to which this is responding
|
596
|
+
# :count - Number of data points returned (size of :results).
|
597
|
+
# :results - an Array of Historical Data Bars
|
598
|
+
# :start_date
|
599
|
+
# :end_date
|
600
|
+
# :completed_indicator - string in stupid legacy format
|
601
|
+
class HistoricalData < AbstractMessage
|
602
|
+
@message_id = 17
|
603
|
+
|
604
|
+
def load
|
605
|
+
super
|
606
|
+
load_map [:id, :int],
|
607
|
+
[:start_date, :string],
|
608
|
+
[:end_date, :string],
|
609
|
+
[:count, :int]
|
610
|
+
|
611
|
+
@data[:completed_indicator] =
|
612
|
+
"finished-#{@data[:start_date]}-#{@data[:end_date]}"
|
613
|
+
|
614
|
+
@data[:results] = Array.new(@data[:count]) do |index|
|
615
|
+
Models::Bar.new :date => @socket.read_string,
|
616
|
+
:open => @socket.read_decimal,
|
617
|
+
:high => @socket.read_decimal,
|
618
|
+
:low => @socket.read_decimal,
|
619
|
+
:close => @socket.read_decimal,
|
620
|
+
:volume => @socket.read_int,
|
621
|
+
:wap => @socket.read_decimal,
|
622
|
+
:has_gaps => @socket.read_string,
|
623
|
+
:trades => @socket.read_int
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
def to_human
|
628
|
+
"<HistoricalData: req id #{@data[:id]}, #{@data[:item_count]} items, from #{@data[:start_date_str]} to #{@data[:end_date_str]}>"
|
629
|
+
end
|
630
|
+
end # HistoricalData
|
631
|
+
|
632
|
+
class BondContractData < AbstractMessage
|
633
|
+
@message_id = 18
|
634
|
+
|
635
|
+
attr_accessor :contract
|
636
|
+
|
637
|
+
def load
|
638
|
+
super
|
639
|
+
load_map [:id, :int] # request id
|
640
|
+
|
641
|
+
@contract =
|
642
|
+
Models::Contract.new :symbol => @socket.read_string,
|
643
|
+
:sec_type => @socket.read_string,
|
644
|
+
:cusip => @socket.read_string,
|
645
|
+
:coupon => @socket.read_decimal,
|
646
|
+
:maturity => @socket.read_string,
|
647
|
+
:issue_date => @socket.read_string,
|
648
|
+
:ratings => @socket.read_string,
|
649
|
+
:bond_type => @socket.read_string,
|
650
|
+
:coupon_type => @socket.read_string,
|
651
|
+
:convertible => @socket.read_boolean,
|
652
|
+
:callable => @socket.read_boolean,
|
653
|
+
:puttable => @socket.read_boolean,
|
654
|
+
:desc_append => @socket.read_string,
|
655
|
+
:exchange => @socket.read_string,
|
656
|
+
:currency => @socket.read_string,
|
657
|
+
:market_name => @socket.read_string,
|
658
|
+
:trading_class => @socket.read_string,
|
659
|
+
:con_id => @socket.read_int,
|
660
|
+
:min_tick => @socket.read_decimal,
|
661
|
+
:order_types => @socket.read_string,
|
662
|
+
:valid_exchanges => @socket.read_string,
|
663
|
+
:valid_next_option_date => @socket.read_string,
|
664
|
+
:valid_next_option_type => @socket.read_string,
|
665
|
+
:valid_next_option_partial => @socket.read_string,
|
666
|
+
:notes => @socket.read_string,
|
667
|
+
:long_name => @socket.read_string
|
668
|
+
end
|
669
|
+
end # BondContractData
|
670
|
+
|
671
|
+
# This method receives the requested market scanner data results.
|
672
|
+
# ScannerData contains following @data:
|
673
|
+
# :id - The ID of the request to which this row is responding
|
674
|
+
# :count - Number of data points returned (size of :results).
|
675
|
+
# :results - an Array of Hashes, each hash contains a set of
|
676
|
+
# data about one scanned Contract:
|
677
|
+
# :contract - a full description of the contract (details).
|
678
|
+
# :distance - Varies based on query.
|
679
|
+
# :benchmark - Varies based on query.
|
680
|
+
# :projection - Varies based on query.
|
681
|
+
# :legs - Describes combo legs when scan is returning EFP.
|
682
|
+
class ScannerData < AbstractMessage
|
683
|
+
@message_id = 20
|
684
|
+
|
685
|
+
def load
|
686
|
+
super
|
687
|
+
load_map [:id, :int],
|
688
|
+
[:count, :int]
|
689
|
+
|
690
|
+
@data[:results] = Array.new(@data[:count]) do |index|
|
691
|
+
{:rank => @socket.read_int,
|
692
|
+
:contract => Contract.new(:con_id => @socket.read_int,
|
693
|
+
:symbol => @socket.read_str,
|
694
|
+
:sec_type => @socket.read_str,
|
695
|
+
:expiry => @socket.read_str,
|
696
|
+
:strike => @socket.read_decimal,
|
697
|
+
:right => @socket.read_str,
|
698
|
+
:exchange => @socket.read_str,
|
699
|
+
:currency => @socket.read_str,
|
700
|
+
:local_symbol => @socket.read_str,
|
701
|
+
:market_name => @socket.read_str,
|
702
|
+
:trading_class => @socket.read_str),
|
703
|
+
:distance => @socket.read_str,
|
704
|
+
:benchmark => @socket.read_str,
|
705
|
+
:projection => @socket.read_str,
|
706
|
+
:legs => @socket.read_str,
|
707
|
+
}
|
708
|
+
#eWrapper().scannerData(tickerId, rank, contract, distance,
|
709
|
+
# benchmark, projection, legsStr);
|
710
|
+
|
711
|
+
end
|
712
|
+
|
713
|
+
#eWrapper().scannerDataEnd(tickerId);
|
714
|
+
end
|
715
|
+
end # ScannerData
|
716
|
+
|
717
|
+
# HistoricalData contains following @data:
|
718
|
+
# :id - The ID of the *request* to which this is responding
|
719
|
+
# :time - The date-time stamp of the start of the bar. The format is offset in
|
720
|
+
# seconds from the beginning of 1970, same format as the UNIX epoch time
|
721
|
+
# :bar - received RT Bar
|
722
|
+
class RealTimeBar < AbstractMessage
|
723
|
+
@message_id = 50
|
724
|
+
|
725
|
+
attr_accessor :bar
|
726
|
+
|
727
|
+
def load
|
728
|
+
super
|
729
|
+
load_map [:id, :int],
|
730
|
+
[:time, :int] # long!
|
731
|
+
|
732
|
+
@bar = Models::Bar.new :date => Time.at(@data[:time]),
|
733
|
+
:open => @socket.read_decimal,
|
734
|
+
:high => @socket.read_decimal,
|
735
|
+
:low => @socket.read_decimal,
|
736
|
+
:close => @socket.read_decimal,
|
737
|
+
:volume => @socket.read_int,
|
738
|
+
:wap => @socket.read_decimal,
|
739
|
+
:trades => @socket.read_int
|
740
|
+
end
|
741
|
+
|
742
|
+
def to_human
|
743
|
+
"<RealTimeBar: req id #{@data[:id]}, #{@bar}>"
|
744
|
+
end
|
745
|
+
end # RealTimeBar
|
746
|
+
RealTimeBars = RealTimeBar
|
747
|
+
|
748
|
+
# The server sends this message upon accepting a Delta-Neutral DN RFQ
|
749
|
+
# - see API Reference p. 26
|
750
|
+
class DeltaNeutralValidation < AbstractMessage
|
751
|
+
@message_id = 56
|
752
|
+
|
753
|
+
attr_accessor :contract
|
754
|
+
|
755
|
+
def load
|
756
|
+
super
|
757
|
+
load_map [:id, :int] # request id
|
758
|
+
|
759
|
+
@contract = Models::Contract.new :under_comp => true,
|
760
|
+
:under_con_id => @socket.read_int,
|
761
|
+
:under_delta => @socket.read_decimal,
|
762
|
+
:under_price => @socket.read_decimal
|
763
|
+
end
|
764
|
+
end # DeltaNeutralValidation
|
765
|
+
|
766
|
+
Table = Hash.new
|
767
|
+
Classes.each { |msg_class| Table[msg_class.message_id] = msg_class }
|
768
|
+
|
769
|
+
end # module Incoming
|
770
|
+
end # module Messages
|
771
|
+
end # module IB
|
772
|
+
__END__
|
773
|
+
|
774
|
+
// incoming msg id's
|
775
|
+
static final int TICK_PRICE = 1; * TODO: realize both events
|
776
|
+
static final int TICK_SIZE = 2; *
|
777
|
+
static final int ORDER_STATUS = 3; *
|
778
|
+
static final int ERR_MSG = 4; *
|
779
|
+
static final int OPEN_ORDER = 5; *
|
780
|
+
static final int ACCT_VALUE = 6; *
|
781
|
+
static final int PORTFOLIO_VALUE = 7; *
|
782
|
+
static final int ACCT_UPDATE_TIME = 8; *
|
783
|
+
static final int NEXT_VALID_ID = 9; *
|
784
|
+
static final int CONTRACT_DATA = 10; *
|
785
|
+
static final int EXECUTION_DATA = 11; *
|
786
|
+
static final int MARKET_DEPTH = 12; *
|
787
|
+
static final int MARKET_DEPTH_L2 = 13; *
|
788
|
+
static final int NEWS_BULLETINS = 14; *
|
789
|
+
static final int MANAGED_ACCTS = 15; *
|
790
|
+
static final int RECEIVE_FA = 16; *
|
791
|
+
static final int HISTORICAL_DATA = 17; *
|
792
|
+
static final int BOND_CONTRACT_DATA = 18; *
|
793
|
+
static final int SCANNER_PARAMETERS = 19; *
|
794
|
+
static final int SCANNER_DATA = 20; *
|
795
|
+
static final int TICK_OPTION_COMPUTATION = 21; *
|
796
|
+
static final int TICK_GENERIC = 45; *
|
797
|
+
static final int TICK_STRING = 46; *
|
798
|
+
static final int TICK_EFP = 47; *
|
799
|
+
static final int CURRENT_TIME = 49; *
|
800
|
+
static final int REAL_TIME_BARS = 50; *
|
801
|
+
static final int FUNDAMENTAL_DATA = 51; *
|
802
|
+
static final int CONTRACT_DATA_END = 52; *
|
803
|
+
static final int OPEN_ORDER_END = 53; *
|
804
|
+
static final int ACCT_DOWNLOAD_END = 54; *
|
805
|
+
static final int EXECUTION_DATA_END = 55; *
|
806
|
+
static final int DELTA_NEUTRAL_VALIDATION = 56; *
|
807
|
+
static final int TICK_SNAPSHOT_END = 57; *
|