ib-ruby 0.4.20 → 0.4.22

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1458 +0,0 @@
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