ib-api 972.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +16 -0
  7. data/Gemfile.lock +105 -0
  8. data/Guardfile +24 -0
  9. data/LICENSE +674 -0
  10. data/README.md +65 -0
  11. data/Rakefile +11 -0
  12. data/VERSION +1 -0
  13. data/api.gemspec +43 -0
  14. data/bin/console +95 -0
  15. data/bin/console.yml +3 -0
  16. data/bin/setup +8 -0
  17. data/changelog.md +7 -0
  18. data/example/README.md +76 -0
  19. data/example/account_info +54 -0
  20. data/example/account_positions +30 -0
  21. data/example/account_summary +88 -0
  22. data/example/cancel_orders +74 -0
  23. data/example/fa_accounts +25 -0
  24. data/example/fundamental_data +40 -0
  25. data/example/historic_data_cli +186 -0
  26. data/example/list_orders +45 -0
  27. data/example/portfolio_csv +81 -0
  28. data/example/scanner_data +62 -0
  29. data/example/template +19 -0
  30. data/example/tick_data +28 -0
  31. data/lib/extensions/class-extensions.rb +87 -0
  32. data/lib/ib-api.rb +7 -0
  33. data/lib/ib/base.rb +103 -0
  34. data/lib/ib/base_properties.rb +160 -0
  35. data/lib/ib/connection.rb +450 -0
  36. data/lib/ib/constants.rb +393 -0
  37. data/lib/ib/errors.rb +44 -0
  38. data/lib/ib/logger.rb +26 -0
  39. data/lib/ib/messages.rb +99 -0
  40. data/lib/ib/messages/abstract_message.rb +101 -0
  41. data/lib/ib/messages/incoming.rb +251 -0
  42. data/lib/ib/messages/incoming/abstract_message.rb +116 -0
  43. data/lib/ib/messages/incoming/account_value.rb +78 -0
  44. data/lib/ib/messages/incoming/alert.rb +34 -0
  45. data/lib/ib/messages/incoming/contract_data.rb +102 -0
  46. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  47. data/lib/ib/messages/incoming/execution_data.rb +50 -0
  48. data/lib/ib/messages/incoming/historical_data.rb +84 -0
  49. data/lib/ib/messages/incoming/market_depths.rb +44 -0
  50. data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
  51. data/lib/ib/messages/incoming/open_order.rb +277 -0
  52. data/lib/ib/messages/incoming/order_status.rb +85 -0
  53. data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
  54. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  55. data/lib/ib/messages/incoming/scanner_data.rb +54 -0
  56. data/lib/ib/messages/incoming/ticks.rb +268 -0
  57. data/lib/ib/messages/outgoing.rb +437 -0
  58. data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
  59. data/lib/ib/messages/outgoing/account_requests.rb +112 -0
  60. data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
  61. data/lib/ib/messages/outgoing/place_order.rb +209 -0
  62. data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
  63. data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
  64. data/lib/ib/model.rb +4 -0
  65. data/lib/ib/models.rb +14 -0
  66. data/lib/ib/server_versions.rb +114 -0
  67. data/lib/ib/socket.rb +185 -0
  68. data/lib/ib/support.rb +160 -0
  69. data/lib/ib/version.rb +6 -0
  70. data/lib/models/ib/account.rb +85 -0
  71. data/lib/models/ib/account_value.rb +33 -0
  72. data/lib/models/ib/bag.rb +55 -0
  73. data/lib/models/ib/bar.rb +31 -0
  74. data/lib/models/ib/combo_leg.rb +105 -0
  75. data/lib/models/ib/condition.rb +245 -0
  76. data/lib/models/ib/contract.rb +415 -0
  77. data/lib/models/ib/contract_detail.rb +108 -0
  78. data/lib/models/ib/execution.rb +67 -0
  79. data/lib/models/ib/forex.rb +13 -0
  80. data/lib/models/ib/future.rb +15 -0
  81. data/lib/models/ib/index.rb +15 -0
  82. data/lib/models/ib/option.rb +78 -0
  83. data/lib/models/ib/option_detail.rb +55 -0
  84. data/lib/models/ib/order.rb +519 -0
  85. data/lib/models/ib/order_state.rb +152 -0
  86. data/lib/models/ib/portfolio_value.rb +64 -0
  87. data/lib/models/ib/stock.rb +16 -0
  88. data/lib/models/ib/underlying.rb +34 -0
  89. data/lib/models/ib/vertical.rb +96 -0
  90. data/lib/requires.rb +12 -0
  91. metadata +203 -0
