ib-api 972.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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