ib-ruby 0.9.0 → 0.9.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 0.9.1
@@ -40,8 +40,8 @@ module IB
40
40
  when String # Probably a Rails URI, delegate to AR::Base
41
41
  super(other)
42
42
  else
43
- content_attributes.keys.inject(true) { |res, key|
44
- res && (send(key) == other.send(key)) }
43
+ content_attributes.keys.inject(true) { |res, key|
44
+ res && other.respond_to?(key) && (send(key) == other.send(key)) }
45
45
  end
46
46
  end
47
47
 
@@ -13,7 +13,7 @@ module IB
13
13
 
14
14
  DEFAULT_OPTIONS = {:host =>'127.0.0.1',
15
15
  :port => '4001', # IB Gateway connection (default)
16
- #:port => '7496', # TWS connection
16
+ #:port => '7496', # TWS connection
17
17
  :connect => true, # Connect at initialization
18
18
  :reader => true, # Start a separate reader Thread
19
19
  :received => true, # Keep all received messages in a @received Hash
@@ -21,16 +21,22 @@ module IB
21
21
  :client_id => nil, # Will be randomly assigned
22
22
  :client_version => IB::Messages::CLIENT_VERSION,
23
23
  :server_version => IB::Messages::SERVER_VERSION
24
- }
24
+ }
25
25
 
26
26
  # Singleton to make active Connection universally accessible as IB::Connection.current
27
27
  class << self
28
28
  attr_accessor :current
29
29
  end
30
30
 
31
- attr_accessor :server, # Info about IB server and server connection state
32
- :options, # Connection options
33
- :next_local_id # Next valid order id
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
34
40
 
35
41
  alias next_order_id next_local_id
36
42
  alias next_order_id= next_local_id=
@@ -45,7 +51,6 @@ module IB
45
51
  self.default_logger = options[:logger] if options[:logger]
46
52
  @connected = false
47
53
  self.next_local_id = nil
48
- @server = Hash.new
49
54
 
50
55
  connect if options[:connect]
51
56
  Connection.current = self
@@ -62,28 +67,28 @@ module IB
62
67
  log.info "Got next valid order id: #{next_local_id}."
63
68
  end
64
69
 
65
- server[:socket] = IBSocket.open(options[:host], options[:port])
70
+ @socket = IBSocket.open(options[:host], options[:port])
66
71
 
67
72
  # Secret handshake
68
- socket.write_data options[:client_version]
69
- server[:client_version] = options[:client_version]
70
- server[:server_version] = socket.read_int
71
- if server[:server_version] < options[:server_version]
72
- error "Server version #{server[:server_version]}, #{options[:server_version]} required."
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."
73
78
  end
74
- server[:remote_connect_time] = socket.read_string
75
- server[:local_connect_time] = Time.now()
79
+ @remote_connect_time = socket.read_string
80
+ @local_connect_time = Time.now
76
81
 
77
82
  # Sending (arbitrary) client ID to identify subsequent communications.
78
83
  # The client with a client_id of 0 can manage the TWS-owned open orders.
79
84
  # Other clients can only manage their own open orders.
80
- server[:client_id] = options[:client_id] || random_id
81
- socket.write_data server[:client_id]
85
+ @client_id = options[:client_id] || random_id
86
+ socket.write_data @client_id
82
87
 
83
88
  @connected = true
84
- log.info "Connected to server, ver: #{server[:server_version]}, connection time: " +
85
- "#{server[:local_connect_time]} local, " +
86
- "#{server[:remote_connect_time]} remote."
89
+ log.info "Connected to server, ver: #{@server_version}, connection time: " +
90
+ "#{@local_connect_time} local, " +
91
+ "#{@remote_connect_time} remote."
87
92
 
88
93
  start_reader if options[:reader] # Allows reconnect
89
94
  end
@@ -93,11 +98,10 @@ module IB
93
98
  def disconnect
94
99
  if reader_running?
95
100
  @reader_running = false
96
- server[:reader].join
101
+ @reader.join
97
102
  end
98
103
  if connected?
99
104
  socket.close
100
- @server = Hash.new
101
105
  @connected = false
102
106
  end
103
107
  end
@@ -108,10 +112,6 @@ module IB
108
112
  @connected
109
113
  end
110
114
 
111
- def socket
112
- server[:socket]
113
- end
114
-
115
115
  ### Working with message subscribers
116
116
 
117
117
  # Subscribe Proc or block to specific type(s) of incoming message events.
@@ -126,16 +126,16 @@ module IB
126
126
 
127
127
  args.each do |what|
128
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
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
139
  message_classes.flatten.each do |message_class|
140
140
  # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
141
141
  subscribers[message_class][id] = subscriber
@@ -194,17 +194,17 @@ module IB
194
194
  # Check if all given conditions are satisfied
195
195
  def satisfied? *conditions
196
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
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
208
  end
209
209
 
210
210
  # Wait for specific condition(s) - given as callable/block, or
@@ -216,7 +216,7 @@ module IB
216
216
  conditions = args.delete_if { |arg| arg.is_a? Numeric }.push(block).compact
217
217
 
218
218
  until end_time < Time.now || satisfied?(*conditions)
219
- if server[:reader]
219
+ if @reader
220
220
  sleep 0.05
221
221
  else
