ib-ruby 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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