ib-ruby 0.5.19 → 0.5.21
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/TODO +0 -3
- data/VERSION +1 -1
- data/bin/contract_details +3 -3
- data/bin/depth_of_market +1 -1
- data/bin/historic_data +5 -8
- data/bin/market_data +2 -2
- data/bin/option_data +2 -2
- data/lib/ib-ruby/connection.rb +1 -9
- data/lib/ib-ruby/extensions.rb +8 -0
- data/lib/ib-ruby/messages/abstract_message.rb +89 -0
- data/lib/ib-ruby/messages/incoming.rb +415 -487
- data/lib/ib-ruby/messages/outgoing.rb +241 -305
- data/lib/ib-ruby/models/bar.rb +3 -3
- data/lib/ib-ruby/models/contract/bag.rb +1 -5
- data/lib/ib-ruby/models/contract.rb +50 -33
- data/lib/ib-ruby/models/execution.rb +6 -3
- data/lib/ib-ruby/models/order.rb +7 -5
- data/lib/ib-ruby/socket.rb +13 -0
- data/lib/ib-ruby/symbols/forex.rb +7 -14
- data/lib/ib-ruby/symbols/futures.rb +16 -20
- data/lib/ib-ruby/symbols/options.rb +6 -4
- data/lib/ib-ruby/symbols/stocks.rb +1 -1
- data/lib/ib-ruby.rb +1 -0
- data/spec/README.md +34 -0
- data/spec/ib-ruby/connection_spec.rb +4 -4
- data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
- data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
- data/spec/ib-ruby/models/contract_spec.rb +27 -25
- data/spec/ib-ruby/models/order_spec.rb +56 -23
- data/spec/integration/account_info_spec.rb +85 -0
- data/spec/integration/contract_info_spec.rb +209 -0
- data/spec/integration/depth_data_spec.rb +46 -0
- data/spec/integration/historic_data_spec.rb +82 -0
- data/spec/integration/market_data_spec.rb +97 -0
- data/spec/integration/option_data_spec.rb +96 -0
- data/spec/integration/orders/execution_spec.rb +135 -0
- data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
- data/spec/integration/orders/placement_spec.rb +150 -0
- data/spec/integration/orders/valid_ids_spec.rb +84 -0
- data/spec/integration_helper.rb +110 -0
- data/spec/message_helper.rb +13 -18
- data/spec/spec_helper.rb +35 -26
- metadata +33 -17
- data/spec/ib-ruby/messages/README.md +0 -16
- data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
- data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
- data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
- data/spec/ib-ruby/messages/orders_spec.rb +0 -219
- data/spec/ib-ruby_spec.rb +0 -0
@@ -133,11 +133,11 @@ module IB
|
|
133
133
|
@con_id = 0
|
134
134
|
@strike = 0
|
135
135
|
@sec_type = ''
|
136
|
+
@exchange = 'SMART'
|
136
137
|
@include_expired = false
|
137
138
|
@legs = Array.new
|
138
139
|
|
139
140
|
# These properties are from ContractDetails
|
140
|
-
@summary = self
|
141
141
|
@under_con_id = 0
|
142
142
|
@min_tick = 0
|
143
143
|
@callable = false
|
@@ -149,41 +149,55 @@ module IB
|
|
149
149
|
super opts
|
150
150
|
end
|
151
151
|
|
152
|
+
# This property is from ContractDetails
|
153
|
+
def summary
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
152
157
|
# some protective filters
|
153
|
-
def primary_exchange=
|
158
|
+
def primary_exchange= x
|
154
159
|
x.upcase! if x.is_a?(String)
|
155
160
|
|
156
161
|
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
157
|
-
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x ==
|
162
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == 'SMART'
|
158
163
|
|
159
164
|
@primary_exchange = x
|
160
165
|
end
|
161
166
|
|
162
|
-
def right=
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
167
|
+
def right= x
|
168
|
+
@right =
|
169
|
+
case x.to_s.upcase
|
170
|
+
when '', '0', '?'
|
171
|
+
nil
|
172
|
+
when 'PUT', 'P'
|
173
|
+
'PUT'
|
174
|
+
when 'CALL', 'C'
|
175
|
+
'CALL'
|
176
|
+
else
|
177
|
+
raise ArgumentError.new("Invalid right '#{x}' (must be one of PUT, CALL, P, C)")
|
178
|
+
end
|
168
179
|
end
|
169
180
|
|
170
|
-
def expiry=
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
181
|
+
def expiry= x
|
182
|
+
@expiry =
|
183
|
+
case x.to_s
|
184
|
+
when /\d{6,8}/
|
185
|
+
x.to_s
|
186
|
+
when ''
|
187
|
+
nil
|
188
|
+
else
|
189
|
+
raise ArgumentError.new("Invalid expiry '#{x}' (must be in format YYYYMM or YYYYMMDD)")
|
190
|
+
end
|
176
191
|
end
|
177
192
|
|
178
|
-
def sec_type=
|
193
|
+
def sec_type= x
|
179
194
|
x = nil if !x.nil? && x.empty?
|
180
|
-
raise(ArgumentError.new("Invalid security type
|
195
|
+
raise(ArgumentError.new("Invalid security type '#{x}' (must be one of #{SECURITY_TYPES.values}")) unless x.nil? || SECURITY_TYPES.values.include?(x)
|
181
196
|
@sec_type = x
|
182
197
|
end
|
183
198
|
|
184
|
-
def
|
185
|
-
@
|
186
|
-
@strike = 0
|
199
|
+
def multiplier= x
|
200
|
+
@multiplier = x.to_i
|
187
201
|
end
|
188
202
|
|
189
203
|
# This returns an Array of data from the given contract.
|
@@ -265,6 +279,8 @@ module IB
|
|
265
279
|
|
266
280
|
# Contract comparison
|
267
281
|
def == other
|
282
|
+
return false unless other.is_a?(self.class)
|
283
|
+
|
268
284
|
# Different sec_id_type
|
269
285
|
return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
|
270
286
|
|
@@ -274,6 +290,12 @@ module IB
|
|
274
290
|
# Different under_comp
|
275
291
|
return false if under_comp && other.under_comp && under_comp != other.under_comp
|
276
292
|
|
293
|
+
# Different symbols
|
294
|
+
return false if symbol && other.symbol && symbol != other.symbol
|
295
|
+
|
296
|
+
# Different currency
|
297
|
+
return false if currency && other.currency && currency != other.currency
|
298
|
+
|
277
299
|
# Different legs
|
278
300
|
return false unless same_legs? other
|
279
301
|
|
@@ -288,26 +310,21 @@ module IB
|
|
288
310
|
|
289
311
|
# Comparison for Bonds and Options
|
290
312
|
if sec_type == SECURITY_TYPES[:bond] || sec_type == SECURITY_TYPES[:option]
|
291
|
-
return false
|
292
|
-
|
293
|
-
|
294
|
-
|
313
|
+
return false if right != other.right || strike != other.strike
|
314
|
+
return false if multiplier && other.multiplier && multiplier != other.multiplier
|
315
|
+
return false if expiry[0..5] != other.expiry[0..5]
|
316
|
+
return false unless expiry[6..7] == other.expiry[6..7] ||
|
317
|
+
expiry[6..7].empty? || other.expiry[6..7].empty?
|
295
318
|
end
|
296
319
|
|
297
320
|
# All else being equal...
|
298
|
-
sec_type == other.sec_type
|
299
|
-
symbol == other.symbol &&
|
300
|
-
currency == other.currency
|
321
|
+
sec_type == other.sec_type
|
301
322
|
end
|
302
323
|
|
303
|
-
|
304
|
-
# TODO: Remove @summary into reader method
|
305
324
|
def to_s
|
306
325
|
"<Contract: " + instance_variables.map do |key|
|
307
|
-
|
308
|
-
|
309
|
-
" #{key}=#{value}" unless value.nil? || value == '' || value == 0
|
310
|
-
end
|
326
|
+
value = send(key[1..-1])
|
327
|
+
" #{key}=#{value}" unless value.nil? || value == '' || value == 0
|
311
328
|
end.compact.join(',') + " >"
|
312
329
|
end
|
313
330
|
|
@@ -10,7 +10,7 @@ module IB
|
|
10
10
|
# TWS orders have a fixed client id of 0.
|
11
11
|
:exec_id, # String: Unique order execution id.
|
12
12
|
:time, # String: The order execution time.
|
13
|
-
:
|
13
|
+
:account_name, #String: The customer account number.
|
14
14
|
:exchange, # String: Exchange that executed the order.
|
15
15
|
:side, # String: Was the transaction a buy or a sale: BOT|SLD
|
16
16
|
:shares, # int: The number of shares filled.
|
@@ -23,13 +23,16 @@ module IB
|
|
23
23
|
# trades, combo trades and legs of the combo
|
24
24
|
:average_price # double: Average price. Used in regular trades, combo
|
25
25
|
# trades and legs of the combo.
|
26
|
+
# Legacy
|
27
|
+
alias account_number account_name
|
28
|
+
alias account_number= account_name=
|
26
29
|
|
27
30
|
def side= value
|
28
31
|
@side = case value
|
29
32
|
when 'BOT'
|
30
|
-
:
|
33
|
+
:BUY
|
31
34
|
when 'SLD'
|
32
|
-
:
|
35
|
+
:SELL
|
33
36
|
else
|
34
37
|
value
|
35
38
|
end
|
data/lib/ib-ruby/models/order.rb
CHANGED
@@ -46,7 +46,7 @@ module IB
|
|
46
46
|
Max_Value = 99999999
|
47
47
|
|
48
48
|
# Main order fields
|
49
|
-
attr_accessor :
|
49
|
+
attr_accessor :order_id, # int: m_orderId? Order id associated with client (volatile).
|
50
50
|
:client_id, # int: The id of the client that placed this order.
|
51
51
|
:perm_id, # int: TWS id used to identify orders, remains
|
52
52
|
# the same over TWS sessions.
|
@@ -263,6 +263,7 @@ module IB
|
|
263
263
|
|
264
264
|
# Some Order properties (received back from IB) are separated into
|
265
265
|
# OrderState object. Here, they are lumped into Order proper: see OrderState.java
|
266
|
+
# TODO: Extract OrderState object, for better record keeping
|
266
267
|
attr_accessor :status, # String: Displays the order status.Possible values include:
|
267
268
|
# � PendingSubmit - indicates that you have transmitted the order, but
|
268
269
|
# have not yet received confirmation that it has been accepted by the
|
@@ -304,8 +305,8 @@ module IB
|
|
304
305
|
|
305
306
|
:warning_text # String: Displays a warning message if warranted.
|
306
307
|
|
307
|
-
alias order_id id # TODO: Change due to ActiveRecord specifics
|
308
|
-
alias order_id= id= # TODO: Change due to ActiveRecord specifics
|
308
|
+
#alias order_id id # TODO: Change due to ActiveRecord specifics
|
309
|
+
#alias order_id= id= # TODO: Change due to ActiveRecord specifics
|
309
310
|
|
310
311
|
# IB uses weird String with Java Double.MAX_VALUE to indicate no value here
|
311
312
|
def init_margin= val
|
@@ -438,7 +439,7 @@ module IB
|
|
438
439
|
|
439
440
|
# Order comparison
|
440
441
|
def == other
|
441
|
-
perm_id == other.perm_id ||
|
442
|
+
perm_id && perm_id == other.perm_id ||
|
442
443
|
order_id == other.order_id && # ((p __LINE__)||true) &&
|
443
444
|
client_id == other.client_id &&
|
444
445
|
parent_id == other.parent_id &&
|
@@ -480,7 +481,8 @@ module IB
|
|
480
481
|
|
481
482
|
def to_human
|
482
483
|
"<Order: #{order_type} #{tif} #{action} #{total_quantity} #{status} #{limit_price}" +
|
483
|
-
" id: #{order_id}/#{perm_id} from: #{client_id}/#{account}
|
484
|
+
" id: #{order_id}/#{perm_id} from: #{client_id}/#{account}" +
|
485
|
+
(commission ? " fee: #{commission}" : "") + ">"
|
484
486
|
end
|
485
487
|
end # class Order
|
486
488
|
end # module Models
|
data/lib/ib-ruby/socket.rb
CHANGED
@@ -47,6 +47,19 @@ module IB
|
|
47
47
|
# str.nil? || str.empty? ? nil : str.to_d
|
48
48
|
str.to_f unless str.nil? || str.empty? || str.to_f > 1.797 * 10.0 ** 306
|
49
49
|
end
|
50
|
+
|
51
|
+
# If received decimal is below limit ("not yet computed"), return nil
|
52
|
+
def read_decimal_limit limit = -1
|
53
|
+
value = self.read_decimal
|
54
|
+
# limit is the "not yet computed" indicator
|
55
|
+
value <= limit ? nil : value
|
56
|
+
end
|
57
|
+
|
58
|
+
alias read_decimal_limit_1 read_decimal_limit
|
59
|
+
|
60
|
+
def read_decimal_limit_2
|
61
|
+
read_decimal_limit -2
|
62
|
+
end
|
50
63
|
end # class IBSocket
|
51
64
|
|
52
65
|
end # module IB
|
@@ -1,8 +1,11 @@
|
|
1
|
-
# Note that the :description field is particular to ib-ruby, and is NOT part of the
|
2
|
-
# It is never transmitted to IB. It's purely used clientside, and
|
3
|
-
# you may find useful there.
|
1
|
+
# Note that the :description field is particular to ib-ruby, and is NOT part of the
|
2
|
+
# standard TWS API. It is never transmitted to IB. It's purely used clientside, and
|
3
|
+
# you can store any arbitrary string that you may find useful there.
|
4
4
|
module IB
|
5
5
|
module Symbols
|
6
|
+
# IDEALPRO is for orders over 25,000 and routes to the interbank quote stream.
|
7
|
+
# IDEAL is for smaller orders, and has wider spreads/slower execution... generally
|
8
|
+
# used for smaller currency conversions.
|
6
9
|
Forex = {
|
7
10
|
:audusd => Models::Contract.new(:symbol => "AUD",
|
8
11
|
:exchange => "IDEALPRO",
|
@@ -64,15 +67,5 @@ module IB
|
|
64
67
|
:sec_type => SECURITY_TYPES[:forex],
|
65
68
|
:description => "USDJPY")
|
66
69
|
}
|
67
|
-
|
68
|
-
#.symbol = "EUR"
|
69
|
-
#.currency = "USD"
|
70
|
-
#.exchange = "IDEALPRO"
|
71
|
-
#.secType = "CASH"
|
72
|
-
# This is all that is required for an FX contract object.
|
73
|
-
# IDEALPRO is for orders over 25,000 and routes to the interbank quote stream.
|
74
|
-
# IDEAL is for smaller orders, and has wider spreads/slower execution... generally
|
75
|
-
# used for smaller currency conversions.
|
76
|
-
|
77
|
-
end # Contracts
|
70
|
+
end
|
78
71
|
end
|
@@ -12,20 +12,16 @@
|
|
12
12
|
module IB
|
13
13
|
module Symbols
|
14
14
|
|
15
|
-
#
|
15
|
+
# Find the next front month of quarterly futures.
|
16
16
|
#
|
17
|
-
# N.B. This will not work as expected
|
18
|
-
#
|
19
|
-
def self.next_quarter_month
|
20
|
-
|
17
|
+
# N.B. This will not work as expected during the front month before expiration, as
|
18
|
+
# it will point to the next quarter even though the current month is still valid!
|
19
|
+
def self.next_quarter_month time=Time.now
|
20
|
+
[3, 6, 9, 12].find { |month| month > time.month } || 3 # for December, next March
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.next_quarter_year
|
24
|
-
|
25
|
-
time.year + 1
|
26
|
-
else
|
27
|
-
time.year
|
28
|
-
end
|
23
|
+
def self.next_quarter_year time=Time.now
|
24
|
+
next_quarter_month(time) < time.month ? time.year + 1 : time.year
|
29
25
|
end
|
30
26
|
|
31
27
|
# WARNING: This is currently broken. It returns the next
|
@@ -39,8 +35,8 @@ module IB
|
|
39
35
|
# vast majority of their volume in the Nov 2011 contract, but this
|
40
36
|
# method will return the Dec 2011 contract instead.
|
41
37
|
#
|
42
|
-
def self.next_expiry
|
43
|
-
"#{
|
38
|
+
def self.next_expiry time=Time.now
|
39
|
+
"#{ next_quarter_year(time) }#{ sprintf("%02d", next_quarter_month(time)) }"
|
44
40
|
end
|
45
41
|
|
46
42
|
# Convenience method; generates a Models::Contract instance for a futures
|
@@ -58,7 +54,7 @@ module IB
|
|
58
54
|
#
|
59
55
|
def self.future(base_symbol, exchange, currency, description="", expiry=nil)
|
60
56
|
Models::Contract.new(:symbol => base_symbol,
|
61
|
-
:expiry => expiry
|
57
|
+
:expiry => expiry || next_expiry,
|
62
58
|
:exchange => exchange,
|
63
59
|
:currency => currency,
|
64
60
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -67,14 +63,14 @@ module IB
|
|
67
63
|
|
68
64
|
Futures ={
|
69
65
|
:ym => Models::Contract.new(:symbol => "YM",
|
70
|
-
:expiry =>
|
66
|
+
:expiry => next_expiry,
|
71
67
|
:exchange => "ECBOT",
|
72
68
|
:currency => "USD",
|
73
69
|
:sec_type => SECURITY_TYPES[:future],
|
74
70
|
:description => "Mini Dow Jones Industrial"),
|
75
71
|
|
76
72
|
:es => Models::Contract.new(:symbol => "ES",
|
77
|
-
:expiry =>
|
73
|
+
:expiry => next_expiry,
|
78
74
|
:exchange => "GLOBEX",
|
79
75
|
:currency => "USD",
|
80
76
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -82,7 +78,7 @@ module IB
|
|
82
78
|
:description => "E-Mini S&P 500"),
|
83
79
|
|
84
80
|
:gbp => Models::Contract.new(:symbol => "GBP",
|
85
|
-
:expiry =>
|
81
|
+
:expiry => next_expiry,
|
86
82
|
:exchange => "GLOBEX",
|
87
83
|
:currency => "USD",
|
88
84
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -90,7 +86,7 @@ module IB
|
|
90
86
|
:description => "British Pounds"),
|
91
87
|
|
92
88
|
:eur => Models::Contract.new(:symbol => "EUR",
|
93
|
-
:expiry =>
|
89
|
+
:expiry => next_expiry,
|
94
90
|
:exchange => "GLOBEX",
|
95
91
|
:currency => "USD",
|
96
92
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -98,7 +94,7 @@ module IB
|
|
98
94
|
:description => "Euro FX"),
|
99
95
|
|
100
96
|
:jpy => Models::Contract.new(:symbol => "JPY",
|
101
|
-
:expiry =>
|
97
|
+
:expiry => next_expiry,
|
102
98
|
:exchange => "GLOBEX",
|
103
99
|
:currency => "USD",
|
104
100
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -106,7 +102,7 @@ module IB
|
|
106
102
|
:description => "Japanese Yen"),
|
107
103
|
|
108
104
|
:hsi => Models::Contract.new(:symbol => "HSI",
|
109
|
-
:expiry =>
|
105
|
+
:expiry => next_expiry,
|
110
106
|
:exchange => "HKFE",
|
111
107
|
:currency => "HKD",
|
112
108
|
:sec_type => SECURITY_TYPES[:future],
|
@@ -9,11 +9,15 @@ module IB
|
|
9
9
|
|
10
10
|
Options =
|
11
11
|
{:wfc20 => Models::Contract::Option.new(:symbol => "WFC",
|
12
|
-
:exchange => "SMART",
|
13
12
|
:expiry => "201207",
|
14
13
|
:right => "CALL",
|
15
14
|
:strike => 20.0,
|
16
15
|
:description => "Wells Fargo 20 Call 2012-07"),
|
16
|
+
:aapl500 => Models::Contract::Option.new(:symbol => "AAPL",
|
17
|
+
:expiry => "201301",
|
18
|
+
:right => "CALL",
|
19
|
+
:strike => 500,
|
20
|
+
:description => "Apple 500 Call 2013-01"),
|
17
21
|
:z50 => Models::Contract::Option.new(:symbol => "Z",
|
18
22
|
:exchange => "LIFFE",
|
19
23
|
:expiry => "201206",
|
@@ -21,14 +25,12 @@ module IB
|
|
21
25
|
:strike => 50.0,
|
22
26
|
:description => " FTSE-100 index 50 Call 2012-06"),
|
23
27
|
:spy75 => Models::Contract::Option.new(:symbol => 'SPY',
|
24
|
-
:exchange => "SMART",
|
25
28
|
:expiry => "20120615",
|
26
29
|
:right => "P",
|
27
30
|
:currency => "USD",
|
28
31
|
:strike => 75.0,
|
29
32
|
:description => "SPY 75.0 Put 2012-06-16"),
|
30
|
-
:spy100 => Models::Contract::Option.new(:osi => 'SPY 121222P00100000',
|
31
|
-
:exchange => "SMART"),
|
33
|
+
:spy100 => Models::Contract::Option.new(:osi => 'SPY 121222P00100000'),
|
32
34
|
}
|
33
35
|
end
|
34
36
|
end
|
data/lib/ib-ruby.rb
CHANGED
data/spec/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# WRITING INTEGRATION SPECS
|
2
|
+
|
3
|
+
Pattern for writing integration specs is like this:
|
4
|
+
|
5
|
+
1. You define your user scenario (such as: subscribe for FOREX market data).
|
6
|
+
|
7
|
+
2. You find out experimentally, what messages should be sent to IB to accomplish it,
|
8
|
+
and what messages are sent by IB in return.
|
9
|
+
|
10
|
+
3. You start writing spec, requiring 'integration_helper'.
|
11
|
+
|
12
|
+
4. Indicate your interest in incoming message types by calling 'connect_and_receive'
|
13
|
+
in a top-level before(:all) block. All messages of given types will be caught
|
14
|
+
and placed into @received Hash, keyed by message type.
|
15
|
+
|
16
|
+
5. All log entries produced by ib-ruby will be caught and placed into log_entries Array.
|
17
|
+
|
18
|
+
6. You send request messages to IB and then wait for specific conditions (or timeout)
|
19
|
+
by calling 'wait_for' (usually, in a context before(:all) block).
|
20
|
+
|
21
|
+
7. Once the conditions are satisfied, your examples can test the content of @received
|
22
|
+
Hash to see what messages were received, or log_entries Array to see what was logged
|
23
|
+
|
24
|
+
8. When done, you call 'close_connection' in a top-level after(:all) block.
|
25
|
+
|
26
|
+
TODO: Add more scenarios:
|
27
|
+
1. RealTimeBars
|
28
|
+
2. BondContractData
|
29
|
+
3. RequestScannerParameters + RequestScannerSubscription
|
30
|
+
4. RequestFundamentalData
|
31
|
+
5. ExerciseOptions
|
32
|
+
6. RequestMarketData + special tick list
|
33
|
+
7. RequestNewsBulletins
|
34
|
+
8. RequestImpliedVolatility / RequestOptionPrice
|
@@ -1,15 +1,15 @@
|
|
1
|
-
require '
|
1
|
+
require 'message_helper'
|
2
2
|
|
3
3
|
describe IB::Connection do
|
4
4
|
|
5
5
|
context 'when connected to IB Gateway', :connected => true do
|
6
6
|
# THIS depends on TWS|Gateway connectivity
|
7
7
|
before(:all) do
|
8
|
-
@ib = IB::Connection.new
|
8
|
+
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
|
9
9
|
@ib.subscribe(:OpenOrderEnd) {}
|
10
10
|
end
|
11
11
|
|
12
|
-
after(:all) {
|
12
|
+
after(:all) { close_connection }
|
13
13
|
|
14
14
|
context 'instantiation with default options' do
|
15
15
|
subject { @ib }
|
@@ -33,7 +33,7 @@ describe IB::Connection do
|
|
33
33
|
}.to_not raise_error
|
34
34
|
|
35
35
|
expect {
|
36
|
-
@ib.send_message IB::Messages::Outgoing::RequestOpenOrders.new
|
36
|
+
@ib.send_message IB::Messages::Outgoing::RequestOpenOrders.new :subscribe => true
|
37
37
|
}.to_not raise_error
|
38
38
|
end
|
39
39
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'message_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'Alert message' do
|
4
|
+
it { should be_an IB::Messages::Incoming::Alert }
|
5
|
+
it { should be_warning }
|
6
|
+
it { should_not be_error }
|
7
|
+
its(:message_type) { should == :Alert }
|
8
|
+
its(:message_id) { should == 4 }
|
9
|
+
its(:version) { should == 2 }
|
10
|
+
its(:data) { should == {:version=>2, :error_id=>-1, :code=>2104, :message=>"Market data farm connection is OK:cashfarm"}}
|
11
|
+
its(:error_id) { should == -1 }
|
12
|
+
its(:code) { should == 2104 }
|
13
|
+
its(:message) { should =~ /Market data farm connection is OK/ }
|
14
|
+
its(:to_human) { should =~ /TWS Warning/ }
|
15
|
+
|
16
|
+
it 'has class accessors as well' do
|
17
|
+
subject.class.message_id.should == 4
|
18
|
+
subject.class.message_type.should == :Alert
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe IB::Messages::Incoming do
|
23
|
+
|
24
|
+
context 'Newly instantiated Message' do
|
25
|
+
|
26
|
+
subject do
|
27
|
+
IB::Messages::Incoming::Alert.new(
|
28
|
+
:version => 2,
|
29
|
+
:error_id => -1,
|
30
|
+
:code => 2104,
|
31
|
+
:message => 'Market data farm connection is OK:cashfarm')
|
32
|
+
end
|
33
|
+
|
34
|
+
it_behaves_like 'Alert message'
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'Message received from IB', :connected => true do
|
38
|
+
|
39
|
+
before(:all) do
|
40
|
+
connect_and_receive :Alert
|
41
|
+
wait_for(2) { received? :Alert }
|
42
|
+
end
|
43
|
+
|
44
|
+
after(:all) { close_connection }
|
45
|
+
|
46
|
+
subject { @received[:Alert].first }
|
47
|
+
|
48
|
+
it_behaves_like 'Alert message'
|
49
|
+
end #
|
50
|
+
end # describe IB::Messages:Incoming
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'message_helper'
|
2
|
+
|
3
|
+
describe IB::Messages::Outgoing do
|
4
|
+
|
5
|
+
context 'Newly instantiated Message' do
|
6
|
+
|
7
|
+
subject do
|
8
|
+
IB::Messages::Outgoing::RequestAccountData.new(
|
9
|
+
:subscribe => true,
|
10
|
+
:account_code => 'DUH')
|
11
|
+
end
|
12
|
+
|
13
|
+
it { should be_an IB::Messages::Outgoing::RequestAccountData }
|
14
|
+
its(:message_type) { should == :RequestAccountData }
|
15
|
+
its(:message_id) { should == 6 }
|
16
|
+
its(:data) { should == {:subscribe=>true, :account_code=>"DUH"}}
|
17
|
+
its(:subscribe) { should == true }
|
18
|
+
its(:account_code) { should == 'DUH' }
|
19
|
+
its(:to_human) { should =~ /RequestAccountData/ }
|
20
|
+
|
21
|
+
it 'has class accessors as well' do
|
22
|
+
subject.class.message_type.should == :RequestAccountData
|
23
|
+
subject.class.message_id.should == 6
|
24
|
+
subject.class.version.should == 2
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'encodes into Array' do
|
28
|
+
subject.encode.should == [6, 2, true, "DUH"]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end # describe IB::Messages:Outgoing
|
@@ -7,7 +7,7 @@ describe IB::Models::Contract do
|
|
7
7
|
:sec_type => IB::SECURITY_TYPES[:stock],
|
8
8
|
:expiry => '200609',
|
9
9
|
:strike => 1234,
|
10
|
-
:right => "
|
10
|
+
:right => "PUT",
|
11
11
|
:multiplier => 123,
|
12
12
|
:exchange => "SMART",
|
13
13
|
:currency => "USD",
|
@@ -82,28 +82,15 @@ describe IB::Models::Contract do
|
|
82
82
|
end #instantiation
|
83
83
|
|
84
84
|
it 'allows setting attributes' do
|
85
|
-
x = IB::Models::Contract.new
|
86
85
|
expect {
|
87
|
-
x
|
88
|
-
|
86
|
+
x = IB::Models::Contract.new
|
87
|
+
properties.each do |name, value|
|
88
|
+
subject.send("#{name}=", value)
|
89
|
+
subject.send(name).should == value
|
90
|
+
end
|
89
91
|
x.expiry = 200609
|
90
|
-
x.
|
91
|
-
x.right = "put"
|
92
|
-
x.multiplier = 123
|
93
|
-
x.exchange = "SMART"
|
94
|
-
x.currency = "USD"
|
95
|
-
x.local_symbol = "baz"
|
92
|
+
x.expiry.should == '200609'
|
96
93
|
}.to_not raise_error
|
97
|
-
|
98
|
-
x.symbol.should == "TEST"
|
99
|
-
x.sec_type.should == IB::SECURITY_TYPES[:stock]
|
100
|
-
x.expiry.should == '200609'
|
101
|
-
x.strike.should == 1234
|
102
|
-
x.right.should == "PUT"
|
103
|
-
x.multiplier.should == 123
|
104
|
-
x.exchange.should == "SMART"
|
105
|
-
x.currency.should == "USD"
|
106
|
-
x.local_symbol = "baz"
|
107
94
|
end
|
108
95
|
|
109
96
|
it 'allows setting ContractDetails attributes' do
|
@@ -125,6 +112,12 @@ describe IB::Models::Contract do
|
|
125
112
|
x.next_option_partial.should == true
|
126
113
|
end
|
127
114
|
|
115
|
+
it 'converts multiplier to int' do
|
116
|
+
expect { @contract = IB::Models::Contract.new(:multiplier => '123') }.to_not raise_error
|
117
|
+
expect { @contract.multiplier = '123' }.to_not raise_error
|
118
|
+
@contract.multiplier.should == 123
|
119
|
+
end
|
120
|
+
|
128
121
|
it 'raises on wrong security type' do
|
129
122
|
expect { IB::Models::Contract.new(:sec_type => "asdf") }.to raise_error ArgumentError
|
130
123
|
|
@@ -157,7 +150,6 @@ describe IB::Models::Contract do
|
|
157
150
|
x.expiry = 200607
|
158
151
|
x.expiry.should == "200607" # converted to a string
|
159
152
|
}.to_not raise_error
|
160
|
-
|
161
153
|
end
|
162
154
|
|
163
155
|
it 'raises on incorrect right (option type)' do
|
@@ -166,16 +158,26 @@ describe IB::Models::Contract do
|
|
166
158
|
end
|
167
159
|
|
168
160
|
it 'accepts all correct values for right (option type)' do
|
169
|
-
["PUT", "put", "P", "p"
|
170
|
-
expect { IB::Models::Contract.new(:right => right) }.to_not raise_error
|
161
|
+
["PUT", "put", "P", "p"].each do |right|
|
162
|
+
expect { @contract = IB::Models::Contract.new(:right => right) }.to_not raise_error
|
163
|
+
@contract.right.should == "PUT"
|
164
|
+
|
165
|
+
expect { @contract.right = right }.to_not raise_error
|
166
|
+
@contract.right.should == "PUT"
|
167
|
+
end
|
168
|
+
|
169
|
+
["CALL", "call", "C", "c"].each do |right|
|
170
|
+
expect { @contract = IB::Models::Contract.new(:right => right) }.to_not raise_error
|
171
|
+
@contract.right.should == "CALL"
|
171
172
|
|
172
|
-
expect {
|
173
|
+
expect { @contract.right = right }.to_not raise_error
|
174
|
+
@contract.right.should == "CALL"
|
173
175
|
end
|
174
176
|
end
|
175
177
|
end #instantiation
|
176
178
|
|
177
179
|
context "serialization" do
|
178
|
-
subject {
|
180
|
+
subject { IB::Models::Contract.new properties }
|
179
181
|
|
180
182
|
it "serializes long" do
|
181
183
|
subject.serialize_long.should ==
|