222
222
  process_messages 50
@@ -226,26 +226,26 @@ module IB
226
226
 
227
227
  ### Working with Incoming messages from IB
228
228
 
229
- # Start reader thread that continuously reads messages from server in background.
229
+ # Start reader thread that continuously reads messages from @socket in background.
230
230
  # If you don't start reader, you should manually poll @socket for messages
231
231
  # or use #process_messages(msec) API.
232
232
  def start_reader
233
233
  Thread.abort_on_exception = true
234
234
  @reader_running = true
235
- server[:reader] = Thread.new do
235
+ @reader = Thread.new do
236
236
  process_messages while @reader_running
237
237
  end
238
238
  end
239
239
 
240
240
  def reader_running?
241
- @reader_running && server[:reader] && server[:reader].alive?
241
+ @reader_running && @reader && @reader.alive?
242
242
  end
243
243
 
244
244
  # Process incoming messages during *poll_time* (200) msecs, nonblocking
245
245
  def process_messages poll_time = 200 # in msec
246
246
  time_out = Time.now + poll_time/1000.0
247
247
  while (time_left = time_out - Time.now) > 0
248
- # If server socket is readable, process single incoming message
248
+ # If socket is readable, process single incoming message
249
249
  process_message if select [socket], nil, nil, time_left
250
250
  end
251
251
  end
@@ -258,10 +258,10 @@ module IB
258
258
  log.debug "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"
259
259
 
260
260
  # Create new instance of the appropriate message type,
261
- # and have it read the message from server.
261
+ # and have it read the message from socket.
262
262
  # NB: Failure here usually means unsupported message type received
263
263
  error "Got unsupported message #{msg_id}" unless Messages::Incoming::Classes[msg_id]
264
- msg = Messages::Incoming::Classes[msg_id].new(server)
264
+ msg = Messages::Incoming::Classes[msg_id].new(socket)
265
265
 
266
266
  # Deliver message to all registered subscribers, alert if no subscribers
267
267
  @subscribe_lock.synchronize do
@@ -270,8 +270,10 @@ module IB
270
270
  log.warn "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
271
271
 
272
272
  # Collect all received messages into a @received Hash
273
- @receive_lock.synchronize do
274
- received[msg.message_type] << msg if options[:received]
273
+ if options[:received]
274
+ @receive_lock.synchronize do
275
+ received[msg.message_type] << msg
276
+ end
275
277
  end
276
278
  end
277
279
 
@@ -280,18 +282,18 @@ module IB
280
282
  # Send an outgoing message.
281
283
  def send_message what, *args
282
284
  message =
283
- case
284
- when what.is_a?(Messages::Outgoing::AbstractMessage)
285
- what
286
- when what.is_a?(Class) && what < Messages::Outgoing::AbstractMessage
287
- what.new *args
288
- when what.is_a?(Symbol)
289
- Messages::Outgoing.const_get(what).new *args
290
- else
291
- error "Only able to send outgoing IB messages", :args
292
- end
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
+ else
293
+ error "Only able to send outgoing IB messages", :args
294
+ end
293
295
  error "Not able to send messages, IB not connected!" unless connected?
294
- message.send_to server
296
+ message.send_to socket
295
297
  end
296
298
 
297
299
  alias dispatch send_message # Legacy alias
@@ -159,7 +159,8 @@ module IB
159
159
  'OCA' => :one_cancels_all, # One-Cancels-All
160
160
  'VOL' => :volatility, # Volatility
161
161
  'SCALE' => :scale, # Scale
162
- 'NONE' => :no_order # Used to indicate no hedge in :delta_neutral_order_type
162
+ 'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
163
+ 'None' => :none, # Used to indicate no hedge in :delta_neutral_order_type
163
164
  }.freeze
164
165
 
165
166
  # Valid security types (sec_type attribute of IB::Contract)
@@ -3,8 +3,10 @@ require "logger"
3
3
  # Add default_logger accessor into Object
4
4
  def default_logger
5
5
  @@default_logger ||= Logger.new(STDOUT).tap do |logger|
6
+ time_format = RUBY_VERSION =~ /1\.8\./ ? '%H:%M:%S.%N' : '%H:%M:%S.%3N'
6
7
  logger.formatter = proc do |level, time, prog, msg|
7
- "#{time.strftime('%H:%M:%S.%N')} #{msg}\n"
8
+
9
+ "#{time.strftime(time_format)} #{msg}\n"
8
10
  end
9
11
  logger.level = Logger::INFO
10
12
  end
@@ -3,7 +3,7 @@ module IB
3
3
  # This gem supports incoming/outgoing IB messages compatible with the following
4
4
  # IB client/server versions:
5
5
  CLIENT_VERSION = 59 # 59? Maximal client version implemented
6
- SERVER_VERSION = 60 # 38? 53? 62? Minimal server version required
6
+ SERVER_VERSION = 62 # 38? 53? 62? Minimal server version required
7
7
  end
8
8
  end
9
9
 
@@ -44,10 +44,10 @@ __END__
44
44
  // 29 = can receive trail stop limit price in open order and can place them: API 8.91
45
45
  // 30 = can receive extended bond contract def, new ticks, and trade count in bars
46
46
  // 31 = can receive EFP extensions to scanner and market data, and combo legs on open orders
