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.
Files changed (50) hide show
  1. data/.gitignore +32 -0
  2. data/HISTORY +68 -0
  3. data/README.rdoc +9 -6
  4. data/VERSION +1 -1
  5. data/bin/account_info +29 -0
  6. data/bin/contract_details +37 -0
  7. data/bin/depth_of_market +43 -0
  8. data/bin/historic_data +62 -0
  9. data/bin/{RequestHistoricData → historic_data_cli} +46 -91
  10. data/bin/market_data +49 -0
  11. data/bin/option_data +45 -0
  12. data/bin/template +21 -0
  13. data/bin/time_and_sales +63 -0
  14. data/lib/ib-ruby/connection.rb +166 -0
  15. data/lib/ib-ruby/constants.rb +91 -0
  16. data/lib/ib-ruby/messages/incoming.rb +807 -0
  17. data/lib/ib-ruby/messages/outgoing.rb +573 -0
  18. data/lib/ib-ruby/messages.rb +8 -1445
  19. data/lib/ib-ruby/models/bar.rb +26 -0
  20. data/lib/ib-ruby/models/contract.rb +335 -0
  21. data/lib/ib-ruby/models/execution.rb +55 -0
  22. data/lib/ib-ruby/models/model.rb +20 -0
  23. data/lib/ib-ruby/models/order.rb +262 -0
  24. data/lib/ib-ruby/models.rb +11 -0
  25. data/lib/ib-ruby/socket.rb +50 -0
  26. data/lib/ib-ruby/symbols/forex.rb +32 -72
  27. data/lib/ib-ruby/symbols/futures.rb +47 -68
  28. data/lib/ib-ruby/symbols/options.rb +30 -0
  29. data/lib/ib-ruby/symbols/stocks.rb +23 -0
  30. data/lib/ib-ruby/symbols.rb +9 -0
  31. data/lib/ib-ruby.rb +7 -8
  32. data/lib/legacy/bin/account_info_old +36 -0
  33. data/lib/legacy/bin/historic_data_old +81 -0
  34. data/lib/legacy/bin/market_data_old +68 -0
  35. data/lib/legacy/datatypes.rb +485 -0
  36. data/lib/legacy/ib-ruby.rb +10 -0
  37. data/lib/legacy/ib.rb +226 -0
  38. data/lib/legacy/messages.rb +1458 -0
  39. data/lib/version.rb +2 -3
  40. data/spec/ib-ruby/models/contract_spec.rb +261 -0
  41. data/spec/ib-ruby/models/order_spec.rb +64 -0
  42. data/spec/ib-ruby_spec.rb +0 -131
  43. metadata +106 -76
  44. data/bin/AccountInfo +0 -67
  45. data/bin/HistoricToCSV +0 -111
  46. data/bin/RequestMarketData +0 -78
  47. data/bin/SimpleTimeAndSales +0 -98
  48. data/bin/ib-ruby +0 -8
  49. data/lib/ib-ruby/datatypes.rb +0 -400
  50. data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,1458 @@
