ib-ruby 0.5.19 → 0.5.21
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/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 ==
|