47
- // ; can receive RT bars
47
+ // ; can receive RT bars
48
48
  // 32 = can receive TickType.LAST_TIMESTAMP
49
- // ; can receive "whyHeld" in order status messages
50
- // 33 = can receive ScaleNumComponents and ScaleComponentSize is open order messages
49
+ // ; can receive "whyHeld" in order status messages
50
+ // 33 = can receive ScaleNumComponents and ScaleComponentSize is open order messages
51
51
  // 34 = can receive whatIf orders / order state
52
52
  // 35 = can receive contId field for Contract objects
53
53
  // 36 = can receive outsideRth field for Order objects
@@ -78,7 +78,7 @@ __END__
78
78
  // 51 = can receive smartComboRoutingParams in openOrder
79
79
  // 52 = can receive deltaNeutralConId, deltaNeutralSettlingFirm, deltaNeutralClearingAccount and deltaNeutralClearingIntent in openOrder
80
80
  // 53 = can receive orderRef in execution
81
- // 54 = can receive scale order fields (PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset,
81
+ // 54 = can receive scale order fields (PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset,
82
82
  // InitPosition, InitFillQty and RandomPercent) in openOrder
83
83
  // 55 = can receive orderComboLegs (price) in openOrder
84
84
  // 56 = can receive trailingPercent in openOrder
@@ -9,6 +9,8 @@ module IB
9
9
 
10
10
  class AbstractMessage < IB::Messages::AbstractMessage
11
11
 
12
+ attr_accessor :socket
13
+
12
14
  def version # Per message, received messages may have the different versions
13
15
  @data[:version]
14
16
  end
@@ -19,36 +21,33 @@ module IB
19
21
  end
20
22
  end
21
23
 
22
- # Create incoming message from a given source (IB server or data Hash)
24
+ # Create incoming message from a given source (IB Socket or data Hash)
23
25
  def initialize source
24
26
  @created_at = Time.now
25
- if source[:socket] # Source is a server
26
- @server = source
27
- @data = Hash.new
28
- begin
29
- self.load
30
- rescue => e
31
- error "Reading #{self.class}: #{e.class}: #{e.message}", :load, e.backtrace
32
- ensure
33
- @server = nil
34
- end
35
- else # Source is a @data Hash
27
+ if source.is_a?(Hash) # Source is a @data Hash
36
28
  @data = source
29
+ else # Source is a Socket
30
+ @socket = source
31
+ @data = Hash.new
32
+ self.load
37
33
  end
38
34
  end
39
35
 
40
- def socket
41
- @server[:socket]
42
- end
43
-
44
36
  # Every message loads received message version first
45
37
  # Override the load method in your subclass to do actual reading into @data.
46
38
  def load
47
- @data[:version] = socket.read_int
39
+ if socket
40
+ @data[:version] = socket.read_int
41
+
42
+ check_version @data[:version], self.class.version
48
43
 
49
- check_version @data[:version], self.class.version
44
+ load_map *self.class.data_map
45
+ else
46
+ raise "Unable to load, no socket"
47
+ end
50
48
 
51
- load_map *self.class.data_map
49
+ rescue => e
50
+ error "Reading #{self.class}: #{e.class}: #{e.message}", :load, e.backtrace
52
51
  end
53
52
 
54
53
  # Load @data from the socket according to the given data map.
@@ -62,34 +61,34 @@ module IB
62
61
  # We determine the function of the first element
63
62
  head = instruction.first
64
63
  case head
65
- when Integer # >= Version condition: [ min_version, [map]]
66
- load_map *instruction.drop(1) if version >= head
67
-
68
- when Proc # Callable condition: [ condition, [map]]
69
- load_map *instruction.drop(1) if head.call
70
-
71
- when true # Pre-condition already succeeded!
72
- load_map *instruction.drop(1)
73
-
74
- when nil, false # Pre-condition already failed! Do nothing...
75
-
76
- when Symbol # Normal map
77
- group, name, type, block =
78
- if instruction[2].nil? || instruction[2].is_a?(Proc)
79
- [nil] + instruction # No group, [ :name, :type, (:block) ]
80
- else
81
- instruction # [ :group, :name, :type, (:block)]
82
- end
83
-
84
- data = socket.__send__("read_#{type}", &block)
85
- if group
86
- @data[group] ||= {}
87
- @data[group][name] = data
88
- else
89
- @data[name] = data
90
- end
64
+ when Integer # >= Version condition: [ min_version, [map]]
65
+ load_map *instruction.drop(1) if version >= head
66
+
67
+ when Proc # Callable condition: [ condition, [map]]
68
+ load_map *instruction.drop(1) if head.call
69
+
70
+ when true # Pre-condition already succeeded!
71
+ load_map *instruction.drop(1)
72
+
73
+ when nil, false # Pre-condition already failed! Do nothing...
74
+
75
+ when Symbol # Normal map
76
+ group, name, type, block =
77
+ if instruction[2].nil? || instruction[2].is_a?(Proc)
78
+ [nil] + instruction # No group, [ :name, :type, (:block) ]
79
+ else
80
+ instruction # [ :group, :name, :type, (:block)]
81
+ end
82
+
83
+ data = socket.__send__("read_#{type}", &block)
84
+ if group
85
+ @data[group] ||= {}
86
+ @data[group][name] = data
91
87
  else
