my-ib-api 0.0.1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ib-api.rb +10 -0
  3. data/lib/ib/base.rb +99 -0
  4. data/lib/ib/base_properties.rb +154 -0
  5. data/lib/ib/connection.rb +327 -0
  6. data/lib/ib/constants.rb +334 -0
  7. data/lib/ib/db.rb +29 -0
  8. data/lib/ib/engine.rb +35 -0
  9. data/lib/ib/errors.rb +40 -0
  10. data/lib/ib/extensions.rb +72 -0
  11. data/lib/ib/flex.rb +106 -0
  12. data/lib/ib/logger.rb +25 -0
  13. data/lib/ib/messages.rb +88 -0
  14. data/lib/ib/messages/abstract_message.rb +89 -0
  15. data/lib/ib/messages/incoming.rb +134 -0
  16. data/lib/ib/messages/incoming/abstract_message.rb +99 -0
  17. data/lib/ib/messages/incoming/alert.rb +34 -0
  18. data/lib/ib/messages/incoming/contract_data.rb +102 -0
  19. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  20. data/lib/ib/messages/incoming/execution_data.rb +54 -0
  21. data/lib/ib/messages/incoming/historical_data.rb +55 -0
  22. data/lib/ib/messages/incoming/market_depths.rb +44 -0
  23. data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
  24. data/lib/ib/messages/incoming/open_order.rb +232 -0
  25. data/lib/ib/messages/incoming/order_status.rb +81 -0
  26. data/lib/ib/messages/incoming/portfolio_value.rb +39 -0
  27. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  28. data/lib/ib/messages/incoming/scanner_data.rb +53 -0
  29. data/lib/ib/messages/incoming/ticks.rb +131 -0
  30. data/lib/ib/messages/outgoing.rb +331 -0
  31. data/lib/ib/messages/outgoing/abstract_message.rb +73 -0
  32. data/lib/ib/messages/outgoing/bar_requests.rb +189 -0
  33. data/lib/ib/messages/outgoing/place_order.rb +141 -0
  34. data/lib/ib/model.rb +6 -0
  35. data/lib/ib/models.rb +10 -0
  36. data/lib/ib/requires.rb +9 -0
  37. data/lib/ib/socket.rb +81 -0
  38. data/lib/ib/symbols.rb +35 -0
  39. data/lib/ib/symbols/bonds.rb +28 -0
  40. data/lib/ib/symbols/forex.rb +41 -0
  41. data/lib/ib/symbols/futures.rb +117 -0
  42. data/lib/ib/symbols/options.rb +39 -0
  43. data/lib/ib/symbols/stocks.rb +37 -0
  44. data/lib/ib/version.rb +6 -0
  45. data/lib/models/ib/bag.rb +51 -0
  46. data/lib/models/ib/bar.rb +45 -0
  47. data/lib/models/ib/combo_leg.rb +103 -0
  48. data/lib/models/ib/contract.rb +292 -0
  49. data/lib/models/ib/contract_detail.rb +89 -0
  50. data/lib/models/ib/execution.rb +65 -0
  51. data/lib/models/ib/option.rb +60 -0
  52. data/lib/models/ib/order.rb +391 -0
  53. data/lib/models/ib/order_state.rb +128 -0
  54. data/lib/models/ib/underlying.rb +34 -0
  55. metadata +96 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 792fc49c490a522dbe2f5bb42ed76c25f72257ad309fad068e2422d7cf7cc320
