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 +1 -1
- data/lib/ib/base_properties.rb +2 -2
- data/lib/ib/connection.rb +68 -66
- data/lib/ib/constants.rb +2 -1
- data/lib/ib/logger.rb +3 -1
- data/lib/ib/messages.rb +5 -5
- data/lib/ib/messages/incoming/abstract_message.rb +45 -46
- data/lib/ib/messages/incoming/open_order.rb +15 -1
- data/lib/ib/messages/incoming/order_status.rb +12 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +28 -17
- data/lib/ib/messages/outgoing/bar_requests.rb +28 -7
- data/lib/ib/messages/outgoing/place_order.rb +29 -46
- data/lib/ib/socket.rb +1 -6
- data/lib/ib/symbols/futures.rb +16 -9
- data/lib/ib/symbols/options.rb +3 -1
- data/lib/ib/symbols/stocks.rb +11 -5
- data/lib/models/ib/order.rb +2 -2
- data/spec/account_helper.rb +5 -13
- data/spec/ib/connection_spec.rb +9 -6
- data/spec/ib/messages/incoming/open_order_spec.rb +1 -1
- data/spec/ib/messages/outgoing/account_data_spec.rb +11 -3
- data/spec/ib/symbols/symbols_spec.rb +1 -1
- data/spec/models/ib/order_spec.rb +1 -1
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.1
|
data/lib/ib/base_properties.rb
CHANGED
@@ -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
|
|
data/lib/ib/connection.rb
CHANGED
@@ -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 :
|
32
|
-
|
33
|
-
|
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
|
-
|
70
|
+
@socket = IBSocket.open(options[:host], options[:port])
|
66
71
|
|
67
72
|
# Secret handshake
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if
|
72
|
-
error "Server version #{
|
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
|
-
|
75
|
-
|
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
|
-
|
81
|
-
socket.write_data
|
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: #{
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
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
|
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
|
-
|
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 &&
|
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
|
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
|
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(
|
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
|
-
|
274
|
-
|
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
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
296
|
+
message.send_to socket
|
295
297
|
end
|
296
298
|
|
297
299
|
alias dispatch send_message # Legacy alias
|
data/lib/ib/constants.rb
CHANGED
@@ -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' => :
|
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)
|
data/lib/ib/logger.rb
CHANGED
@@ -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
|
-
|
8
|
+
|
9
|
+
"#{time.strftime(time_format)} #{msg}\n"
|
8
10
|
end
|
9
11
|
logger.level = Logger::INFO
|
10
12
|
end
|
data/lib/ib/messages.rb
CHANGED
@@ -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 =
|
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
|
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
|
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
|
-
|
39
|
+
if socket
|
40
|
+
@data[:version] = socket.read_int
|
41
|
+
|
42
|
+
check_version @data[:version], self.class.version
|
48
43
|
|
49
|
-
|
44
|
+
load_map *self.class.data_map
|
45
|
+
else
|
46
|
+
raise "Unable to load, no socket"
|
47
|
+
end
|
50
48
|
|
51
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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
|
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
|
27
|
-
self.
|
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
|
-
#
|
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
|
-
|
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
|
-
|
50
|
+
@data[:local_id] || @data[:order_id] || [],
|
41
51
|
self.class.data_map.map do |(field, default_method, args)|
|
42
52
|
case
|
43
|
-
|
44
|
-
|
53
|
+
when default_method.nil?
|
54
|
+
@data[field]
|
45
55
|
|
46
|
-
|
47
|
-
|
56
|
+
when default_method.is_a?(Symbol) # method name with args
|
57
|
+
@data[field].send default_method, *args
|
48
58
|
|
49
|
-
|
50
|
-
|
59
|
+
when default_method.respond_to?(:call) # callable with args
|
60
|
+
default_method.call @data[field], *args
|
51
61
|
|
52
|
-
|
53
|
-
|
62
|
+
else # default
|
63
|
+
@data[field].nil? ? default_method : @data[field] # may be false still
|
54
64
|
end
|
55
65
|
end
|
56
|
-
|
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
|
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]]
|
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
|
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
|
-
#
|
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
|
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]
|
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]
|
8
|
+
PlaceOrder = def_message [3, 38]
|
9
9
|
|
10
10
|
class PlaceOrder
|
11
11
|
|
12
|
-
def encode
|
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
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
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, #
|
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
|
102
|
-
[order
|
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, #
|
112
|
-
order[:reference_price_type], #
|
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
|
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
|
-
#
|
139
|
-
# MIN_SERVER_VER_HEDGE_ORDERS
|
140
|
-
|
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,
|
data/lib/ib/socket.rb
CHANGED
@@ -3,13 +3,8 @@ require 'socket'
|
|
3
3
|
module IB
|
4
4
|
class IBSocket < TCPSocket
|
5
5
|
|
6
|
-
#
|
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
|
|
data/lib/ib/symbols/futures.rb
CHANGED
@@ -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
|
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
|
data/lib/ib/symbols/options.rb
CHANGED
@@ -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
|
data/lib/ib/symbols/stocks.rb
CHANGED
@@ -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",
|
data/lib/models/ib/order.rb
CHANGED
@@ -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.
|
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
|
-
|
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}" : '') +
|
data/spec/account_helper.rb
CHANGED
@@ -17,18 +17,11 @@ def verify_account
|
|
17
17
|
|
18
18
|
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
data/spec/ib/connection_spec.rb
CHANGED
@@ -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(:
|
22
|
-
its(:
|
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(:
|
192
|
-
its(:
|
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(:
|
219
|
-
its(:
|
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.*
|
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
|
-
|
10
|
-
|
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
|
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
|
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
|
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.
|
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-
|
13
|
+
date: 2012-11-15 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|