92
- error "Unrecognized instruction #{instruction}"
88
+ @data[name] = data
89
+ end
90
+ else
91
+ error "Unrecognized instruction #{instruction}"
93
92
  end
94
93
  end
95
94
  end
@@ -74,7 +74,19 @@ module IB
74
74
 
75
75
  # Accessors to make OpenOrder API-compatible with OrderStatus message
76
76
 
77
- def local_id
77
+ def client_id
78
+ order.client_id
79
+ end
80
+
81
+ def parent_id
82
+ order.parent_id
83
+ end
84
+
85
+ def perm_id
86
+ order.perm_id
87
+ end
88
+
89
+ def local_id
78
90
  order.local_id
79
91
  end
80
92
 
@@ -84,6 +96,8 @@ module IB
84
96
  order.status
85
97
  end
86
98
 
99
+ # Object accessors
100
+
87
101
  def order
88
102
  @order ||= IB::Order.new @data[:order].merge(:order_state => order_state)
89
103
  end
@@ -49,6 +49,18 @@ module IB
49
49
  end
50
50
 
51
51
  # Accessors to make OpenOrder and OrderStatus messages API-compatible
52
+ def client_id
53
+ order_state.client_id
54
+ end
55
+
56
+ def parent_id
57
+ order_state.parent_id
58
+ end
59
+
60
+ def perm_id
61
+ order_state.perm_id
62
+ end
63
+
52
64
  def local_id
53
65
  order_state.local_id
54
66
  end
@@ -23,37 +23,48 @@ module IB
23
23
  # an Array of elements that ought to be sent to the server by calling to_s on
24
24
  # each one and postpending a '\0'.
25
25
  #
26
- def send_to server
27
- self.encode(server).flatten.each do |datum|
28
- #p datum
29
- server[:socket].write_data datum
30
- end
26
+ def send_to socket
27
+ self.preprocess.each {|data| socket.write_data data}
31
28
  end
32
29
 
33
- # At minimum, Outgoing message contains message_id and version.
30
+ # Same message representation as logged by TWS into API messages log file
31
+ def to_s
32
+ self.preprocess.join('-')
33
+ end
34
+
35
+ # Pre-process encoded message Array before sending into socket, such as
36
+ # changing booleans into 0/1 and stuff
37
+ def preprocess
38
+ self.encode.flatten.map {|data| data == true ? 1 : data == false ? 0 : data }
39
+ end
40
+
41
+ # Encode message content into (possibly, nested) Array of values.
42
+ # At minimum, encoded Outgoing message contains message_id and version.
34
43
  # Most messages also contain (ticker, request or order) :id.
35
44
  # Then, content of @data Hash is encoded per instructions in data_map.
36
- def encode server
45
+ # This method may be modified by message subclasses!
46
+ def encode
37
47
  [self.class.message_id,
38
48
  self.class.version,
39
49
  @data[:id] || @data[:ticker_id] || @data[:request_id] ||
40
- @data[:local_id] || @data[:order_id] || [],
50
+ @data[:local_id] || @data[:order_id] || [],
41
51
  self.class.data_map.map do |(field, default_method, args)|
42
52
  case
43
- when default_method.nil?
44
- @data[field]
53
+ when default_method.nil?
54
+ @data[field]
45
55
 
46
- when default_method.is_a?(Symbol) # method name with args
47
- @data[field].send default_method, *args
56
+ when default_method.is_a?(Symbol) # method name with args
57
+ @data[field].send default_method, *args
48
58
 
49
- when default_method.respond_to?(:call) # callable with args
50
- default_method.call @data[field], *args
59
+ when default_method.respond_to?(:call) # callable with args
60
+ default_method.call @data[field], *args
51
61
 
52
- else # default
53
- @data[field].nil? ? default_method : @data[field] # may be false still
62
+ else # default
63
+ @data[field].nil? ? default_method : @data[field] # may be false still
54
64
  end
55
65
  end
56
- ].flatten
66
+ ]
67
+ # TWS wants to receive booleans as 1 or 0
57
68
  end
58
69
 
59
70
  end # AbstractMessage
@@ -44,14 +44,14 @@ module IB
44
44
  RequestRealTimeBars = def_message 50, BarRequestMessage
45
45
 
46
46
  class RequestRealTimeBars
47
- def encode server
47
+ def encode
48
48
  data_type, bar_size, contract = parse @data
49
49
 
50
50
  [super,
51
51
  contract.serialize_long,
52
52
  bar_size,
53
53
  data_type.to_s.upcase,
54
- @data[:use_rth]].flatten
54
+ @data[:use_rth]]
55
55
  end
56
56
  end # RequestRealTimeBars
57
57
 
@@ -95,11 +95,32 @@ module IB
95
95
  # even if the time span requested falls partially or completely
96
96
  # outside of them.
97
97
  # :format_date => int: 1 - text format, like "20050307 11:32:16".
98
- # 2 - offset in seconds from the beginning of 1970,
99
- # which is the same format as the UNIX epoch time.
98
+ # 2 - offset from 1970-01-01 in sec (UNIX epoch)
100
99
  # }
101
100
  #