1
+ #
2
+ # Copyright (C) 2006 Blue Voodoo Magic LLC.
3
+ #
4
+ # This library is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 2.1 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA
18
+
19
+
20
+ # EClientSocket.java uses sendMax() rather than send() for a number of these.
21
+ # It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
22
+ # These fields are initialized to this MAX_VALUE.
23
+ # This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
24
+
25
+ module IB
26
+
27
+ #logger = Logger.new(STDERR)
28
+
29
+ class ExtremelyAbstractMessage
30
+ attr_reader :created_at
31
+
32
+ def to_human
33
+ self.inspect
34
+ end
35
+ end
36
+
37
+
38
+ ################################################################
39
+ #### Outgoing messages
40
+ ################################################################
41
+
42
+ module OutgoingMessages
43
+ EOL = "\0"
44
+
45
+ class AbstractMessage < ExtremelyAbstractMessage
46
+ def self.message_id
47
+ raise Exception("AbstractMessage.message_id called - you need to override this in a subclass.")
48
+ end
49
+
50
+ # data is a Hash.
51
+ def initialize(data=nil)
52
+ @created_at = Time.now
53
+ @data = Datatypes::StringentHash.new(data)
54
+ end
55
+
56
+ # This causes the message to send itself over the server socket in server[:socket].
57
+ # "server" is the @server instance variable from the IB object.
58
+ # You can also use this to e.g. get the server version number.
59
+ #
60
+ # Subclasses can either override this method for precise control
61
+ # over how stuff gets sent to the server, or else define a
62
+ # method queue() that returns an Array of elements that ought to
63
+ # be sent to the server by calling to_s on each one and
64
+ # postpending a '\0'.
65
+ #
66
+ def send(server)
67
+ self.queue(server).each { |datum|
68
+
69
+ # TWS wants to receive booleans as 1 or 0... rewrite as necessary.
70
+ datum = "1" if datum == true
71
+ datum = "0" if datum == false
72
+ #
73
+ #print 'SENDING: '
74
+ #p datum
75
+ server[:socket].syswrite(datum.to_s + "\0")
76
+ }
77
+ end
78
+
79
+ def queue
80
+ raise Exception("AbstractMessage.queue() called - you need to override this in a subclass.")
81
+ end
82
+
83
+
84
+ protected
85
+
86
+ def requireVersion(server, version)
87
+ raise(Exception.new("TWS version >= #{version} required.")) if server[:version] < version
88
+ end
89
+
90
+ # Returns EOL instead of datum if datum is nil, providing the same functionality
91
+ # as sendMax() in the Java version, which uses Double.MAX_VALUE to mean "item not set"
92
+ # in a variable, and replaces that with EOL on send.
93
+ def nilFilter(datum)
94
+ datum.nil? ? EOL : datum
95
+ end
96
+
97
+ end # AbstractMessage
98
+
99
+
100
+ # Data format is { :ticker_id => int, :contract => Datatypes::Contract }
101
+ class RequestMarketData < AbstractMessage
102
+ def self.message_id
103
+ 1
104
+ end
105
+
106
+ def queue(server)
107
+ queue = [self.class.message_id,
108
+ 5, # message version number
109
+ @data[:ticker_id]
110
+ ].concat(@data[:contract].serialize_long(server[:version]))
111
+
112
+ # No idea what "BAG" means. Copied from the Java code.
113
+ queue.concat(@data[:contract].serialize_combo_legs
114
+ ) if server[:version] >= 8 && @data[:contract].sec_type == "BAG"
115
+
116
+ queue
117
+ end # queue
118
+ end # RequestMarketData
119
+
120
+ # Data format is { :ticker_id => int }
121
+ class CancelMarketData < AbstractMessage
122
+ def self.message_id
123
+ 2
124
+ end
125
+
126
+ def queue(server)
127
+ [self.class.message_id,
128
+ 1, # message version number
129
+ @data[:ticker_id]]
130
+ end # queue
131
+ end # CancelMarketData
132
+
133
+ # Data format is { :order_id => int, :contract => Contract, :order => Order }
134
+ class PlaceOrder < AbstractMessage
135
+ def self.message_id
136
+ 3
137
+ end
138
+
139
+ def queue(server)
140
+ queue = [self.class.message_id,
141
+ 20, # version
142
+ @data[:order_id],
143
+ @data[:contract].symbol,
144
+ @data[:contract].sec_type,
145
+ @data[:contract].expiry,
146
+ @data[:contract].strike,
147
+ @data[:contract].right
148
+ ]
149
+ queue.push(@data[:contract].multiplier) if server[:version] >= 15
150
+ queue.push(@data[:contract].exchange) if server[:version] >= 14
151
+ queue.push(@data[:contract].currency)
152
+ queue.push(@data[:contract].local_symbol) if server[:version] >= 2
153
+
154
+ queue.concat([
155
+ @data[:order].tif,
156
+ @data[:order].oca_group,
157
+ @data[:order].account,
158
+ @data[:order].open_close,
159
+ @data[:order].origin,
160
+ @data[:order].order_ref,
161
+ @data[:order].transmit
162
+ ])
163
+
164
+ queue.push(@data[:contract].parent_id) if server[:version] >= 4
165
+
166
+ queue.concat([
167
+ @data[:order].block_order,
168
+ @data[:order].sweep_to_fill,
169
+ @data[:order].display_size,
170
+ @data[:order].trigger_method,
171
+ @data[:order].outside_rth
172
+ ]) if server[:version] >= 5
173
+
174
+ queue.push(@data[:order].hidden) if server[:version] >= 7
175
+
176
+
177
+ queue.concat(@data[:contract].serialize_combo_legs(true)) if server[:version] >= 8 &&
178
+ @data[:contract].sec_type.upcase == "BAG" # "BAG" is defined as a constant in EClientSocket.java, line 45
179
+
180
+ queue.push(@data[:order].shares_allocation) if server[:version] >= 9 # EClientSocket.java says this is deprecated. No idea.
181
+ queue.push(@data[:order].discretionary_amount) if server[:version] >= 10
182
+ queue.push(@data[:order].good_after_time) if server[:version] >= 11
183
+ queue.push(@data[:order].good_till_date) if server[:version] >= 12
184
+
185
+ queue.concat([
186
+ @data[:order].fa_group,
187
+ @data[:order].fa_method,
188
+ @data[:order].fa_percentage,
189
+ @data[:order].fa_profile
190
+ ]) if server[:version] >= 13
191
+
192
+ queue.concat([
193
+ @data[:order].short_sale_slot,
194
+ @data[:order].designated_location
195
+ ]) if server[:version] >= 18
196
+
197
+ queue.concat([
198
+ @data[:order].oca_type,
199
+ @data[:order].rth_only,
200
+ @data[:order].rule_80a,
201
+ @data[:order].settling_firm,
202
+ @data[:order].all_or_none,
203
+ nilFilter(@data[:order].min_quantity),
204
+ nilFilter(@data[:order].percent_offset),
205
+ @data[:order].etrade_only,
206
+ @data[:order].firm_quote_only,
207
+ nilFilter(@data[:order].nbbo_price_cap),
208
+ nilFilter(@data[:order].auction_strategy),
209
+ nilFilter(@data[:order].starting_price),
210
+ nilFilter(@data[:order].stock_ref_price),
211
+ nilFilter(@data[:order].delta),
212
+
213
+ # Says the Java here:
214
+ # "// Volatility orders had specific watermark price attribs in server version 26"
215
+ # I have no idea what this means.
216
+
217
+ ((server[:version] == 26 && @data[:order].order_type.upcase == "VOL") ? EOL : @data[:order].stock_range_lower),
218
+ ((server[:version] == 26 && @data[:order].order_type.upcase == "VOL") ? EOL : @data[:order].stock_range_upper),
219
+
220
+ ]) if server[:version] >= 19
221
+
222
+ queue.push(@data[:order].override_percentage_constraints) if server[:version] >= 22
223
+
224
+ # Volatility orders
225
+ if server[:version] >= 26
226
+ queue.concat([nilFilter(@data[:order].volatility),
227
+ nilFilter(@data[:order].volatility_type)])
228
+
229
+ if server[:version] < 28
230
+ queue.push(@data[:order].delta_neutral_order_type.upcase == "MKT")
231
+ else
232
+ queue.concat([@data[:order].delta_neutral_order_type,
233
+ nilFilter(@data[:order].delta_neutral_aux_price)
234
+ ])
235
+ end
236
+
237
+ queue.push(@data[:order].continuous_update)
238
+ queue.concat([
239
+ (@data[:order].order_type.upcase == "VOL" ? @data[:order].stock_range_lower : EOL),
240
+ (@data[:order].order_type.upcase == "VOL" ? @data[:order].stock_range_upper : EOL)
241
+ ]) if server[:version] == 26
242
+
243
+ queue.push(@data[:order].reference_price_type)
244
+
245
+ end # if version >= 26
246
+
247
+ queue
248
+ end # queue()
249
+
250
+ end # PlaceOrder
251
+
252
+ # Data format is { :id => id-to-cancel }
253
+ class CancelOrder < AbstractMessage
254
+ def self.message_id
255
+ 4
256
+ end
257
+
258
+ def queue(server)
259
+ [
260
+ self.class.message_id,
261
+ 1, # version
262
+ @data[:id]
263
+ ]
264
+ end # queue
265
+ end # CancelOrder
266
+
267
+ class RequestOpenOrders < AbstractMessage
268
+ def self.message_id
269
+ 5
270
+ end
271
+
272
+ def queue(server)
273
+ [self.class.message_id,
274
+ 1 # version
275
+ ]
276
+ end
277
+ end # RequestOpenOrders
278
+
279
+ # Data is { :subscribe => boolean, :account_code => string }
280
+ #
281
+ # :account_code is only necessary for advisor accounts. Set it to
282
+ # empty ('') for a standard account.
283
+ #
284
+ class RequestAccountData < AbstractMessage
285
+ def self.message_id
286
+ 6
287
+ end
288
+
289
+ def queue(server)
290
+ queue = [self.class.message_id,
291
+ 2, # version
292
+ @data[:subscribe]
293
+ ]
294
+ queue.push(@data[:account_code]) if server[:version] >= 9
295
+ queue
296
+ end
297
+ end # RequestAccountData
298
+
299
+
300
+ # data = { :filter => ExecutionFilter ]
301
+ class RequestExecutions < AbstractMessage
302
+ def self.message_id
303
+ 7
304
+ end
305
+
306
+ def queue(server)
307
+ queue = [self.class.message_id,
308
+ 2 # version
309
+ ]
310
+
311
+ queue.concat([
312
+ @data[:filter].client_id,
313
+ @data[:filter].acct_code,
314
+
315
+ # The Java says: 'Note that the valid format for m_time is "yyyymmdd-hh:mm:ss"'
316
+ @data[:filter].time,
317
+ @data[:filter].symbol,
318
+ @data[:filter].sec_type,
319
+ @data[:filter].exchange,
320
+ @data[:filter].side
321
+ ]) if server[:version] >= 9
322
+
323
+ queue
324
+ end # queue
325
+ end # RequestExecutions
326
+
327
+
328
+ # data = { :number_of_ids => int }
329
+ class RequestIds < AbstractMessage
330
+ def self.message_id
331
+ 8
332
+ end
333
+
334
+ def queue(server)
335
+ [self.class.message_id,
336
+ 1, # version
337
+ @data[:number_of_ids]
338
+ ]
339
+ end
340
+ end # RequestIds
341
+
342
+
343
+ # data => { :contract => Contract }
344
+ class RequestContractData < AbstractMessage
345
+ def self.message_id
346
+ 9
347
+ end
348
+
349
+ def queue(server)
350
+ requireVersion(server, 4)
351
+
352
+ queue = [
353
+ self.class.message_id,
354
+ 2, # version
355
+ @data[:contract].symbol,
356
+ @data[:contract].sec_type,
357
+ @data[:contract].expiry,
358
+ @data[:contract].strike,
359
+ @data[:contract].right
360
+ ]
361
+ queue.push(@data[:contract].multiplier) if server[:version] >= 15
362
+
363
+ queue.concat([
364
+ @data[:contract].exchange,
365
+ @data[:contract].currency,
366
+ @data[:contract].local_symbol,
367
+ ])
368
+
369
+ queue
370
+ end # send
371
+ end # RequestContractData
372
+
373
+ # data = { :ticker_id => int, :contract => Contract, :num_rows => int }
374
+ class RequestMarketDepth < AbstractMessage
375
+ def self.message_id
376
+ 10
377
+ end
378
+
379
+ def queue(server)
380
+ requireVersion(server, 6)
381
+
382
+ queue = [self.class.message_id,
383
+ 3, # version
384
+ @data[:ticker_id]
385
+ ]
386
+ queue.concat(@data[:contract].serialize_short(server[:version]))
387
+ queue.push(@data[:num_rows]) if server[:version] >= 19
388
+
389
+ queue
390
+
391
+ end # queue
392
+ end # RequestMarketDepth
393
+
394
+ # data = { :ticker_id => int }
395
+ class CancelMarketDepth < AbstractMessage
396
+ def self.message_id
397
+ 11
398
+ end
399
+
400
+ def queue(server)
401
+ requireVersion(self, 6)
402
+
403
+ [self.class.message_id,
404
+ 1, # version
405
+ @data[:ticker_id]
406
+ ]
407
+ end
408
+ end # CancelMarketDepth
409
+
410
+
411
+ # data = { :all_messages => boolean }
412
+ class RequestNewsBulletins < AbstractMessage
413
+ def self.message_id
414
+ 12
415
+ end
416
+
417
+ def queue(server)
418
+ [self.class.message_id,
419
+ 1, # version
420
+ @data[:all_messages]
421
+ ]
422
+ end
423
+ end # RequestNewsBulletins
424
+
425
+ class CancelNewsBulletins < AbstractMessage
426
+ def self.message_id
427
+ 13
428
+ end
429
+
430
+ def queue(server)
431
+ [self.class.message_id,
432
+ 1 # version
433
+ ]
434
+ end
435
+ end # CancelNewsBulletins
436
+
437
+ # data = { :loglevel => int }
438
+ class SetServerLoglevel < AbstractMessage
439
+ def self.message_id
440
+ 14
441
+ end
442
+
443
+ def queue(server)
444
+ [self.class.message_id,
445
+ 1, # version
446
+ @data[:loglevel]
447
+ ]
448
+ end
449
+ end # SetServerLoglevel
450
+
451
+ # data = { :auto_bind => boolean }
452
+ class RequestAutoOpenOrders < AbstractMessage
453
+ def self.message_id
454
+ 15
455
+ end
456
+
457
+ def queue(server)
458
+ [self.class.message_id,
459
+ 1, # version
460
+ @data[:auto_bind]
461
+ ]
462
+ end
463
+ end # RequestAutoOpenOrders
464
+
465
+
466
+ class RequestAllOpenOrders < AbstractMessage
467
+ def self.message_id
468
+ 16
469
+ end
470
+
471
+ def queue(server)
472
+ [self.class.message_id,
473
+ 1 # version
474
+ ]
475
+ end
476
+ end # RequestAllOpenOrders
477
+
478
+ class RequestManagedAccounts < AbstractMessage
479
+ def self.message_id
480
+ 17
481
+ end
482
+
483
+ def queue(server)
484
+ [self.class.message_id,
485
+ 1 # version
486
+ ]
487
+ end
488
+ end # RequestManagedAccounts
489
+
490
+ # No idea what this is.
491
+ # data = { :fa_data_type => int }
492
+ class RequestFA < AbstractMessage
493
+ def self.message_id
494
+ 18
495
+ end
496
+
497
+ def queue(server)
498
+ requireVersion(server, 13)
499
+
500
+ [self.class.message_id,
501
+ 1, # version
502
+ @data[:fa_data_type]
503
+ ]
504
+ end
505
+ end # RequestFA
506
+
507
+ # No idea what this is.
508
+ # data = { :fa_data_type => int, :xml => string }
509
+ class ReplaceFA < AbstractMessage
510
+ def self.message_id
511
+ 19
512
+ end
513
+
514
+ def queue(server)
515
+ requireVersion(server, 13)
516
+
517
+ [self.class.message_id,
518
+ 1, # version
519
+ @data[:fa_data_type],
520
+ @data[:xml]
521
+ ]
522
+ end
523
+ end # ReplaceFA
524
+
525
+ # data = { :ticker_id => int,
526
+ # :contract => Contract,
527
+ # :end_date_time => string,
528
+ # :duration => string, # this specifies an integer number of seconds
529
+ # :bar_size => int,
530
+ # :what_to_show => symbol, # one of :trades, :midpoint, :bid, or :ask
531
+ # :use_RTH => int,
532
+ # :format_date => int
533
+ # }
534
+ #
535
+ # Note that as of 4/07 there is no historical data available for forex spot.
536
+ #
537
+ # data[:contract] may either be a Contract object or a String. A String should be
538
+ # in serialize_ib_ruby format; that is, it should be a colon-delimited string in
539
+ # the format:
540
+ #
541
+ # symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
542
+ #
543
+ # Fields not needed for a particular security should be left blank (e.g. strike
544
+ # and right are only relevant for options.)
545
+ #
546
+ # For example, to query the British pound futures contract trading on Globex expiring
547
+ # in September, 2008, the correct string is:
548
+ #
549
+ # GBP:FUT:200809:::62500:GLOBEX::USD:
550
+ #
551
+ # A Contract object will be automatically serialized into the required format.
552
+ #
553
+ # See also http://chuckcaplan.com/twsapi/index.php/void%20reqIntradayData%28%29
554
+ # for general information about how TWS handles historic data requests, whence
555
+ # the following has been adapted:
556
+ #
557
+ # The server providing historical prices appears to not always be
558
+ # available outside of market hours. If you call it outside of its
559
+ # supported time period, or if there is otherwise a problem with
560
+ # it, you will receive error #162 "Historical Market Data Service
561
+ # query failed.:HMDS query returned no data."
562
+ #
563
+ # The "endDateTime" parameter accepts a string in the form
564
+ # "yyyymmdd HH:mm:ss", with a time zone optionally allowed after a
565
+ # space at the end of the string; e.g. "20050701 18:26:44 GMT"
566
+ #
567
+ # The ticker id needs to be different than the reqMktData ticker
568
+ # id. If you use the same ticker ID you used for the symbol when
569
+ # you did ReqMktData, nothing comes back for the historical data call.
570
+ #
571
+ # Possible :bar_size values:
572
+ # 1 = 1 sec
573
+ # 2 = 5 sec
574
+ # 3 = 15 sec
575
+ # 4 = 30 sec
576
+ # 5 = 1 minute
577
+ # 6 = 2 minutes
578
+ # 7 = 5 minutes
579
+ # 8 = 15 minutes
580
+ # 9 = 30 minutes
581
+ # 10 = 1 hour
582
+ # 11 = 1 day
583
+ #
584
+ # Values less than 4 do not appear to work for certain securities.
585
+ #
586
+ # The nature of the data extracted is governed by sending a string
587
+ # having a value of "TRADES," "MIDPOINT," "BID," or "ASK." Here,
588
+ # we require a symbol argument of :trades, :midpoint, :bid, or
589
+ # :asked to be passed as data[:what_to_show].
590
+ #
591
+ # If data[:use_RTH] is set to 0, all data available during the time
592
+ # span requested is returned, even data bars covering time
593
+ # intervals where the market in question was illiquid. If useRTH
594
+ # has a non-zero value, only data within the "Regular Trading
595
+ # Hours" of the product in question is returned, even if the time
596
+ # span requested falls partially or completely outside of them.
597
+ #
598
+ # Using a :format_date of 1 will cause the dates in the returned
599
+ # messages with the historic data to be in a text format, like
600
+ # "20050307 11:32:16". If you set :format_date to 2 instead, you
601
+ # will get an offset in seconds from the beginning of 1970, which
602
+ # is the same format as the UNIX epoch time.
603
+ #
604
+ # For backfill on futures data, you may need to leave the Primary
605
+ # Exchange field of the Contract structure blank; see
606
+ # http://www.interactivebrokers.com/discus/messages/2/28477.html?1114646754
607
+ # [This message does not appear to exist anymore as of 4/07.]
608
+
609
+ ALLOWED_HISTORICAL_TYPES = [:trades, :midpoint, :bid, :ask]
610
+
611
+ class RequestHistoricalData < AbstractMessage
612
+ # Enumeration of bar size types for convenience. These are passed to TWS as the (one-based!) index into the array.
613
+ # Bar sizes less than 30 seconds do not work for some securities.
614
+ BarSizes = [
615
+ :invalid, # zero is not a valid barsize
616
+ :second,
617
+ :five_seconds,
618
+ :fifteen_seconds,
619
+ :thirty_seconds,
620
+ :minute,
621
+ :two_minutes,
622
+ :five_minutes,
623
+ :fifteen_minutes,
624
+ :thirty_minutes,
625
+ :hour,
626
+ :day,
627
+ ]
628
+
629
+
630
+ def self.message_id
631
+ 20
632
+ end
633
+
634
+ def queue(server)
635
+ requireVersion(server, 16)
636
+
637
+ if @data.has_key?(:what_to_show) && @data[:what_to_show].is_a?(String)
638
+ @data[:what_to_show].downcase!
639
+ @data[:what_to_show] = @data[:what_to_show].to_sym
640
+ end
641
+
642
+ raise ArgumentError("RequestHistoricalData: @data[:what_to_show] must be one of #{ALLOWED_HISTORICAL_TYPES.inspect}.") unless ALLOWED_HISTORICAL_TYPES.include?(@data[:what_to_show])
643
+
644
+ queue = [self.class.message_id,
645
+ 3, # version
646
+ @data[:ticker_id]
647
+ ]
648
+
649
+ contract = @data[:contract].is_a?(Datatypes::Contract) ? @data[:contract] : Datatypes::Contract.from_ib_ruby(@data[:contract])
650
+ queue.concat(contract.serialize_long(server[:version]))
651
+
652
+ queue.concat([
653
+ @data[:end_date_time],
654
+ @data[:bar_size]
655
+ ]) if server[:version] > 20
656
+
657
+
658
+ queue.concat([
659
+ @data[:duration],
660
+ @data[:use_RTH],
661
+ @data[:what_to_show].to_s.upcase
662
+ ])
663
+
664
+ queue.push(@data[:format_date]) if server[:version] > 16
665
+
666
+ if contract.sec_type.upcase == "BAG"
667
+ queue.concat(contract.serialize_combo_legs)
668
+ end
669
+
670
+ queue
671
+ end
672
+ end # RequestHistoricalData
673
+
674
+ # data = { :ticker_id => int,
675
+ # :contract => Contract,
676
+ # :exercise_action => int,
677
+ # :exercise_quantity => int,
678
+ # :account => string,
679
+ # :override => int } ## override? override what?
680
+ class ExerciseOptions < AbstractMessage
681
+ def self.message_id
682
+ 21
683
+ end
684
+
685
+ def queue(server)
686
+
687
+ requireVersion(server, 21)
688
+
689
+ q = [self.class.message_id,
690
+ 1, # version
691
+ @data[:ticker_id]
692
+ ]
693
+ q.concat(@data[:contract].serialize_long(server[:version]))
694
+ q.concat([
695
+ @data[:exercise_action],
696
+ @data[:exercise_quantity],
697
+ @data[:account],
698
+ @data[:override]
699
+ ])
700
+ q
701
+ end # queue
702
+ end # ExerciseOptions
703
+
704
+ # data = { :ticker_id => int,
705
+ # :scanner_subscription => ScannerSubscription
706
+ # }
707
+ class RequestScannerSubscription < AbstractMessage
708
+ def self.message_id
709
+ 22
710
+ end
711
+
712
+ def queue(server)
713
+ requireVersion(server, 24)
714
+
715
+ [
716
+ self.class.message_id,
717
+ 3, # version
718
+ @data[:ticker_id],
719
+ @data[:subscription].number_of_rows,
720
+ nilFilter(@data[:subscription].number_of_rows),
721
+ @data[:subscription].instrument,
722
+ @data[:subscription].location_code,
723
+ @data[:subscription].scan_code,
724
+ nilFilter(@data[:subscription].above_price),
725
+ nilFilter(@data[:subscription].below_price),
726
+ nilFilter(@data[:subscription].above_volume),
727
+ nilFilter(@data[:subscription].market_cap_above),
728
+ @data[:subscription].moody_rating_above,
729
+ @data[:subscription].moody_rating_below,
730
+ @data[:subscription].sp_rating_above,
731
+ @data[:subscription].sp_rating_below,
732
+ @data[:subscription].maturity_date_above,
733
+ @data[:subscription].maturity_date_below,
734
+ nilFilter(@data[:subscription].coupon_rate_above),
735
+ nilFilter(@data[:subscription].coupon_rate_below),
736
+ @data[:subscription].exclude_convertible,
737
+ (server[:version] >= 25 ? [@data[:subscription].average_option_volume_above,
738
+ @data[:subscription].scanner_setting_pairs] : []),
739
+
740
+ (server[:version] >= 27 ? [@data[:subscription].stock_type_filter] : []),
741
+ ].flatten
742
+
743
+ end
744
+ end # RequestScannerSubscription
745
+
746
+
747
+ # data = { :ticker_id => int }
748
+ class CancelScannerSubscription
749
+ def self.message_id
750
+ 23
751
+ end
752
+
753
+ def queue(server)
754
+ requireVersion(server, 24)
755
+ [self.class.message_id,
756
+ 1, # version
757
+ @data[:ticker_id]
758
+ ]
759
+ end
760
+ end # CancelScannerSubscription
761
+
762
+
763
+ class RequestScannerParameters
764
+ def self.message_id
765
+ 24
766
+ end
767
+
768
+ def queue(server)
769
+ requireVersion(server, 24)
770
+
771
+ [self.class.message_id,
772
+ 1 # version
773
+ ]
774
+ end
775
+ end # RequestScannerParameters
776
+
777
+
778
+ # data = { :ticker_id => int }
779
+ class CancelHistoricalData
780
+ def self.message_id
781
+ 25
782
+ end
783
+
784
+ def queue(server)
785
+ requireVersion(server, 24)
786
+ [self.class.message_id,
787
+ 1, # version
788
+ @data[:ticker_id]
789
+ ]
790
+ end
791
+ end # CancelHistoricalData
792
+
793
+ end # module OutgoingMessages
794
+
795
+ ################################################################
796
+ #### end outgoing messages
797
+ ################################################################
798
+
799
+
800
+ ################################################################
801
+ #### Incoming messages
802
+ ################################################################
803
+
804
+ module IncomingMessages
805
+ Classes = Array.new
806
+
807
+ #
808
+ # This is just a basic generic message from the server.
809
+ #
810
+ # Class variables:
811
+ # @@message_id - integer message id.
812
+ #
813
+ # Instance attributes:
814
+ # :data - Hash of actual data read from a stream.
815
+ #
816
+ # Override the load(socket) method in your subclass to do actual reading into @data.
817
+ #
818
+ class AbstractMessage < ExtremelyAbstractMessage
819
+ attr_accessor :data
820
+
821
+ def self.message_id
822
+ raise Exception("AbstractMessage.message_id called - you need to override this in a subclass.")
823
+ end
824
+
825
+
826
+ def initialize(socket, server_version)
827
+ raise Exception.new("Don't use AbstractMessage directly; use the subclass for your specific message type") if self.class.name == "AbstractMessage"
828
+ #logger.debug(" * loading #{self.class.name}")
829
+ @created_at = Time.now
830
+
831
+ @data = Hash.new
832
+ @socket = socket
833
+ @server_version = server_version
834
+
835
+ self.load()
836
+
837
+ @socket = nil
838
+
839
+ #logger.debug(" * New #{self.class.name}: #{ self.to_human }")
840
+ end
841
+
842
+ def AbstractMessage.inherited(by)
843
+ super(by)
844
+ Classes.push(by)
845
+ end
846
+
847
+ def load
848
+ raise Exception.new("Don't use AbstractMessage; override load() in a subclass.")
849
+ end
850
+
851
+ protected
852
+
853
+ #
854
+ # Load @data from the socket according to the given map.
855
+ #
856
+ # map is a series of Arrays in the format [ [ :name, :type ] ], e.g. autoload([:version, :int ], [:ticker_id, :int])
857
+ # type identifiers must have a corresponding read_type method on socket (read_int, etc.).
858
+ #
859
+ def autoload(*map)
860
+ ##logger.debug("autoloading map: " + map.inspect)
861
+ map.each { |spec|
862
+ @data[spec[0]] = @socket.__send__(("read_" + spec[1].to_s).to_sym)
863
+ }
864
+ end
865
+
866
+ # version_load loads map only if @data[:version] is >= required_version.
867
+ def version_load(required_version, *map)
868
+ if @data[:version] >= required_version
869
+ map.each { |item|
870
+ autoload(item)
871
+ }
872
+ end
873
+ end
874
+
875
+ end # class AbstractMessage
876
+
877
+
878
+ ### Actual message classes
879
+
880
+ # The IB code seems to dispatch up to two wrapped objects for this message, a tickPrice
881
+ # and sometimes a tickSize, which seems to be identical to the TICK_SIZE object.
882
+ #
883
+ # Important note from
884
+ # http://chuckcaplan.com/twsapi/index.php/void%20tickPrice%28%29 :
885
+ #
886
+ # "The low you get is NOT the low for the day as you'd expect it
887
+ # to be. It appears IB calculates the low based on all
888
+ # transactions after 4pm the previous day. The most inaccurate
889
+ # results occur when the stock moves up in the 4-6pm aftermarket
890
+ # on the previous day and then gaps open upward in the
891
+ # morning. The low you receive from TWS can be easily be several
892
+ # points different from the actual 9:30am-4pm low for the day in
893
+ # cases like this. If you require a correct traded low for the
894
+ # day, you can't get it from the TWS API. One possible source to
895
+ # help build the right data would be to compare against what Yahoo
896
+ # lists on finance.yahoo.com/q?s=ticker under the "Day's Range"
897
+ # statistics (be careful here, because Yahoo will use anti-Denial
898
+ # of Service techniques to hang your connection if you try to
899
+ # request too many bytes in a short period of time from them). For
900
+ # most purposes, a good enough approach would start by replacing
901
+ # the TWS low for the day with Yahoo's day low when you first
902
+ # start watching a stock ticker; let's call this time T. Then,
903
+ # update your internal low if the bid or ask tick you receive is
904
+ # lower than that for the remainder of the day. You should check
905
+ # against Yahoo again at time T+20min to handle the occasional
906
+ # case where the stock set a new low for the day in between
907
+ # T-20min (the real time your original quote was from, taking into
908
+ # account the delay) and time T. After that you should have a
909
+ # correct enough low for the rest of the day as long as you keep
910
+ # updating based on the bid/ask. It could still get slightly off
911
+ # in a case where a short transaction setting a new low appears in
912
+ # between ticks of data that TWS sends you. The high is probably
913
+ # distorted in the same way the low is, which would throw your
914
+ # results off if the stock traded after-hours and gapped down. It
915
+ # should be corrected in a similar way as described above if this
916
+ # is important to you."
917
+ #
918
+
919
+ class TickPrice < AbstractMessage
920
+ def self.message_id
921
+ 1
922
+ end
923
+
924
+ def load
925
+ autoload([:version, :int], [:ticker_id, :int], [:tick_type, :int], [:price, :decimal])
926
+
927
+ version_load(2, [:size, :int])
928
+ version_load(3, [:can_auto_execute, :int])
929
+
930
+ if @data[:version] >= 2
931
+ # the IB code translates these into 0, 3, and 5, respectively, and wraps them in a TICK_SIZE-type wrapper.
932
+ @data[:type] = case @data[:tick_type]
933
+ when 1
934
+ :bid
935
+ when 2
936
+ :ask
937
+ when 4
938
+ :last
939
+ when 6
940
+ :high
941
+ when 7
942
+ :low
943
+ when 9
944
+ :close
945
+ else
946
+ nil
947
+ end
948
+ end
949
+
950
+ end
951
+
952
+ # load
953
+
954
+ def inspect
955
+ "Tick (" + @data[:type].to_s('F') + " at " + @data[:price].to_s('F') + ") " + super.inspect
956
+ end
957
+
958
+ def to_human
959
+ @data[:size].to_s + " " + @data[:type].to_s + " at " + @data[:price].to_s('F')
960
+ end
961
+
962
+ end # TickPrice
963
+
964
+
965
+ class TickSize < AbstractMessage
966
+ def self.message_id
967
+ 2
968
+ end
969
+
970
+ def load
971
+ autoload([:version, :int], [:ticker_id, :int], [:tick_type, :int], [:size, :int])
972
+ @data[:type] = case @data[:tick_type]
973
+ when 0
974
+ :bid
975
+ when 3
976
+ :ask
977
+ when 5
978
+ :last
979
+ when 8
980
+ :volume
981
+ else
982
+ nil
983
+ end
984
+ end
985
+
986
+ def to_human
987
+ @data[:type].to_s + " size: " + @data[:size].to_s
988
+ end
989
+ end # TickSize
990
+
991
+
992
+ class OrderStatus < AbstractMessage
993
+ def self.message_id
994
+ 3
995
+ end
996
+
997
+ def load
998
+ autoload([:version, :int], [:id, :int], [:status, :string], [:filled, :int], [:remaining, :int],
999
+ [:average_fill_price, :decimal])
1000
+
1001
+ version_load(2, [:perm_id, :int])
1002
+ version_load(3, [:parent_id, :int])
1003
+ version_load(4, [:last_fill_price, :decimal])
1004
+ version_load(5, [:client_id, :int])
1005
+ end
1006
+ end
1007
+
1008
+
1009
+ class Error < AbstractMessage
1010
+ def self.message_id
1011
+ 4
1012
+ end
1013
+
1014
+ def code
1015
+ @data && @data[:code]
1016
+ end
1017
+
1018
+ def load
1019
+ @data[:version] = @socket.read_int
1020
+
1021
+ if @data[:version] < 2
1022
+ @data[:message] = @socket.read_string
1023
+ else
1024
+ autoload([:id, :int], [:code, :int], [:message, :string])
1025
+ end
1026
+ end
1027
+
1028
+ def to_human
1029
+ "TWS #{@data[:code]}: #{@data[:message]}"
1030
+ end
1031
+ end # class ErrorMessage
1032
+
1033
+ class OpenOrder < AbstractMessage
1034
+ attr_accessor :order, :contract
1035
+
1036
+ def self.message_id
1037
+ 5
1038
+ end
1039
+
1040
+ def load
1041
+ @order = Datatypes::Order.new
1042
+ @contract = Datatypes::Contract.new
1043
+
1044
+ autoload([:version, :int])
1045
+
1046
+ @order.id = @socket.read_int
1047
+
1048
+ @contract.symbol = @socket.read_string
1049
+ @contract.sec_type = @socket.read_string
1050
+ @contract.expiry = @socket.read_string
1051
+ @contract.strike = @socket.read_decimal
1052
+ @contract.right = @socket.read_string
1053
+ @contract.exchange = @socket.read_string
1054
+ @contract.currency = @socket.read_string
1055
+
1056
+ @contract.local_symbol = @socket.read_string if @data[:version] >= 2
1057
+
1058
+ @order.action = @socket.read_string
1059
+ @order.total_quantity = @socket.read_int
1060
+ @order.order_type = @socket.read_string
1061
+ @order.limit_price = @socket.read_decimal
1062
+ @order.aux_price = @socket.read_decimal
1063
+ @order.tif = @socket.read_string
1064
+ @order.oca_group = @socket.read_string
1065
+ @order.account = @socket.read_string
1066
+ @order.open_close = @socket.read_string
1067
+ @order.origin = @socket.read_int
1068
+ @order.order_ref = @socket.read_string
1069
+
1070
+ @order.client_id = @socket.read_int if @data[:version] >= 3
1071
+
1072
+ if @data[:version] >= 4
1073
+ @order.perm_id = @socket.read_int
1074
+ @order.outside_rth = (@socket.read_int == 1)
1075
+ @order.hidden = (@socket.read_int == 1)
1076
+ @order.discretionary_amount = @socket.read_decimal
1077
+ end
1078
+
1079
+ @order.good_after_time = @socket.read_string if @data[:version] >= 5
1080
+ @order.shares_allocation = @socket.read_string if @data[:version] >= 6
1081
+
1082
+ if @data[:version] >= 7
1083
+ @order.fa_group = @socket.read_string
1084
+ @order.fa_method = @socket.read_string
1085
+ @order.fa_percentage = @socket.read_string
1086
+ @order.fa_profile = @socket.read_string
1087
+ end
1088
+
1089
+ @order.good_till_date = @socket.read_string if @data[:version] >= 8
1090
+
1091
+ if @data[:version] >= 9
1092
+ @order.rule_80A = @socket.read_string
1093
+ @order.percent_offset = @socket.read_decimal
1094
+ @order.settling_firm = @socket.read_string
1095
+ @order.short_sale_slot = @socket.read_int
1096
+ @order.designated_location = @socket.read_string
1097
+ @order.auction_strategy = @socket.read_int
1098
+ @order.starting_price = @socket.read_decimal
1099
+ @order.stock_ref_price = @socket.read_decimal
1100
+ @order.delta = @socket.read_decimal
1101
+ @order.stock_range_lower = @socket.read_decimal
1102
+ @order.stock_range_upper = @socket.read_decimal
1103
+ @order.display_size = @socket.read_int
1104
+ @order.rth_only = @socket.read_boolean
1105
+ @order.block_order = @socket.read_boolean
1106
+ @order.sweep_to_fill = @socket.read_boolean
1107
+ @order.all_or_none = @socket.read_boolean
1108
+ @order.min_quantity = @socket.read_int
1109
+ @order.oca_type = @socket.read_int
1110
+ @order.eTrade_only = @socket.read_boolean
1111
+ @order.firm_quote_only = @socket.read_boolean
1112
+ @order.nbbo_price_cap = @socket.read_decimal
1113
+ end
1114
+
1115
+ if @data[:version] >= 10
1116
+ @order.parent_id = @socket.read_int
1117
+ @order.trigger_method = @socket.read_int
1118
+ end
1119
+
1120
+ if @data[:version] >= 11
1121
+ @order.volatility = @socket.read_decimal
1122
+ @order.volatility_type = @socket.read_int
1123
+
1124
+ if @data[:version] == 11
1125
+ @order.delta_neutral_order_type = (@socket.read_int == 0 ? "NONE" : "MKT")
1126
+ else
1127
+ @order.delta_neutral_order_type = @socket.read_string
1128
+ @order.delta_neutral_aux_price = @socket.read_decimal
1129
+ end
1130
+
1131
+ @order.continuous_update = @socket.read_int
1132
+ if @server_version == 26
1133
+ @order.stock_range_lower = @socket.read_decimal
1134
+ @order.stock_range_upper = @socket.read_decimal
1135
+ end
1136
+
1137
+ @order.reference_price_type = @socket.read_int
1138
+ end # if version >= 11
1139
+
1140
+
1141
+ end # load
1142
+ end # OpenOrder
1143
+
1144
+ class AccountValue < AbstractMessage
1145
+ def self.message_id
1146
+ 6
1147
+ end
1148
+
1149
+ def load
1150
+ autoload([:version, :int], [:key, :string], [:value, :string], [:currency, :string])
1151
+ version_load(2, [:account_name, :string])
1152
+ end
1153
+
1154
+ def to_human
1155
+ "<AccountValue: acct ##{@data[:account_name]}; #{@data[:key]}=#{@data[:value]} (#{@data[:currency]})>"
1156
+ end
1157
+ end # AccountValue
1158
+
1159
+ class PortfolioValue < AbstractMessage
1160
+ attr_accessor :contract
1161
+
1162
+ def self.message_id
1163
+ 7
1164
+ end
1165
+
1166
+ def load
1167
+ @contract = Datatypes::Contract.new
1168
+
1169
+ autoload([:version, :int])
1170
+ @contract.symbol = @socket.read_string
1171
+ @contract.sec_type = @socket.read_string
1172
+ @contract.expiry = @socket.read_string
1173
+ @contract.strike = @socket.read_decimal
1174
+ @contract.right = @socket.read_string
1175
+ @contract.currency = @socket.read_string
1176
+ @contract.local_symbol = @socket.read_string if @data[:version] >= 2
1177
+
1178
+ autoload([:position, :int], [:market_price, :decimal], [:market_value, :decimal])
1179
+ version_load(3, [:average_cost, :decimal], [:unrealized_pnl, :decimal], [:realized_pnl, :decimal])
1180
+ version_load(4, [:account_name, :string])
1181
+ end
1182
+
1183
+ def to_human
1184
+ "<PortfolioValue: update for #{@contract.to_human}: market price #{@data[:market_price].to_s('F')}; market value " +
1185
+ "#{@data[:market_value].to_s('F')}; position #{@data[:position]}; unrealized PnL #{@data[:unrealized_pnl].to_s('F')}; " +
1186
+ "realized PnL #{@data[:realized_pnl].to_s('F')}; account #{@data[:account_name]}>"
1187
+ end
1188
+
1189
+ end # PortfolioValue
1190
+
1191
+ class AccountUpdateTime < AbstractMessage
1192
+ def self.message_id
1193
+ 8
1194
+ end
1195
+
1196
+ def load
1197
+ autoload([:version, :int], [:time_stamp, :string])
1198
+ end
1199
+ end # AccountUpdateTime
1200
+
1201
+
1202
+ #
1203
+ # This message is always sent by TWS automatically at connect.
1204
+ # The IB class subscribes to it automatically and stores the order id in
1205
+ # its :next_order_id attribute.
1206
+ #
1207
+ class NextValidID < AbstractMessage
1208
+ def self.message_id
1209
+ 9
1210
+ end
1211
+
1212
+ def load
1213
+ autoload([:version, :int], [:order_id, :int])
1214
+ end
1215
+
1216
+ end # NextValidIDMessage
1217
+
1218
+
1219
+ class ContractData < AbstractMessage
1220
+ attr_accessor :contract_details
1221
+
1222
+ def self.message_id
1223
+ 10
1224
+ end
1225
+
1226
+ def load
1227
+ @contract_details = Datatypes::ContractDetails.new
1228
+
1229
+ autoload([:version, :int])
1230
+
1231
+ @contract_details.summary.symbol = @socket.read_string
1232
+ @contract_details.summary.sec_type = @socket.read_string
1233
+ @contract_details.summary.expiry = @socket.read_string
1234
+ @contract_details.summary.strike = @socket.read_decimal
1235
+ @contract_details.summary.right = @socket.read_string
1236
+ @contract_details.summary.exchange = @socket.read_string
1237
+ @contract_details.summary.currency = @socket.read_string
1238
+ @contract_details.summary.local_symbol = @socket.read_string
1239
+
1240
+ @contract_details.market_name = @socket.read_string
1241
+ @contract_details.trading_class = @socket.read_string
1242
+ @contract_details.con_id = @socket.read_int
1243
+ @contract_details.min_tick = @socket.read_decimal
1244
+ @contract_details.multiplier = @socket.read_string
1245
+ @contract_details.order_types = @socket.read_string
1246
+ @contract_details.valid_exchanges = @socket.read_string
1247
+ @contract_details.price_magnifier = @socket.read_int if @data[:version] >= 2
1248
+
1249
+ end
1250
+ end # ContractData
1251
+
1252
+
1253
+ class ExecutionData < AbstractMessage
1254
+ attr_accessor :contract, :execution
1255
+
1256
+ def self.message_id
1257
+ 11
1258
+ end
1259
+
1260
+ def load
1261
+ @contract = Datatypes::Contract.new
1262
+ @execution = Datatypes::Execution.new
1263
+
1264
+ autoload([:version, :int], [:order_id, :int])
1265
+
1266
+ @contract.symbol = @socket.read_string
1267
+ @contract.sec_type = @socket.read_string
1268
+ @contract.expiry = @socket.read_string
1269
+ @contract.strike = @socket.read_decimal
1270
+ @contract.right = @socket.read_string
1271
+ @contract.currency = @socket.read_string
1272
+ @contract.local_symbol = @socket.read_string if @data[:version] >= 2
1273
+
1274
+ @execution.order_id = @data[:order_id]
1275
+ @execution.exec_id = @socket.read_string
1276
+ @execution.time = @socket.read_string
1277
+ @execution.account_number = @socket.read_string
1278
+ @execution.exchange = @socket.read_string
1279
+ @execution.side = @socket.read_string
1280
+ @execution.shares = @socket.read_int
1281
+ @execution.price = @socket.read_decimal
1282
+
1283
+ @execution.perm_id = @socket.read_int if @data[:version] >= 2
1284
+ @execution.client_id = @socket.read_int if @data[:version] >= 3
1285
+ @execution.liquidation = @socket.read_int if @data[:version] >= 4
1286
+ end
1287
+ end # ExecutionData
1288
+
1289
+ class MarketDepth < AbstractMessage
1290
+ def self.message_id
1291
+ 12
1292
+ end
1293
+
1294
+ def load
1295
+ autoload([:version, :int], [:id, :int], [:position, :int], [:operation, :int], [:side, :int], [:price, :decimal], [:size, :int])
1296
+ end
1297
+ end # MarketDepth
1298
+
1299
+ class MarketDepthL2 < AbstractMessage
1300
+ def self.message_id
1301
+ 13
1302
+ end
1303
+
1304
+ def load
1305
+ autoload([:version, :int], [:id, :int], [:position, :int], [:market_maker, :string], [:operation, :int], [:side, :int],
1306
+ [:price, :decimal], [:size, :int])
1307
+ end
1308
+ end # MarketDepthL2
1309
+
1310
+
1311
+ class NewsBulletins < AbstractMessage
1312
+ def self.message_id
1313
+ 14
1314
+ end
1315
+
1316
+ def load
1317
+ autoload([:version, :int], [:news_message_id, :int], [:news_message_type, :int], [:news_message, :string], [:originating_exchange, :string])
1318
+ end
1319
+ end # NewsBulletins
1320
+
1321
+ class ManagedAccounts < AbstractMessage
1322
+ def self.message_id
1323
+ 15
1324
+ end
1325
+
1326
+ def load
1327
+ autoload([:version, :int], [:accounts_list, :string])
1328
+ end
1329
+ end # ManagedAccounts
1330
+
1331
+ # "Fa"?
1332
+ class ReceiveFa < AbstractMessage
1333
+ def self.message_id
1334
+ 16
1335
+ end
1336
+
1337
+ def load
1338
+ autoload([:version, :int], [:fa_data_type, :int], [:xml, :string])
1339
+ end
1340
+ end # ReceiveFa
1341
+
1342
+ class HistoricalData < AbstractMessage
1343
+ def self.message_id
1344
+ 17
1345
+ end
1346
+
1347
+ def load
1348
+ autoload([:version, :int], [:req_id, :int])
1349
+ version_load(2, [:start_date_str, :string], [:end_date_str, :string])
1350
+ @data[:completed_indicator] = "finished-" + @data[:start_date_str] + "-" + @data[:end_date_str] if @data[:version] >= 2
1351
+
1352
+ autoload([:item_count, :int])
1353
+ @data[:history] = Array.new(@data[:item_count]) { |index|
1354
+ attrs = {
1355
+ :date => @socket.read_string,
1356
+ :open => @socket.read_decimal,
1357
+ :high => @socket.read_decimal,
1358
+ :low => @socket.read_decimal,
1359
+ :close => @socket.read_decimal,
1360
+ :volume => @socket.read_int,
1361
+ :wap => @socket.read_decimal,
1362
+ :has_gaps => @socket.read_string
1363
+ }
1364
+
1365
+ Datatypes::Bar.new(attrs)
1366
+ }
1367
+
1368
+ end
1369
+
1370
+ def to_human
1371
+ "<HistoricalData: req id #{@data[:req_id]}, #{@data[:item_count]} items, from #{@data[:start_date_str]} to #{@data[:end_date_str]}>"
1372
+ end
1373
+ end # HistoricalData
1374
+
1375
+ class BondContractData < AbstractMessage
1376
+ attr_accessor :contract_details
1377
+
1378
+ def self.message_id
1379
+ 18
1380
+ end
1381
+
1382
+ def load
1383
+ @contract_details = Datatypes::ContractDetails.new
1384
+ @contract_details.summary.symbol = @socket.read_string
1385
+ @contract_details.summary.sec_type = @socket.read_string
1386
+ @contract_details.summary.cusip = @socket.read_string
1387
+ @contract_details.summary.coupon = @socket.read_decimal
1388
+ @contract_details.summary.maturity = @socket.read_string
1389
+ @contract_details.summary.issue_date = @socket.read_string
1390
+ @contract_details.summary.ratings = @socket.read_string
1391
+ @contract_details.summary.bond_type = @socket.read_string
1392
+ @contract_details.summary.coupon_type = @socket.read_string
1393
+ @contract_details.summary.convertible = @socket.read_boolean
1394
+ @contract_details.summary.callable = @socket.read_boolean
1395
+ @contract_details.summary.puttable = @socket.read_boolean
1396
+ @contract_details.summary.desc_append = @socket.read_string
1397
+ @contract_details.summary.exchange = @socket.read_string
1398
+ @contract_details.summary.currency = @socket.read_string
1399
+ @contract_details.market_name = @socket.read_string
1400
+ @contract_details.trading_class = @socket.read_string
1401
+ @contract_details.con_id = @socket.read_int
1402
+ @contract_details.min_tick = @socket.read_decimal
1403
+ @contract_details.order_types = @socket.read_string
1404
+ @contract_details.valid_exchanges = @socket.read_string
1405
+
1406
+ end
1407
+ end # BondContractData
1408
+
1409
+ class ScannerParameters < AbstractMessage
1410
+ def self.message_id
1411
+ 19
1412
+ end
1413
+
1414
+ def load
1415
+ autoload([:version, :int], [:xml, :string])
1416
+ end
1417
+ end # ScannerParamters
1418
+
1419
+
1420
+ class ScannerData < AbstractMessage
1421
+ attr_accessor :contract_details
1422
+
1423
+ def self.message_id
1424
+ 20
1425
+ end
1426
+
1427
+ def load
1428
+ autoload([:version, :int], [:ticker_id, :int], [:number_of_elements, :int])
1429
+ @data[:results] = Array.new(@data[:number_of_elements]) { |index|
1430
+ {
1431
+ :rank => @socket.read_int
1432
+ ## TODO: Pick up here.
1433
+ }
1434
+ }
1435
+
1436
+ end
1437
+ end # ScannerData
1438
+
1439
+ ###########################################
1440
+ ###########################################
1441
+ ## End message classes
1442
+ ###########################################
1443
+ ###########################################
1444
+
1445
+ Table = Hash.new
1446
+ Classes.each { |msg_class|
1447
+ Table[msg_class.message_id] = msg_class
1448
+ }
1449
+
1450
+ #logger.debug("Incoming message class table is #{Table.inspect}")
1451
+
1452
+ end # module IncomingMessages
1453
+ ################################################################
1454
+ #### End incoming messages
1455
+ ################################################################
1456
+
1457
+
1458
+ end # module IB