@@ -0,0 +1,450 @@
1
+ require 'thread'
2
+ #require 'active_support'
3
+ require 'ib/socket'
4
+ require 'ib/logger'
5
+ require 'ib/messages'
6
+
7
+ module IB
8
+ # Encapsulates API connection to TWS or Gateway
9
+ class Connection
10
+
11
+
12
+ ## -------------------------------------------- Interface ---------------------------------
13
+ ## public attributes: socket, next_local_id ( alias next_order_id)
14
+ ## public methods: connect (alias open), disconnect, connected?
15
+ ## subscribe, unsubscribe
16
+ ## send_message (alias dispatch)
17
+ ## place_order, modify_order, cancel_order
18
+ ## public data-queue: received, received?, wait_for, clear_received
19
+ ## misc: reader_running?
20
+
21
+ include LogDev # provides default_logger
22
+
23
+ mattr_accessor :current
24
+ mattr_accessor :logger ## borrowed from active_support
25
+ # Please note, we are realizing only the most current TWS protocol versions,
26
+ # thus improving performance at the expense of backwards compatibility.
27
+ # Older protocol versions support can be found in older gem versions.
28
+
29
+ attr_accessor :socket # Socket to IB server (TWS or Gateway)
30
+ attr_accessor :next_local_id # Next valid order id
31
+ attr_accessor :client_id
32
+ attr_accessor :server_version
33
+ attr_accessor :client_version
34
+ alias next_order_id next_local_id
35
+ alias next_order_id= next_local_id=
36
+
37
+ def initialize host: '127.0.0.1',
38
+ port: '4002', # IB Gateway connection (default --> demo) 4001: production
39
+ #:port => '7497', # TWS connection --> demo 7496: production
40
+ connect: true, # Connect at initialization
41
+ received: true, # Keep all received messages in a @received Hash
42
+ # redis: false, # future plans
43
+ logger: default_logger,
44
+ client_id: rand( 1001 .. 9999 ) ,
45
+ client_version: IB::Messages::CLIENT_VERSION, # lib/ib/server_versions.rb
46
+ optional_capacities: "", # TWS-Version 974: "+PACEAPI"
47
+ #server_version: IB::Messages::SERVER_VERSION, # lib/messages.rb
48
+ **any_other_parameters_which_are_ignored
49
+ # V 974 release motes
50
+ # API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
51
+
52
+
53
+ # convert parameters into instance-variables and assign them
54
+ method(__method__).parameters.each do |type, k|
55
+ next unless type == :key
56
+ case k
57
+ when :logger
58
+ self.logger = logger
59
+ else
60
+ v = eval(k.to_s)
61
+ instance_variable_set("@#{k}", v) unless v.nil?
62
+ end
63
+ end
64
+
65
+ # A couple of locks to avoid race conditions in JRuby
66
+ @subscribe_lock = Mutex.new
67
+ @receive_lock = Mutex.new
68
+ @message_lock = Mutex.new
69
+
70
+ @connected = false
71
+ self.next_local_id = nil
72
+
73
+ # self.subscribe(:Alert) do |msg|
74
+ # puts msg.to_human
75
+ # end
76
+
77
+ # TWS always sends NextValidId message at connect -subscribe save this id
78
+ ## this block is executed before tws-communication is established
79
+ yield self if block_given?
80
+
81
+ self.subscribe(:NextValidId) do |msg|
82
+ logger.progname = "Connection#connect"
83
+ self.next_local_id = msg.local_id
84
+ logger.info { "Got next valid order id: #{next_local_id}." }
85
+ end
86
+
87
+ # Ensure the transmission of NextValidId.
88
+ # works even if no reader_thread is established
89
+ if connect
90
+ disconnect if connected?
91
+ update_next_order_id
92
+ Kernel.exit if self.next_local_id.nil?
93
+ end
94
+ #start_reader if @received && connected?
95
+ Connection.current = self
96
+ end
97
+
98
+ # read actual order_id and
99
+ # connect if not connected
100
+ def update_next_order_id
101
+ i,finish = 0, false
102
+ sub = self.subscribe(:NextValidID) { finish = true }
103
+ connected? ? self.send_message( :RequestIds ) : open()
104
+ Timeout::timeout(1, IB::TransmissionError,"Could not get NextValidId" ) do
105
+ loop { sleep 0.1; break if finish }
106
+ end
107
+ self.unsubscribe sub
108
+ end
109
+
110
+ ### Working with connection
111
+
112
+ def connect
113
+ logger.progname='IB::Connection#connect'
114
+ if connected?
115
+ error "Already connected!"
116
+ return
117
+ end
118
+
119
+ self.socket = IBSocket.open(@host, @port) # raises Errno::ECONNREFUSED if no connection is possible
120
+ socket.initialising_handshake
121
+ socket.decode_message( socket.recieve_messages ) do | the_message |
122
+ # logger.info{ "TheMessage :: #{the_message.inspect}" }
123
+ @server_version = the_message.shift.to_i
124
+ error "ServerVersion does not match #{@server_version} <--> #{MAX_CLIENT_VER}" if @server_version != MAX_CLIENT_VER
125
+
126
+ @remote_connect_time = DateTime.parse the_message.shift
127
+ @local_connect_time = Time.now
128
+ end
129
+
130
+ # Sending (arbitrary) client ID to identify subsequent communications.
131
+ # The client with a client_id of 0 can manage the TWS-owned open orders.
132
+ # Other clients can only manage their own open orders.
133
+
134
+ # V100 initial handshake
135
+ # Parameters borrowed from the python client
136
+ start_api = 71
137
+ version = 2
138
+ # optcap = @optional_capacities.empty? ? "" : " "+ @optional_capacities
139
+ socket.send_messages start_api, version, @client_id , @optional_capacities
140
+ @connected = true
141
+ logger.info { "Connected to server, version: #{@server_version},\n connection time: " +
142
+ "#{@local_connect_time} local, " +
143
+ "#{@remote_connect_time} remote."}
144
+
145
+ # if the client_id is wrong or the port is not accessible the first read attempt fails
146
+ # get the first message and proceed if something reasonable is recieved
147
+ the_message = process_message # recieve next_order_id
148
+ error "Check Port/Client_id ", :reader if the_message == " "
149
+ start_reader
150
+ end
151
+
152
+ alias open connect # Legacy alias
153
+
154
+ def disconnect
155
+ if reader_running?
156
+ @reader_running = false
157
+ @reader_thread.join
158
+ end
159
+ if connected?
160
+ socket.close
161
+ @connected = false
162
+ end
163
+ end
164
+
165
+ alias close disconnect # Legacy alias
166
+
167
+ def connected?
168
+ @connected
169
+ end
170
+
171
+ ### Working with message subscribers
172
+
173
+ # Subscribe Proc or block to specific type(s) of incoming message events.
174
+ # Listener will be called later with received message instance as its argument.
175
+ # Returns subscriber id to allow unsubscribing
176
+ def subscribe *args, &block
177
+ @subscribe_lock.synchronize do
178
+ subscriber = args.last.respond_to?(:call) ? args.pop : block
179
+ id = random_id
180
+
181
+ error "Need subscriber proc or block ", :args unless subscriber.is_a? Proc
182
+
183
+ args.each do |what|
184
+ message_classes =
185
+ case
186
+ when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
187
+ [what]
188
+ when what.is_a?(Symbol)
189
+ [Messages::Incoming.const_get(what)]
190
+ when what.is_a?(Regexp)
191
+ Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
192
+ else
193
+ error "#{what} must represent incoming IB message class", :args
194
+ end
195
+ # @subscribers_lock.synchronize do
196
+ message_classes.flatten.each do |message_class|
197
+ # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
198
+ subscribers[message_class][id] = subscriber
199
+ end
200
+ # end # lock
201
+ end
202
+
203
+ id
204
+ end
205
+ end
206
+
207
+ # Remove all subscribers with specific subscriber id
208
+ def unsubscribe *ids
209
+ @subscribe_lock.synchronize do
210
+ ids.collect do |id|
211
+ removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
212
+ logger.error "No subscribers with id #{id}" if removed_at_id.empty?
213
+ removed_at_id # return_value
214
+ end.flatten
215
+ end
216
+ end
217
+ ### Working with received messages Hash
218
+
219
+ # Clear received messages Hash
220
+ def clear_received *message_types
221
+ @receive_lock.synchronize do
222
+ if message_types.empty?
223
+ received.each { |message_type, container| container.clear }
224
+ else
225
+ message_types.each { |message_type| received[message_type].clear }
226
+ end
227
+ end
228
+ end
229
+
230
+ # Hash of received messages, keyed by message type
231
+ def received
232
+ @received_hash ||= Hash.new do |hash, message_type|
233
+ # enable access to the hash via
234
+ # ib.received[:MessageType].attribute
235
+ the_array = Array.new
236
+ def the_array.method_missing(method, *key)
237
+ unless method == :to_hash || method == :to_str #|| method == :to_int
238
+ return self.map{|x| x.public_send(method, *key)}
239
+ end
240
+ end
241
+ hash[message_type] = the_array
242
+ end
243
+ end
244
+
245
+ # Check if messages of given type were received at_least n times
246
+ def received? message_type, times=1
247
+ @receive_lock.synchronize do
248
+ received[message_type].size >= times
249
+ end
250
+ end
251
+
252
+
253
+ # Wait for specific condition(s) - given as callable/block, or
254
+ # message type(s) - given as Symbol or [Symbol, times] pair.
255
+ # Timeout after given time or 1 second.
256
+ #
257
+ # wait_for depends heavyly on Connection#received. If collection of messages through recieved
258
+ # is turned off, wait_for loses most of its functionality
259
+ def wait_for *args, &block
260
+ timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
261
+ end_time = Time.now + (timeout || 1) # default timeout 1 sec
262
+ conditions = args.delete_if { |arg| arg.is_a? Numeric }.push(block).compact
263
+
264
+ until end_time < Time.now || satisfied?(*conditions)
265
+ if reader_running?
266
+ sleep 0.05
267
+ else
268
+ process_messages 50
269
+ end
270
+ end
271
+ end
272
+
273
+ ### Working with Incoming messages from IB
274
+
275
+
276
+ def reader_running?
277
+ @reader_running && @reader_thread && @reader_thread.alive?
278
+ end
279
+
280
+ # Process incoming messages during *poll_time* (200) msecs, nonblocking
281
+ def process_messages poll_time = 50 # in msec
282
+ time_out = Time.now + poll_time/1000.0
283
+ while (time_left = time_out - Time.now) > 0
284
+ # If socket is readable, process single incoming message
285
+ #process_message if select [socket], nil, nil, time_left
286
+ # the following checks for shutdown of TWS side; ensures we don't run in a spin loop.
287
+ # unfortunately, it raises Errors in windows environment
288
+ # disabled for now
289
+ if select [socket], nil, nil, time_left
290
+ # # Peek at the message from the socket; if it's blank then the
291
+ # # server side of connection (TWS) has likely shut down.
292
+ socket_likely_shutdown = socket.recvmsg(100, Socket::MSG_PEEK)[0] == ""
293
+ #
294
+ # # We go ahead process messages regardless (a no-op if socket_likely_shutdown).
295
+ process_message
296
+ #
297
+ # # After processing, if socket has shut down we sleep for 100ms
298
+ # # to avoid spinning in a tight loop. If the server side somehow
299
+ # # comes back up (gets reconnedted), normal processing
300
+ # # (without the 100ms wait) should happen.
301
+ sleep(0.1) if socket_likely_shutdown
302
+ end
303
+ end
304
+ end
305
+
306
+ ### Sending Outgoing messages to IB
307
+
308
+ # Send an outgoing message.
309
+ # returns the used request_id if appropiate, otherwise "true"
310
+ def send_message what, *args
311
+ message =
312
+ case
313
+ when what.is_a?(Messages::Outgoing::AbstractMessage)
314
+ what
315
+ when what.is_a?(Class) && what < Messages::Outgoing::AbstractMessage
316
+ what.new *args
317
+ when what.is_a?(Symbol)
318
+ Messages::Outgoing.const_get(what).new *args
319
+ else
320
+ error "Only able to send outgoing IB messages", :args
321
+ end
322
+ error "Not able to send messages, IB not connected!" unless connected?
323
+ begin
324
+ @message_lock.synchronize do
325
+ message.send_to socket
326
+ end
327
+ rescue Errno::EPIPE
328
+ logger.error{ "Broken Pipe, trying to reconnect" }
329
+ disconnect
330
+ connect
331
+ retry
332
+ end
333
+ ## return the transmitted message
334
+ message.data[:request_id].presence || true
335
+ end
336
+
337
+ alias dispatch send_message # Legacy alias
338
+
339
+ # Place Order (convenience wrapper for send_message :PlaceOrder).
340
+ # Assigns client_id and order_id fields to placed order. Returns assigned order_id.
341
+ def place_order order, contract
342
+ # order.place contract, self ## old
343
+ error "Unable to place order, next_local_id not known" unless next_local_id
344
+ error "local_id present. Order is already placed. Do might use modify insteed" unless order.local_id.nil?
345
+ order.client_id = client_id
346
+ order.local_id = next_local_id
347
+ self.next_local_id += 1
348
+ order.placed_at = Time.now
349
+ modify_order order, contract
350
+ end
351
+
352
+ # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id.
353
+ def modify_order order, contract
354
+ # order.modify contract, self ## old
355
+ error "Unable to modify order; local_id not specified" if order.local_id.nil?
356
+ order.modified_at = Time.now
357
+ send_message :PlaceOrder,
358
+ :order => order,
359
+ :contract => contract,
360
+ :local_id => order.local_id
361
+ order.local_id # return value
362
+ end
363
+
364
+ # Cancel Orders by their local ids (convenience wrapper for send_message :CancelOrder).
365
+ def cancel_order *local_ids
366
+ local_ids.each do |local_id|
367
+ send_message :CancelOrder, :local_id => local_id.to_i
368
+ end
369
+ end
370
+
371
+ # Start reader thread that continuously reads messages from @socket in background.
372
+ # If you don't start reader, you should manually poll @socket for messages
373
+ # or use #process_messages(msec) API.
374
+ def start_reader
375
+ return(@reader_thread) if @reader_running
376
+ if connected?
377
+ Thread.abort_on_exception = true
378
+ @reader_running = true
379
+ @reader_thread = Thread.new { process_messages while @reader_running }
380
+ else
381
+ logger.fatal {"Could not start reader, not connected!"}
382
+ nil # return_value
383
+ end
384
+ end
385
+
386
+ protected
387
+ # Message subscribers. Key is the message class to listen for.
388
+ # Value is a Hash of subscriber Procs, keyed by their subscription id.
389
+ # All subscriber Procs will be called with the message instance
390
+ # as an argument when a message of that type is received.
391
+ def subscribers
392
+ @subscribers ||= Hash.new { |hash, subs| hash[subs] = Hash.new }
393
+ end
394
+
395
+ # Process single incoming message (blocking!)
396
+ def process_message
397
+ logger.progname='IB::Connection#process_message' if logger.is_a?(Logger)
398
+
399
+ socket.decode_message( socket.recieve_messages ) do | the_decoded_message |
400
+ # puts "THE deCODED MESSAGE #{ the_decoded_message.inspect}"
401
+ msg_id = the_decoded_message.shift.to_i
402
+
403
+ # Debug:
404
+ logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}
405
+
406
+ # Create new instance of the appropriate message type,
407
+ # and have it read the message from socket.
408
+ # NB: Failure here usually means unsupported message type received
409
+ logger.error { "Got unsupported message #{msg_id}" } unless Messages::Incoming::Classes[msg_id]
410
+ error "Something strange happened - Reader has to be restarted" , :reader if msg_id.to_i.zero?
411
+ msg = Messages::Incoming::Classes[msg_id].new(the_decoded_message)
412
+
413
+ # Deliver message to all registered subscribers, alert if no subscribers
414
+ # Ruby 2.0 and above: Hashes are ordered.
415
+ # Thus first declared subscribers of a class are executed first
416
+ @subscribe_lock.synchronize do
417
+ subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
418
+ end
419
+ logger.warn { "No subscribers for message #{msg.class}!" } if subscribers[msg.class].empty?
420
+
421
+ # Collect all received messages into a @received Hash
422
+ if @received
423
+ @receive_lock.synchronize do
424
+ received[msg.message_type] << msg
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ def random_id
431
+ rand 999999
432
+ end
433
+
434
+ # Check if all given conditions are satisfied
435
+ def satisfied? *conditions
436
+ !conditions.empty? &&
437
+ conditions.inject(true) do |result, condition|
438
+ result && if condition.is_a?(Symbol)
439
+ received?(condition)
440
+ elsif condition.is_a?(Array)
441
+ received?(*condition)
442
+ elsif condition.respond_to?(:call)
443
+ condition.call
444
+ else
445
+ logger.error { "Unknown wait condition #{condition}" }
446
+ end
447
+ end
448
+ end
449
+ end # class Connection
450
+ end # module IB
@@ -0,0 +1,393 @@
1
+ module IB
2
+ ### Widely used TWS constants:
3
+
4
+ EOL = "\0"
5
+ # TWS_MAX is TWSMAX (transmitted from the TWS) minus the first digit (1)
6
+ # Anything bigger then TWS_MAX is considered as nil (in read_decimal @ messages/incomming/abstract_message.rb)
7
+ TWS_MAX = 79769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0
8
+ TWSMAX = 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0
9
+
10
+
11
+ # Enumeration of bar size types for convenience.
12
+ # Bar sizes less than 30 seconds do not work for some securities.
13
+ BAR_SIZES = {'1 sec' => :sec1,
14
+ '5 secs' => :sec5,
15
+ '15 secs' =>:sec15,
16
+ '30 secs' =>:sec30,
17
+ '1 min' => :min1,
18
+ '2 mins' => :min2,
19
+ '3 mins' => :min3,
20
+ '5 mins' => :min5,
21
+ '15 mins' =>:min15,
22
+ '30 mins' =>:min30,
23
+ '1 hour' =>:hour1,
24
+ '1 day' => :day1
25
+ }.freeze
26
+
27
+ # Enumeration of data types.
28
+ # Determines the nature of data being extracted. Valid values:
29
+ DATA_TYPES = {'TRADES' => :trades,
30
+ 'MIDPOINT' => :midpoint,
31
+ 'BID' => :bid,
32
+ 'ASK' => :ask,
33
+ 'BID_ASK' => :bid_ask,
34
+ 'HISTORICAL_VOLATILITY' => :historical_volatility,
35
+ 'OPTION_IMPLIED_VOLATILITY' => :option_implied_volatility,
36
+ 'OPTION_VOLUME' => :option_volume,
37
+ 'OPTION_OPEN_INTEREST' => :option_open_interest
38
+ }.freeze
39
+
40
+ ### These values are typically received from TWS in incoming messages
41
+
42
+ # Tick types as received in TickPrice and TickSize messages (enumeration)
43
+ TICK_TYPES = {
44
+ # int id => :Description # Corresponding API Event/Function/Method
45
+ 0 => :bid_size, # tickSize()
46
+ 1 => :bid_price, # tickPrice()
47
+ 2 => :ask_price, # tickPrice()
48
+ 3 => :ask_size, # tickSize()
49
+ 4 => :last_price, # tickPrice()
50
+ 5 => :last_size, # tickSize()
51
+ 6 => :high, # tickPrice()
52
+ 7 => :low, # tickPrice()
53
+ 8 => :volume, # tickSize()
54
+ 9 => :close_price, # tickPrice()
55
+ 10 => :bid_option, # tickOptionComputation() See Note 1 below
56
+ 11 => :ask_option, # tickOptionComputation() See => :Note 1 below
57
+ 12 => :last_option, # tickOptionComputation() See Note 1 below
58
+ 13 => :model_option, # tickOptionComputation() See Note 1 below
59
+ 14 => :open_tick, # tickPrice()
60
+ 15 => :low_13_week, # tickPrice()
61
+ 16 => :high_13_week, # tickPrice()
62
+ 17 => :low_26_week, # tickPrice()
63
+ 18 => :high_26_week, # tickPrice()
64
+ 19 => :low_52_week, # tickPrice()
65
+ 20 => :high_52_week, # tickPrice()
66
+ 21 => :avg_volume, # tickSize()
67
+ 22 => :open_interest, # tickSize()
68
+ 23 => :option_historical_vol, # tickGeneric()
69
+ 24 => :option_implied_vol, # tickGeneric()
70
+ 25 => :option_bid_exch, # not USED
71
+ 26 => :option_ask_exch, # not USED
72
+ 27 => :option_call_open_interest, # tickSize()
73
+ 28 => :option_put_open_interest, # tickSize()
74
+ 29 => :option_call_volume, # tickSize()
75
+ 30 => :option_put_volume, # tickSize()
76
+ 31 => :index_future_premium, # tickGeneric()
77
+ 32 => :bid_exch, # tickString()
78
+ 33 => :ask_exch, # tickString()
79
+ 34 => :auction_volume, # not USED
80
+ 35 => :auction_price, # not USED
81
+ 36 => :auction_imbalance, # not USED
82
+ 37 => :mark_price, # tickPrice()
83
+ 38 => :bid_efp_computation, # tickEFP()
84
+ 39 => :ask_efp_computation, # tickEFP()
85
+ 40 => :last_efp_computation, # tickEFP()
86
+ 41 => :open_efp_computation, # tickEFP()
87
+ 42 => :high_efp_computation, # tickEFP()
88
+ 43 => :low_efp_computation, # tickEFP()
89
+ 44 => :close_efp_computation, # tickEFP()
90
+ 45 => :last_timestamp, # tickString()
91
+ 46 => :shortable, # tickGeneric()
92
+ 47 => :fundamental_ratios, # tickString()
93
+ 48 => :rt_volume, # tickGeneric()
94
+ 49 => :halted, # see Note 2 below.
95
+ 50 => :bid_yield, # tickPrice() See Note 3 below
96
+ 51 => :ask_yield, # tickPrice() See Note 3 below
97
+ 52 => :last_yield, # tickPrice() See Note 3 below
98
+ 53 => :cust_option_computation, # tickOptionComputation()
99
+ 54 => :trade_count, # tickGeneric()
100
+ 55 => :trade_rate, # tickGeneric()
101
+ 56 => :volume_rate, # tickGeneric()
102
+ 57 => :last_rth_trade, #
103
+ 58 => :rt_historical_vol,
104
+ 59 => :ib_dividends,
105
+ 60 => :bond_factor_multiplier,
106
+ 61 => :regulatory_imbalance,
107
+ 62 => :news_tick,
108
+ 63 => :short_term_volume_3_min,
109
+ 64 => :short_term_volume_5_min,
110
+ 65 => :short_term_volume_10_min,
111
+ 66 => :delayed_bid,
112
+ 67 => :delayed_ask,
113
+ 68 => :delayed_last,
114
+ 69 => :delayed_bid_size,
115
+ 70 => :delayed_ask_size,
116
+ 71 => :delayed_last_size,
117
+ 72 => :delayed_high,
118
+ 73 => :delayed_low,
119
+ 74 => :delayed_volume,
120
+ 75 => :delayed_close,
121
+ 76 => :delayed_open,
122
+ 77 => :rt_trd_volume,
123
+ 78 => :creditman_mark_price,
124
+ 79 => :creditman_slow_mark_price,
125
+ 80 => :delayed_bid_option,
126
+ 81 => :delayed_ask_option,
127
+ 82 => :delayed_last_option,
128
+ 83 => :delayed_model_option,
129
+ 84 => :last_exch,
130
+ 85 => :last_reg_time,
131
+ 86 => :futures_open_interest,
132
+ 87 => :avg_opt_volume,
133
+ 88 => :not_set,
134
+ 105 => :average_option_volume #(for Stocks) tickGeneric()
135
+
136
+
137
+ # Note 1: Tick types BID_OPTION, ASK_OPTION, LAST_OPTION, and MODEL_OPTION return
138
+ # all Greeks (delta, gamma, vega, theta), the underlying price and the
139
+ # stock and option reference price when requested.
140
+ # MODEL_OPTION also returns model implied volatility.
141
+ # Note 2: When trading is halted for a contract, TWS receives a special tick:
142
+ # haltedLast=1. When trading is resumed, TWS receives haltedLast=0.
143
+ # A tick type, HALTED, tick ID= 49, is now available in regular market
144
+ # data via the API to indicate this halted state. Possible values for
145
+ # this new tick type are: 0 = Not halted, 1 = Halted.
146
+ # Note 3: Applies to bond contracts only.
147
+ }
148
+
149
+ # Financial Advisor types (FaMsgTypeName)
150
+ FA_TYPES = {
151
+ 1 => :groups,
152
+ 2 => :profiles,
153
+ 3 => :aliases}.freeze
154
+
155
+ # Received in new MarketDataType (58 incoming) message
156
+ MARKET_DATA_TYPES = {
157
+ 0 => :unknown,
158
+ 1 => :real_time,
159
+ 2 => :frozen,
160
+ 3 => :delayed,
161
+ 4 => :frozen_delayed }.freeze
162
+
163
+ # Market depth messages contain these "operation" codes to tell you what to do with the data.
164
+ # See also http://www.interactivebrokers.com/php/apiUsersGuide/apiguide/java/updatemktdepth.htm
165
+ MARKET_DEPTH_OPERATIONS = {
166
+ 0 => :insert, # New order, insert into the row identified by :position
167
+ 1 => :update, # Update the existing order at the row identified by :position
168
+ 2 => :delete # Delete the existing order at the row identified by :position
169
+ }.freeze
170
+
171
+ MARKET_DEPTH_SIDES = {
172
+ 0 => :ask,
173
+ 1 => :bid
174
+ }.freeze
175
+
176
+ ORDER_TYPES =
177
+ {'LMT' => :limit, # Limit Order
178
+ 'LIT' => :limit_if_touched, # Limit if Touched
179
+ 'LOC' => :limit_on_close, # Limit-on-Close LMTCLS ?
180
+ 'LOO' => :limit_on_open, # Limit-on-Open
181
+ 'MKT' => :market, # Market
182
+ 'MIT' => :market_if_touched, # Market-if-Touched
183
+ 'MOC' => :market_on_close, # Market-on-Close MKTCLSL ?
184
+ 'MOO' => :market_on_open, # Market-on-Open
185
+ 'MTL' => :market_to_limit, # Market-to-Limit
186
+ 'MKT PRT' => :market_protected, # Market with Protection
187
+ 'QUOTE' => :request_for_quote, # Request for Quote
188
+ 'STP' => :stop, # Stop
189
+ 'STP LMT' => :stop_limit, # Stop Limit
190
+ 'STP PRT' => :stop_protected, # Stop with Protection
191
+ 'TRAIL' => :trailing_stop, # Trailing Stop
192
+ 'TRAIL LIMIT' => :trailing_limit, # Trailing Stop Limit
193
+ 'TRAIL LIT' => :trailing_limit_if_touched, # Trailing Limit if Touched
194
+ 'TRAIL MIT' => :trailing_market_if_touched, # Trailing Market If Touched
195
+ 'REL' => :relative, # Relative
196
+ 'BOX TOP' => :box_top, # Box Top
197
+ 'PEG MKT' => :pegged_to_market, # Pegged-to-Market
198
+ 'PEG STK' => :pegged_to_market, # Pegged-to-Stock
199
+ 'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
200
+ 'PEG BENCH' => :pegged_to_benchmark, # Pegged-to-Benmchmark # Vers. 102
201
+ 'VWAP' => :vwap, # VWAP-Guaranted
202
+ 'OCA' => :one_cancels_all, # One-Cancels-All
203
+ 'VOL' => :volatility, # Volatility
204
+ 'SCALE' => :scale, # Scale
205
+ 'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
206
+ 'None' => :none, # Used to indicate no hedge in :delta_neutral_order_type
207
+ }.freeze
208
+ # Valid security types (sec_type attribute of IB::Contract)
209
+ SECURITY_TYPES =
210
+ { 'BAG' => :bag,
211
+ 'BOND' => :bond,
212
+ 'CASH' => :forex,
213
+ 'CMDTY'=> :commodity,
214
+ 'CFD' => :cfd,
215
+ 'FUT' => :future,
216
+ 'CONTFUT' => :continous_future,
217
+ 'FUT+CONTFUT' => :all_futures,
218
+ 'FOP' => :futures_option,
219
+ 'FUND' => :fund, # ETF?
220
+ 'IND' => :index,
221
+ 'NEWS' => :news,
222
+ 'OPT' => :option,
223
+ 'IOPT' => :dutch_option,
224
+ 'STK' => :stock,
225
+ 'WAR' => :warrant,
226
+ 'ICU' => :icu,
227
+ 'ICS' => :ics,
228
+ 'BILL' => :bill,
229
+ 'BSK' => :basket,
230
+ 'FWD' => :forward,
231
+ 'FIXED' => :fixed }.freeze
232
+
233
+ # Obtain symbolic value from given property code:
234
+ # VALUES[:side]['B'] -> :buy
235
+ VALUES = {
236
+ :sec_type => SECURITY_TYPES,
237
+ :order_type => ORDER_TYPES,
238
+ :delta_neutral_order_type => ORDER_TYPES,
239
+
240
+ :origin => {0 => :customer, 1 => :firm},
241
+ :volatility_type => {1 => :daily, 2 => :annual},
242
+ :reference_price_type => {1 => :average, 2 => :bid_or_ask},
243
+
244
+ # This property encodes differently for ComboLeg and Order objects,
245
+ # we use ComboLeg codes and transcode for Order codes as needed
246
+ :open_close =>
247
+ {0 => :same, # Default for Legs, same as the parent (combo) security.
248
+ 1 => :open, # Open. For Legs, this value is only used by institutions.
249
+ 2 => :close, # Close. For Legs, this value is only used by institutions.
250
+ 3 => :unknown}, # WTF
251
+
252
+ :right =>
253
+ {'' => :none, # Not an option
254
+ 'P' => :put,
255
+ 'C' => :call},
256
+
257
+ :side => # AKA action
258
+ {'B' => :buy, # or BOT
259
+ 'S' => :sell, # or SLD
260
+ 'T' => :short, # short
261
+ 'X' => :short_exempt # Short Sale Exempt action. This allows some orders
262
+ # to be exempt from the SEC recent changes to Regulation SHO, which
263
+ # eliminated the old uptick rule and replaced it with a new "circuit breaker"
264
+ # rule, and allows some orders to be exempt from the new rule.
265
+ },
266
+
267
+ :short_sale_slot =>
268
+ {0 => :default, # The only valid option for retail customers
269
+ 1 => :broker, # Shares are at your clearing broker, institutions
270
+ 2 => :third_party}, # Shares will be delivered from elsewhere, institutions
271
+
272
+ :oca_type =>
273
+ {0 => :none, # Not a member of OCA group
274
+ 1 => :cancel_with_block, # Cancel all remaining orders with block
275
+ 2 => :reduce_with_block, # Remaining orders are reduced in size with block
276
+ 3 => :reduce_no_block}, # Remaining orders are reduced in size with no block
277
+
278
+ :auction_strategy =>
279
+ {0 => :none, # Not a BOX order
280
+ 1 => :match,
281
+ 2 => :improvement,
282
+ 3 => :transparent},
283
+
284
+ :trigger_method =>
285
+ {0 => :default, # "double bid/ask" used for OTC/US options, "last" otherswise.
286
+ 1 => :double_bid_ask, # stops are triggered by 2 consecutive bid or ask prices.
287
+ 2 => :last, # stops are triggered based on the last price.
288
+ 3 => :double_last,
289
+ 4 => :bid_ask, # bid >= trigger price for buy orders, ask <= trigger for sell orders
290
+ 7 => :last_or_bid_ask, # bid OR last price >= trigger price for buy orders
291
+ 8 => :mid_point}, # midpoint >= trigger price for buy orders and the
292
+ # spread between the bid and ask must be less than 0.1% of the midpoint
293
+
294
+ :hedge_type =>
295
+ {'D' => :delta, # parent order is an option and the child order is a stock
296
+ 'B' => :beta, # offset market risk by entering into a position with
297
+ # another contract based on the system or user-defined beta
298
+ 'F' => :forex, # offset risk with currency different from your base currency
299
+ 'P' => :pair}, # trade a mis-valued pair of contracts and provide the
300
+ # ratio between the parent and hedging child order
301
+
302
+ :clearing_intent =>
303
+ {'' => :none,
304
+ 'IB' => :ib,
305
+ 'AWAY' => :away,
306
+ 'PTA' => :post_trade_allocation},
307
+
308
+ :delta_neutral_clearing_intent =>
309
+ {'' => :none,
310
+ 'IB' => :ib,
311
+ 'AWAY' => :away,
312
+ 'PTA' => :post_trade_allocation},
313
+
314
+ :tif =>
315
+ {'DAY' => :day,
316
+ 'GAT' => :good_after_time,
317
+ 'GTD' => :good_till_date,
318
+ 'GTC' => :good_till_cancelled,
319
+ 'IOC' => :immediate_or_cancel,
320
+ 'OPG' => :opening_price,
321
+ 'AUC' => :at_auction},
322
+
323
+ :rule_80a =>
324
+ {'I' => :individual,
325
+ 'A' => :agency,
326
+ 'W' => :agent_other_member,
327
+ 'J' => :individual_ptia,
328
+ 'U' => :agency_ptia,
329
+ 'M' => :agent_other_member_ptia,
330
+ 'K' => :individual_pt,
331
+ 'Y' => :agency_pt,
332
+ 'N' => :agent_other_member_pt},
333
+
334
+ :opt? => # TODO: unknown Order property, like OPT_BROKER_DEALER... in Order.java
335
+ {'?' => :unknown,
336
+ 'b' => :broker_dealer,
337
+ 'c' => :customer,
338
+ 'f' => :firm,
339
+ 'm' => :isemm,
340
+ 'n' => :farmm,
341
+ 'y' => :specialist},
342
+ # conditions
343
+ :conjunction_connection => { 'o' => :or, 'a' => :and },
344
+ :operator => { 1 => '>=' , 0 => '<=' }
345
+
346
+ }.freeze
347
+
348
+ # Obtain property code from given symbolic value:
349
+ # CODES[:side][:buy] -> 'B'
350
+ CODES = Hash[VALUES.map { |property, hash| [property, hash.invert] }].freeze
351
+
352
+ # Most common property processors
353
+ PROPS = {:side =>
354
+ {:set => proc { |val| # BUY(BOT)/SELL(SLD)/SSHORT/SSHORTX
355
+ self[:side] = case val.to_s.upcase
356
+ when /SHORT.*X|\AX\z/
357
+ 'X'
358
+ when /SHORT|\AT\z/
359
+ 'T'
360
+ when /\AB/
361
+ 'B'
362
+ when /\AS/
363
+ 'S'
364
+ end },
365
+ :validate =>
366
+ {:format =>
367
+ {:with => /\Abuy\z|\Asell\z|\Ashort\z|\Ashort_exempt\z/,
368
+ :message => "should be buy/sell/short"}
369
+ }
370
+ },
371
+
372
+ :open_close =>
373
+ {:set => proc { |val|
374
+ self[:open_close] = case val.to_s.upcase[0..0]
375
+ when 'S', '0' # SAME
376
+ 0
377
+ when 'O', '1' # OPEN
378
+ 1
379
+ when 'C', '2' # CLOSE
380
+ 2
381
+ when 'U', '3' # Unknown
382
+ 3
383
+ end
384
+ },
385
+ :validate =>
386
+ {:format =>
387
+ {:with => /\Asame\z|\Aopen\z|\Aclose\z|\Aunknown\z/,
388
+ :message => "should be same/open/close/unknown"}
389
+ },
390
+ }
391
+ }.freeze
392
+
393
+ end # module IB