102
- # Note that as of 4/07 there is no historical data available for forex spot.
101
+ # NB: using the D :duration only returns bars in whole days, so requesting "1 D"
102
+ # for contract ending at 08:05 will only return 1 bar, for 08:00 on that day.
103
+ # But requesting "86400 S" gives 86400/barlengthsecs bars before the end Time.
104
+ #
105
+ # Note also that the :duration for any request must be such that the start Time is not
106
+ # more than one year before the CURRENT-Time-less-one-day (not 1 year before the end
107
+ # Time in the Request)
108
+ #
109
+ # Bar Size Max Duration
110
+ # -------- ------------
111
+ # 1 sec 2000 S
112
+ # 5 sec 10000 S
113
+ # 15 sec 30000 S
114
+ # 30 sec 86400 S
115
+ # 1 minute 86400 S, 6 D
116
+ # 2 minutes 86400 S, 6 D
117
+ # 5 minutes 86400 S, 6 D
118
+ # 15 minutes 86400 S, 6 D, 20 D, 2 W
119
+ # 30 minutes 86400 S, 34 D, 4 W, 1 M
120
+ # 1 hour 86400 S, 34 D, 4 w, 1 M
121
+ # 1 day 60 D, 12 M, 52 W, 1 Y
122
+ #
123
+ # NB: as of 4/07 there is no historical data available for forex spot.
103
124
  #
104
125
  # data[:contract] may either be a Contract object or a String. A String should be
105
126
  # in serialize_ib_ruby format; that is, it should be a colon-delimited string in
@@ -129,7 +150,7 @@ module IB
129
150
  RequestHistoricalData = def_message [20, 4], BarRequestMessage
130
151
 
131
152
  class RequestHistoricalData
132
- def encode server
153
+ def encode
133
154
  data_type, bar_size, contract = parse @data
134
155
 
135
156
  [super,
@@ -140,7 +161,7 @@ module IB
140
161
  @data[:use_rth],
141
162
  data_type.to_s.upcase,
142
163
  @data[:format_date],
143
- contract.serialize_legs].flatten
164
+ contract.serialize_legs]
144
165
  end
145
166
  end # RequestHistoricalData
146
167
 
@@ -5,13 +5,11 @@ module IB
5
5
  # Data format is { :id => int: local_id,
6
6
  # :contract => Contract,
7
7
  # :order => Order }
8
- PlaceOrder = def_message [3, 38] # v.38 is NOT properly supported by API yet ?!
8
+ PlaceOrder = def_message [3, 38]
9
9
 
10
10
  class PlaceOrder
11
11
 
12
- def encode server
13
- # Old server version supports no enhancements
14
- #@version = 31 if server[:server_version] <= 60
12
+ def encode
15
13
 
16
14
  order = @data[:order]
17
15
  contract = @data[:contract]
@@ -21,14 +19,7 @@ module IB
21
19
  contract.serialize_long(:con_id, :sec_id),
22
20
 
23
21
  # main order fields
24
- case order.side
25
- when :short
26
- 'SSHORT'
27
- when :short_exempt
28
- 'SSHORTX'
29
- else
30
- order.side.to_sup
31
- end,
22
+ (order.side == :short ? 'SSHORT' : order.side == :short_exempt ? 'SSHORTX' : order.side.to_sup),
32
23
  order.quantity,
33
24
  order[:order_type], # Internal code, 'LMT' instead of :limit
34
25
  order.limit_price,
@@ -49,18 +40,14 @@ module IB
49
40
  order.hidden || false,
50
41
  contract.serialize_legs(:extended),
51
42
 
52
- # This is specific to PlaceOrder v.38, NOT supported by API yet!
53
- ## Support for per-leg prices in Order
54
- if server[:server_version] >= 61 && contract.bag?
55
- #order.leg_prices.empty? ? 0 : [order.leg_prices.size] + order.leg_prices
56
- [contract.legs.size] + contract.legs.map { |_| nil }
57
- else
58
- []
59
- end,
60
43
 
61
- ## Support for combo routing params in Order
62
- if server[:server_version] >= 57 && contract.bag?
63
- order.combo_params.empty? ? 0 : [order.combo_params.size] + order.combo_params.to_a
44
+ if contract.bag?
45
+ [
46
+ ## Support for per-leg prices in Order
47
+ [contract.legs.size] + contract.legs.map { |_| nil },
48
+ ## Support for combo routing params in Order
49
+ order.combo_params.empty? ? 0 : [order.combo_params.size] + order.combo_params.to_a
50
+ ]
64
51
  else
65
52
  []
66
53
  end,
@@ -77,7 +64,7 @@ module IB
77
64
  order.designated_location, # only populate when short_sale_slot == 2 (Institutional)
78
65
  order.exempt_code,
79
66
  order[:oca_type],
80
- order.rule_80a,
67
+ order[:rule_80a], #.to_sup[0..0],
81
68
  order.settling_firm,
82
69
  order.all_or_none || false,
83
70
  order.min_quantity,
@@ -92,37 +79,34 @@ module IB
92
79
  order.stock_range_lower,
93
80
  order.stock_range_upper,
94
81
  order.override_percentage_constraints || false,
95
- order.volatility, # Volatility orders
96
- order[:volatility_type], #
97
- order[:delta_neutral_order_type],
98
- order.delta_neutral_aux_price, #
82
+ order.volatility, # Volatility orders
83
+ order[:volatility_type], # Volatility orders
99
84
 