4
+ data.tar.gz: c6182a9d72d900a820d0493db2c7f8b8eac9d07c42b826f658d64e47ffa2d5d4
5
+ SHA512:
6
+ metadata.gz: f96ed40918e1a8ddf63c57618f2b18c84ecfa4ccef23f92aa3073d5bf30dfe4c11b51d962f2eb7e2bbd852b0dbc16f1e4e3441ede1bf4dce5c8cc0ef0da4f966
7
+ data.tar.gz: 366d962ba84ae4c899d9c0abf897830a2f62d48c9a5a97b14572aba10f9f28d664084db39b95e311c90441a328e8e1c3b41c6e563c4fa7cb0a3eaddfd675c5c8
@@ -0,0 +1,10 @@
1
+ module IB
2
+
3
+ end # module IB
4
+
5
+ IbRuby = IB
6
+ Ib = IB
7
+
8
+ #puts $LOAD_PATH
9
+
10
+ require 'ib/requires'
@@ -0,0 +1,99 @@
1
+ module IB
2
+
3
+ # Base class for tableless IB data Models, extends ActiveModel API
4
+ class Base
5
+ extend ActiveModel::Naming
6
+ extend ActiveModel::Callbacks
7
+ include ActiveModel::Validations
8
+ include ActiveModel::Serialization
9
+ ### include ActiveModel::Serializers::Xml
10
+ ### include ActiveModel::Serializers::JSON
11
+
12
+ define_model_callbacks :initialize
13
+
14
+ # If a opts hash is given, keys are taken as attribute names, values as data.
15
+ # The model instance fields are then set automatically from the opts Hash.
16
+ def initialize attributes={}, opts={}
17
+ run_callbacks :initialize do
18
+ error "Argument must be a Hash", :args unless attributes.is_a?(Hash)
19
+
20
+ self.attributes = attributes # set_attribute_defaults is now after_init callback
21
+ end
22
+ end
23
+
24
+ # ActiveModel API (for serialization)
25
+
26
+ def attributes
27
+ @attributes ||= HashWithIndifferentAccess.new
28
+ end
29
+
30
+ def attributes= attrs
31
+ attrs.keys.each { |key| self.send("#{key}=", attrs[key]) }
32
+ end
33
+
34
+ # ActiveModel-style read/write_attribute accessors
35
+ def [] key
36
+ attributes[key.to_sym]
37
+ end
38
+
39
+ def []= key, val
40
+ # p key, val
41
+ attributes[key.to_sym] = val
42
+ end
43
+
44
+ def to_model
45
+ self
46
+ end
47
+
48
+ def new_record?
49
+ true
50
+ end
51
+
52
+ def save
53
+ valid?
54
+ end
55
+
56
+ alias save! save
57
+
58
+ ### Noop methods mocking ActiveRecord::Base macros
59
+
60
+ def self.attr_protected *args
61
+ end
62
+
63
+ def self.attr_accessible *args
64
+ end
65
+
66
+ ### ActiveRecord::Base association API mocks
67
+
68
+ def self.belongs_to model, *args
69
+ attr_accessor model
70
+ end
71
+
72
+ def self.has_one model, *args
73
+ attr_accessor model
74
+ end
75
+
76
+ def self.has_many models, *args
77
+ attr_accessor models
78
+
79
+ define_method(models) do
80
+ self.instance_variable_get("@#{models}") ||
81
+ self.instance_variable_set("@#{models}", [])
82
+ end
83
+ end
84
+
85
+ def self.find *args
86
+ []
87
+ end
88
+
89
+ ### ActiveRecord::Base callback API mocks
90
+
91
+ define_model_callbacks :initialize, :only => :after
92
+
93
+ ### ActiveRecord::Base misc
94
+
95
+ def self.serialize *properties
96
+ end
97
+
98
+ end # Model
99
+ end # module IB
@@ -0,0 +1,154 @@
1
+
2
+ require 'active_model'
3
+ #require "active_model_serializers"
4
+
5
+ require 'active_support/concern'
6
+ require 'active_support/hash_with_indifferent_access'
7
+
8
+ module IB
9
+
10
+ # Module adds prop Macro and
11
+ module BaseProperties
12
+ extend ActiveSupport::Concern
13
+
14
+ ### Instance methods
15
+
16
+ # Default presentation
17
+ def to_human
18
+ "<#{self.class.to_s.demodulize}: " + attributes.map do |attr, value|
19
+ "#{attr}: #{value}" unless value.nil?
20
+ end.compact.sort.join(' ') + ">"
21
+ end
22
+
23
+ # Comparison support
24
+ def content_attributes
25
+ HashWithIndifferentAccess[attributes.reject do |(attr, _)|
26
+ attr.to_s =~ /(_count)\z/ ||
27
+ [:created_at, :updated_at, :type,
28
+ :id, :order_id, :contract_id].include?(attr.to_sym)
29
+ end]
30
+ end
31
+
32
+ # Update nil attributes from given Hash or model
33
+ def update_missing attrs
34
+ attrs = attrs.content_attributes unless attrs.kind_of?(Hash)
35
+
36
+ attrs.each { |attr, val| send "#{attr}=", val if send(attr).blank? }
37
+ self # for chaining
38
+ end
39
+
40
+ # Default Model comparison
41
+ def == other
42
+ case other
43
+ when String # Probably a Rails URI, delegate to AR::Base
44
+ super(other)
45
+ else
46
+ content_attributes.keys.inject(true) { |res, key|
47
+ res && other.respond_to?(key) && (send(key) == other.send(key)) }
48
+ end
49
+ end
50
+
51
+ ### Default attributes support
52
+
53
+ def default_attributes
54
+ {:created_at => Time.now,
55
+ :updated_at => Time.now,
56
+ }
57
+ end
58
+
59
+ def set_attribute_defaults
60
+ default_attributes.each do |key, val|
61
+ self.send("#{key}=", val) if self.send(key).nil?
62
+ # self.send("#{key}=", val) if self[key].nil? # Problems with association defaults
63
+ end
64
+ end
65
+
66
+ included do
67
+
68
+ after_initialize :set_attribute_defaults
69
+
70
+ ### Class macros
71
+
72
+ def self.prop *properties
73
+ prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
74
+
75
+ properties.each { |names| define_property names, nil }
76
+ prop_hash.each { |names, type| define_property names, type }
77
+ end
78
+
79
+ def self.define_property names, body
80
+ aliases = [names].flatten
81
+ name = aliases.shift
82
+ instance_eval do
83
+
84
+ define_property_methods name, body
85
+
86
+ aliases.each do |ali|
87
+ alias_method "#{ali}", name
88
+ alias_method "#{ali}=", "#{name}="
89
+ end
90
+ end
91
+ end
92
+
93
+ def self.define_property_methods name, body={}
94
+ #p name, body
95
+ case body
96
+ when '' # default getter and setter
97
+ define_property_methods name
98
+
99
+ when Array # [setter, getter, validators]
100
+ define_property_methods name,
101
+ :get => body[0],
102
+ :set => body[1],
103
+ :validate => body[2]
104
+
105
+ when Hash # recursion base case
106
+ getter = case # Define getter
107
+ when body[:get].respond_to?(:call)
108
+ body[:get]
109
+ when body[:get]
110
+ proc { self[name].send "to_#{body[:get]}" }
111
+ when VALUES[name] # property is encoded
112
+ proc { VALUES[name][self[name]] }
113
+ else
114
+ proc { self[name] }
115
+ end
116
+ define_method name, &getter if getter
117
+
118
+ setter = case # Define setter
119
+ when body[:set].respond_to?(:call)
120
+ body[:set]
121
+ when body[:set]
122
+ proc { |value| self[name] = value.send "to_#{body[:set]}" }
123
+ when CODES[name] # property is encoded
124
+ proc { |value| self[name] = CODES[name][value] || value }
125
+ else
126
+ proc { |value| self[name] = value } # p name, value;
127
+ end
128
+ define_method "#{name}=", &setter if setter
129
+
130
+ # Define validator(s)
131
+ [body[:validate]].flatten.compact.each do |validator|
132
+ case validator
133
+ when Proc
134
+ validates_each name, &validator
135
+ when Hash
136
+ validates name, validator.dup
137
+ end
138
+ end
139
+
140
+ # TODO define self[:name] accessors for :virtual and :flag properties
141
+
142
+ else # setter given
143
+ define_property_methods name, :set => body, :get => body
144
+ end
145
+ end
146
+
147
+ # Timestamps in lightweight models
148
+ unless defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
149
+ prop :created_at, :updated_at
150
+ end
151
+
152
+ end # included
153
+ end # module BaseProperties
154
+ end
@@ -0,0 +1,327 @@
1
+ require 'thread'
2
+ require 'ib/socket'
3
+ require 'ib/logger'
4
+ require 'ib/messages'
5
+
6
+ module IB
7
+ # Encapsulates API connection to TWS or Gateway
8
+ class Connection
9
+
10
+ # Please note, we are realizing only the most current TWS protocol versions,
11
+ # thus improving performance at the expense of backwards compatibility.
12
+ # Older protocol versions support can be found in older gem versions.
13
+
14
+ DEFAULT_OPTIONS = {:host =>'127.0.0.1',
15
+ :port => '4001', # IB Gateway connection (default)
16
+ #:port => '7496', # TWS connection
17
+ :connect => true, # Connect at initialization
18
+ :reader => true, # Start a separate reader Thread
19
+ :received => true, # Keep all received messages in a @received Hash
20
+ :logger => nil,
21
+ :client_id => nil, # Will be randomly assigned
22
+ :client_version => IB::Messages::CLIENT_VERSION,
23
+ :server_version => IB::Messages::SERVER_VERSION
24
+ }
25
+
26
+ # Singleton to make active Connection universally accessible as IB::Connection.current
27
+ class << self
28
+ attr_accessor :current
29
+ end
30
+
31
+ attr_accessor :options, # Connection options
32
+ :socket, # Socket to IB server (TWS or Gateway)
33
+ :reader, # Reader thread
34
+ :client_version,
35
+ :server_version,
36
+ :remote_connect_time,
37
+ :local_connect_time,
38
+ :client_id, # Client id of this Connection (as seen bu IB server)
39
+ :next_local_id # Next valid order id
40
+
41
+ alias next_order_id next_local_id
42
+ alias next_order_id= next_local_id=
43
+
44
+ def initialize opts = {}
45
+ @options = DEFAULT_OPTIONS.merge(opts)
46
+
47
+ # A couple of locks to avoid race conditions in JRuby
48
+ @subscribe_lock = Mutex.new
49
+ @receive_lock = Mutex.new
50
+
51
+ self.default_logger = options[:logger] if options[:logger]
52
+ @connected = false
53
+ self.next_local_id = nil
54
+
55
+ connect if options[:connect]
56
+ Connection.current = self
57
+ end
58
+
59
+ ### Working with connection
60
+
61
+ def connect
62
+ error "Already connected!" if connected?
63
+
64
+ # TWS always sends NextValidId message at connect - save this id
65
+ self.subscribe(:NextValidId) do |msg|
66
+ self.next_local_id = msg.local_id
67
+ log.info "Got next valid order id: #{next_local_id}."
68
+ end
69
+
70
+ @socket = IBSocket.open(options[:host], options[:port])
71
+
72
+ # Secret handshake
73
+ @client_version = options[:client_version]
74
+ socket.write_data @client_version
75
+ @server_version = socket.read_int
76
+ if @server_version < options[:server_version]
77
+ error "Server version #{@server_version}, #{options[:server_version]} required."
78
+ end
79
+ @remote_connect_time = socket.read_string
80
+ @local_connect_time = Time.now
81
+
82
+ # Sending (arbitrary) client ID to identify subsequent communications.
83
+ # The client with a client_id of 0 can manage the TWS-owned open orders.
84
+ # Other clients can only manage their own open orders.
85
+ @client_id = options[:client_id] || random_id
86
+ socket.write_data @client_id
87
+
88
+ @connected = true
89
+ log.info "Connected to server, ver: #{@server_version}, connection time: " +
90
+ "#{@local_connect_time} local, " +
91
+ "#{@remote_connect_time} remote."
92
+
93
+ start_reader if options[:reader] # Allows reconnect
94
+ end
95
+
96
+ alias open connect # Legacy alias
97
+
98
+ def disconnect
99
+ if reader_running?
100
+ @reader_running = false
101
+ @reader.join
102
+ end
103
+ if connected?
104
+ socket.close
105
+ @connected = false
106
+ end
107
+ end
108
+
109
+ alias close disconnect # Legacy alias
110
+
111
+ def connected?
112
+ @connected
113
+ end
114
+
115
+ ### Working with message subscribers
116
+
117
+ # Subscribe Proc or block to specific type(s) of incoming message events.
118
+ # Listener will be called later with received message instance as its argument.
119
+ # Returns subscriber id to allow unsubscribing
120
+ def subscribe *args, &block
121
+ @subscribe_lock.synchronize do
122
+ subscriber = args.last.respond_to?(:call) ? args.pop : block
123
+ id = random_id
124
+
125
+ error "Need subscriber proc or block", :args unless subscriber.is_a? Proc
126
+
127
+ args.each do |what|
128
+ message_classes =
129
+ case
130
+ when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
131
+ [what]
132
+ when what.is_a?(Symbol)
133
+ [Messages::Incoming.const_get(what)]
134
+ when what.is_a?(Regexp)
135
+ Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
136
+ else
137
+ error "#{what} must represent incoming IB message class", :args
138
+ end
139
+ message_classes.flatten.each do |message_class|
140
+ # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
141
+ subscribers[message_class][id] = subscriber
142
+ end
143
+ end
144
+ id
145
+ end
146
+ end
147
+
148
+ # Remove all subscribers with specific subscriber id (TODO: multiple ids)
149
+ def unsubscribe *ids
150
+ @subscribe_lock.synchronize do
151
+ removed = []
152
+ ids.each do |id|
153
+ removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
154
+ error "No subscribers with id #{id}" if removed_at_id.empty?
155
+ removed << removed_at_id
156
+ end
157
+ removed.flatten
158
+ end
159
+ end
160
+
161
+ # Message subscribers. Key is the message class to listen for.
162
+ # Value is a Hash of subscriber Procs, keyed by their subscription id.
163
+ # All subscriber Procs will be called with the message instance
164
+ # as an argument when a message of that type is received.
165
+ def subscribers
166
+ @subscribers ||= Hash.new { |hash, subs| hash[subs] = Hash.new }
167
+ end
168
+
169
+ ### Working with received messages Hash
170
+
171
+ # Clear received messages Hash
172
+ def clear_received *message_types
173
+ @receive_lock.synchronize do
174
+ if message_types.empty?
175
+ received.each { |message_type, container| container.clear }
176
+ else
177
+ message_types.each { |message_type| received[message_type].clear }
178
+ end
179
+ end
180
+ end
181
+
182
+ # Hash of received messages, keyed by message type
183
+ def received
184
+ @received ||= Hash.new { |hash, message_type| hash[message_type] = Array.new }
185
+ end
186
+
187
+ # Check if messages of given type were received at_least n times
188
+ def received? message_type, times=1
189
+ @receive_lock.synchronize do
190
+ received[message_type].size >= times
191
+ end
192
+ end
193
+
194
+ # Check if all given conditions are satisfied
195
+ def satisfied? *conditions
196
+ !conditions.empty? &&
197
+ conditions.inject(true) do |result, condition|
198
+ result && if condition.is_a?(Symbol)
199
+ received?(condition)
200
+ elsif condition.is_a?(Array)
201
+ received?(*condition)
202
+ elsif condition.respond_to?(:call)
203
+ condition.call
204
+ else
205
+ error "Unknown wait condition #{condition}"
206
+ end
207
+ end
208
+ end
209
+
210
+ # Wait for specific condition(s) - given as callable/block, or
211
+ # message type(s) - given as Symbol or [Symbol, times] pair.
212
+ # Timeout after given time or 1 second.
213
+ def wait_for *args, &block
214
+ timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
215
+ end_time = Time.now + (timeout || 1) # default timeout 1 sec
216
+ conditions = args.delete_if { |arg| arg.is_a? Numeric }.push(block).compact
217
+
218
+ until end_time < Time.now || satisfied?(*conditions)
219
+ if @reader
220
+ sleep 0.05
221
+ else
222
+ process_messages 50
223
+ end
224
+ end
225
+ end
226
+
227
+ ### Working with Incoming messages from IB
228
+
229
+ # Start reader thread that continuously reads messages from @socket in background.
230
+ # If you don't start reader, you should manually poll @socket for messages
231
+ # or use #process_messages(msec) API.
232
+ def start_reader
233
+ Thread.abort_on_exception = true
234
+ @reader_running = true
235
+ @reader = Thread.new do
236
+ process_messages while @reader_running
237
+ end
238
+ end
239
+
240
+ def reader_running?
241
+ @reader_running && @reader && @reader.alive?
242
+ end
243
+
244
+ # Process incoming messages during *poll_time* (200) msecs, nonblocking
245
+ def process_messages poll_time = 200 # in msec
246
+ time_out = Time.now + poll_time/1000.0
247
+ while (time_left = time_out - Time.now) > 0
248
+ # If socket is readable, process single incoming message
249
+ process_message if select [socket], nil, nil, time_left
250
+ end
251
+ end
252
+
253
+ # Process single incoming message (blocking!)
254
+ def process_message
255
+ msg_id = socket.read_int # This read blocks!
256
+
257
+ # Debug:
258
+ log.debug "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"
259
+
260
+ # Create new instance of the appropriate message type,
261
+ # and have it read the message from socket.
262
+ # NB: Failure here usually means unsupported message type received
263
+ error "Got unsupported message #{msg_id}" unless Messages::Incoming::Classes[msg_id]
264
+ msg = Messages::Incoming::Classes[msg_id].new(socket)
265
+
266
+ # Deliver message to all registered subscribers, alert if no subscribers
267
+ @subscribe_lock.synchronize do
268
+ subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
269
+ end
270
+ log.warn "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
271
+
272
+ # Collect all received messages into a @received Hash
273
+ if options[:received]
274
+ @receive_lock.synchronize do
275
+ received[msg.message_type] << msg
276
+ end
277
+ end
278
+ end
279
+
280
+ ### Sending Outgoing messages to IB
281
+
282
+ # Send an outgoing message.
283
+ def send_message what, *args
284
+ message =
285
+ case
286
+ when what.is_a?(Messages::Outgoing::AbstractMessage)
287
+ what
288
+ when what.is_a?(Class) && what < Messages::Outgoing::AbstractMessage
289
+ what.new *args
290
+ when what.is_a?(Symbol)
291
+ Messages::Outgoing.const_get(what).new *args
292
+
293
+ else
294
+ error "Only able to send outgoing IB messages", :args
295
+ end
296
+ error "Not able to send messages, IB not connected!" unless connected?
297
+ message.send_to socket
298
+ end
299
+
300
+ alias dispatch send_message # Legacy alias
301
+
302
+ # Place Order (convenience wrapper for send_message :PlaceOrder).
303
+ # Assigns client_id and order_id fields to placed order. Returns assigned order_id.
304
+ def place_order order, contract
305
+ order.place contract, self if order.is_a? IB::Order
306
+ end
307
+
308
+ # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id.
309
+ def modify_order order, contract
310
+ order.modify contract, self
311
+ end
312
+
313
+ # Cancel Orders by their local ids (convenience wrapper for send_message :CancelOrder).
314
+ def cancel_order *local_ids
315
+ local_ids.each do |local_id|
316
+ send_message :CancelOrder, :local_id => local_id.to_i
317
+ end
318
+ end
319
+
320
+ protected
321
+
322
+ def random_id
323
+ rand 999999999
324
+ end
325
+
326
+ end # class Connection
327
+ end # module IB