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 +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
|