100
85
  # Support for delta neutral orders with parameters
101
- if server[:server_version] >= 58 && order.delta_neutral_order_type
102
- [order.delta_neutral_con_id,
86
+ if order.delta_neutral_order_type && order.delta_neutral_order_type != :none
87
+ [order[:delta_neutral_order_type],
88
+ order.delta_neutral_aux_price,
89
+ order.delta_neutral_con_id,
103
90
  order.delta_neutral_settling_firm,
104
91
  order.delta_neutral_clearing_account,
105
92
  order[:delta_neutral_clearing_intent]
106
- ]
93
+ ]
107
94
  else
108
- []
95
+ ['', '']
109
96
  end,
110
97
 
111
- order.continuous_update, # Volatility orders
112
- order[:reference_price_type], # Volatility orders
98
+ order.continuous_update, # Volatility orders
99
+ order[:reference_price_type], # Volatility orders
113
100
 
114
101
  order.trail_stop_price, # TRAIL_STOP_LIMIT stop price
115
-
116
- # Support for trailing percent
117
- server[:server_version] >= 62 ? order.trailing_percent : [],
102
+ order.trailing_percent, # Support for trailing percent
118
103
 
119
104
  order.scale_init_level_size, # Scale Orders
120
105
  order.scale_subs_level_size, # Scale Orders
121
106
  order.scale_price_increment, # Scale Orders
122
107
 
123
- # Support extended scale orders parameters
124
- if server[:server_version] >= 60 && # MIN_SERVER_VER_SCALE_ORDERS3
125
- order.scale_price_increment && order.scale_price_increment > 0
108
+ # Support for extended scale orders parameters
109
+ if order.scale_price_increment && order.scale_price_increment > 0
126
110
  [order.scale_price_adjust_value,
127
111
  order.scale_price_adjust_interval,
128
112
  order.scale_profit_offset,
@@ -130,17 +114,16 @@ module IB
130
114
  order.scale_init_position,
131
115
  order.scale_init_fill_qty,
132
116
  order.scale_random_percent || false
133
- ]
117
+ ]
134
118
  else
135
119
  []
136
120
  end,
137
121
 
138
- # TODO: Need to add support for hedgeType, not working ATM - beta only
139
- # MIN_SERVER_VER_HEDGE_ORDERS
140
- server[:server_version] >= 54 ? [order.hedge_type, order.hedge_param || []] : [],
122
+ # Support for hedgeType
123
+ order.hedge_type, # MIN_SERVER_VER_HEDGE_ORDERS
124
+ order.hedge_param || [],
141
125
 
142
- #MIN_SERVER_VER_OPT_OUT_SMART_ROUTING
143
- server[:server_version] >= 56 ? (order.opt_out_smart_routing || false) : [],
126
+ order.opt_out_smart_routing || false, # MIN_SERVER_VER_OPT_OUT_SMART_ROUTING
144
127
 
145
128
  order.clearing_account,
146
129
  order.clearing_intent,
@@ -3,13 +3,8 @@ require 'socket'
3
3
  module IB
4
4
  class IBSocket < TCPSocket
5
5
 
6
- # send nice null terminated binary data into socket
6
+ # Sends null terminated data string into socket
7
7
  def write_data data
8
- # TWS wants to receive booleans as 1 or 0
9
- data = "1" if data == true
10
- data = "0" if data == false
11
-
12
- #p data.to_s + EOL
13
8
  self.syswrite(data.to_s + EOL)
14
9
  end
15
10
 
@@ -1,12 +1,12 @@
1
- # The Futures module tries to guess the front month future using a crude algorithm that
2
- # does not take into account expiry/rollover day. This will be valid most of the time,
1
+ # The Futures module tries to guess the front month future using a crude algorithm that
2
+ # does not take into account expiry/rollover day. This will be valid most of the time,
3
3
  # but near/after expiry day the next quarter's contract takes over as the volume leader.
4
4
 
5
5
  module IB
6
6
  module Symbols
7
7
  module Futures
8
8
  extend Symbols
9
-
9
+
10
10
  # Find the next front month of quarterly futures.
11
11
  # N.B. This will not work as expected during the front month before expiration, as
12
12
  # it will point to the next quarter even though the current month is still valid!
@@ -62,7 +62,7 @@ module IB
62
62
  :exchange => "ECBOT",
63
63
  :currency => "USD",
64
64
  :sec_type => :future,
65
- :description => "Mini Dow Jones Industrial"),
65
+ :description => "Mini-DJIA future"),
66
66
 
67
67
  :es => IB::Contract.new(:symbol => "ES",
68
68
  :expiry => next_expiry,
@@ -70,7 +70,7 @@ module IB
70
70
  :currency => "USD",
71
71
  :sec_type => :future,
72
72
  :multiplier => 50,
73
- :description => "E-Mini S&P 500"),
73
+ :description => "E-Mini S&P 500 future"),
74
74
 
75
75
  :gbp => IB::Contract.new(:symbol => "GBP",
76
76
  :expiry => next_expiry,
@@ -78,7 +78,7 @@ module IB
78
78
  :currency => "USD",
79
79
  :sec_type => :future,
80
80
  :multiplier => 62500,
81
- :description => "British Pounds"),
81
+ :description => "British Pounds future"),
82
82
 
