ib-ruby 0.4.3 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
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