83
83
  :eur => IB::Contract.new(:symbol => "EUR",
84
84
  :expiry => next_expiry,
@@ -86,7 +86,7 @@ module IB
86
86
  :currency => "USD",
87
87
  :sec_type => :future,
88
88
  :multiplier => 12500,
89
- :description => "Euro FX"),
89
+ :description => "Euro FX future"),
90
90
 
91
91
  :jpy => IB::Contract.new(:symbol => "JPY",
92
92
  :expiry => next_expiry,
@@ -94,7 +94,7 @@ module IB
94
94
  :currency => "USD",
95
95
  :sec_type => :future,
96
96
  :multiplier => 12500000,
97
- :description => "Japanese Yen"),
97
+ :description => "Japanese Yen future"),
98
98
 
99
99
  :hsi => IB::Contract.new(:symbol => "HSI",
100
100
  :expiry => next_expiry,
@@ -102,7 +102,14 @@ module IB
102
102
  :currency => "HKD",
103
103
  :sec_type => :future,
104
104
  :multiplier => 50,
105
- :description => "Hang Seng Index")
105
+ :description => "Hang Seng Index future"),
106
+
107
+ :vix => IB::Contract.new(:symbol => "VIX",
108
+ :expiry => next_expiry,
109
+ :exchange => "CFE", #"ECBOT",
110
+ # :currency => "USD",
111
+ :sec_type => :future,
112
+ :description => "CBOE Volatility Index future")
106
113
  }
107
114
  end
108
115
  end
@@ -1,4 +1,4 @@
1
- # Option contracts definitions.
1
+ # Option contracts definitions.
2
2
  # TODO: add next_expiry and other convenience from Futures module.
3
3
  module IB
4
4
  module Symbols
@@ -30,6 +30,8 @@ module IB
30
30
  :strike => 75.0,
31
31
  :description => "SPY 75.0 Put 2014-01"),
32
32
  :spy100 => IB::Option.new(:osi => 'SPY 140118P00100000'),
33
+ :vix20 => IB::Option.new(:osi => 'VIX 121121C00020000'),
34
+ :vxx40 => IB::Option.new(:osi => 'VXX 121117C00040000'),
33
35
  }
34
36
  end
35
37
  end
@@ -7,17 +7,23 @@ module IB
7
7
 
8
8
  def self.contracts
9
9
  @contracts ||= {
10
+ :aapl => IB::Contract.new(:symbol => "AAPL",
11
+ :currency => "USD",
12
+ :sec_type => :stock,
13
+ :description => "Apple Inc."),
14
+
15
+ :vxx => IB::Contract.new(:symbol => "VXX",
16
+ :exchange => "ARCA",
17
+ # :currency => "USD",
18
+ :sec_type => :stock,
19
+ :description => "iPath S&P500 VIX short term Futures ETN"),
20
+
10
21
  :wfc => IB::Contract.new(:symbol => "WFC",
11
22
  :exchange => "NYSE",
12
23
  :currency => "USD",
13
24
  :sec_type => :stock,
14
25
  :description => "Wells Fargo"),
15
26
 
16
- :aapl => IB::Contract.new(:symbol => "AAPL",
17
- :currency => "USD",
18
- :sec_type => :stock,
19
- :description => "Apple Inc."),
20
-
21
27
  :wrong => IB::Contract.new(:symbol => "QEEUUE",
22
28
  :exchange => "NYSE",
23
29
  :currency => "USD",
@@ -329,7 +329,7 @@ module IB
329
329
  # Placement
330
330
  def place contract, connection
331
331
  error "Unable to place order, next_local_id not known" unless connection.next_local_id
332
- self.client_id = connection.server[:client_id]
332
+ self.client_id = connection.client_id
333
333
  self.local_id = connection.next_local_id
334
334
  connection.next_local_id += 1
335
335
  self.placed_at = Time.now
@@ -381,7 +381,7 @@ module IB
381
381
  def to_human
382
382
  "<Order: " + ((order_ref && order_ref != '') ? "#{order_ref} " : '') +
383
383
  "#{self[:order_type]} #{self[:tif]} #{side} #{quantity} " +
384
- "#{status} " + (limit_price ? "#{limit_price} " : '') +
384
+ (limit_price ? "#{limit_price} " : '') + "#{status} " +
385
385
  ((aux_price && aux_price != 0) ? "/#{aux_price}" : '') +
386
386
  "##{local_id}/#{perm_id} from #{client_id}" +
387
387
  (account ? "/#{account}" : '') +
@@ -17,18 +17,11 @@ def verify_account
17
17
 
18
18
  @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
19
19
 
20
- received =
21
- if @ib.server[:server_version] <= 60
22
- @ib.send_message :RequestAccountData, :subscribe => true
23
- @ib.wait_for :AccountValue, 5
24
- raise "Unable to verify IB PAPER ACCOUNT" unless @ib.received? :AccountValue
25
- p @ib.received[:AccountValue].first
26
- @ib.received[:AccountValue].first.account_name
27
- else
28
- @ib.wait_for :ManagedAccounts, 5
29
- raise "Unable to verify IB PAPER ACCOUNT" unless @ib.received?(:ManagedAccounts)
30
- @ib.received[:ManagedAccounts].first.accounts_list
31
- end
20
+ @ib.wait_for :ManagedAccounts, 5
21
+
22
+ raise "Unable to verify IB PAPER ACCOUNT" unless @ib.received?(:ManagedAccounts)
23
+
24
+ received = @ib.received[:ManagedAccounts].first.accounts_list
32
25
 
33
26
  raise "Connected to wrong account #{received}, expected #{account}" if account != received
34
27
 
@@ -87,4 +80,3 @@ shared_examples_for 'Valid account data request' do
87
80
  its(:to_human) { should =~ /AccountDownloadEnd/ }
88
81
  end
89
82
  end
90
-
@@ -18,8 +18,9 @@ shared_examples_for 'Connected Connection without receiver' do
18
18
 
19
19
  it { should_not be_nil }
20
20
  it { should be_connected }
21
- its(:server) { should be_a Hash }
22
- its(:server) { should have_key :reader }
21
+ its(:reader) { should be_a Thread }
22
+ its(:server_version) { should be_an Integer }
23
+ its(:client_version) { should be_an Integer }
23
24
  its(:subscribers) { should have_at_least(1).item } # :NextValidId and empty Hashes
24
25
  its(:next_local_id) { should be_a Fixnum } # Not before :NextValidId arrives
25
26
  end
@@ -188,8 +189,9 @@ describe IB::Connection do
188
189
 
189
190
  it { should_not be_nil }
190
191
  it { should_not be_connected }
191
- its(:server) { should be_a Hash }
192
- its(:server) { should_not have_key :reader }
192
+ its(:reader) { should be_nil }
193
+ its(:server_version) { should be_nil }
194
+ its(:client_version) { should be_nil }
193
195
  its(:received) { should be_empty }
194
196
  its(:subscribers) { should be_empty }
195
197
  its(:next_local_id) { should be_nil }
@@ -215,8 +217,9 @@ describe IB::Connection do
215
217
 
216
218
  it { should_not be_nil }
217
219
  it { should_not be_connected }
218
- its(:server) { should be_a Hash }
219
- its(:server) { should_not have_key :reader }
220
+ its(:reader) { should be_nil }
221
+ its(:server_version) { should be_nil }
222
+ its(:client_version) { should be_nil }
220
223
  its(:received) { should be_empty }
221
224
  its(:subscribers) { should be_empty }
222
225
  its(:next_local_id) { should be_nil }
@@ -9,7 +9,7 @@ shared_examples_for 'OpenOrder message' do
9
9
  its(:local_id) { should be_an Integer }
10
10
  its(:status) { should =~ /Submit/ }
11
11
  its(:to_human) { should =~
12
- /<OpenOrder: <Contract: WFC stock NYSE USD> <Order: LMT DAY buy 100 .*Submit.* 9.13 #\d+\/\d+ from 1111/ }
12
+ /<OpenOrder: <Contract: WFC stock NYSE USD> <Order: LMT DAY buy 100 9.13 .*Submit.* #\d+\/\d+ from 1111/ }
13
13
 
14
14
  it 'has proper contract accessor' do
15
15
  c = subject.contract
@@ -6,8 +6,8 @@ describe IB::Messages::Outgoing do
6
6
 
7
7
  subject do
8
8
  IB::Messages::Outgoing::RequestAccountData.new(
9
- :subscribe => true,
10
- :account_code => 'DUH')
9
+ :subscribe => true,
10
+ :account_code => 'DUH')
11
11
  end
12
12
 
13
13
  it { should be_an IB::Messages::Outgoing::RequestAccountData }
@@ -25,7 +25,15 @@ describe IB::Messages::Outgoing do
25
25
  end
26
26
 
27
27
  it 'encodes into Array' do
28
- subject.encode(:server_version => 60).should == [6, 2, true, "DUH"]
28
+ subject.encode.should == [6, 2, [], [true, "DUH"]]
29
+ end
30
+
31
+ it 'that is flattened before sending it over socket to IB server' do
32
+ subject.preprocess.should == [6, 2, 1, "DUH"]
33
+ end
34
+
35
+ it 'and has correct #to_s representation' do
36
+ subject.to_s.should == "6-2-1-DUH"
29
37
  end
30
38
 
31
39
  end
@@ -42,7 +42,7 @@ describe IB::Symbols do
42
42
  fx.currency.should == "USD"
43
43
  fx.multiplier.should == 62500
44
44
  fx.exchange.should == "GLOBEX"
45
- fx.description.should == "British Pounds"
45
+ fx.description.should =~ /British Pounds/
46
46
  end
47
47
 
48
48
  it 'raises an error if requested contract symbol is not defined' do
@@ -25,7 +25,7 @@ describe IB::Order,
25
25
  :not_held => true},
26
26
 
27
27
  # TODO: :presents => { Object => "Formatted"}
28
- :human => "<Order: Test MIT GTC buy 100 New 0.1 #23/173276893 from 1111>",
28
+ :human => "<Order: Test MIT GTC buy 100 0.1 New #23/173276893 from 1111>",
29
29
 
30
30
  :errors => {:side =>["should be buy/sell/short"]},
31
31
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ib-ruby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.9.0
5
+ version: 0.9.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Paul Legato
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-10-31 00:00:00.000000000 Z
13
+ date: 2012-11-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler