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.
Files changed (50) hide show
  1. data/HISTORY +4 -0
  2. data/TODO +0 -3
  3. data/VERSION +1 -1
  4. data/bin/contract_details +3 -3
  5. data/bin/depth_of_market +1 -1
  6. data/bin/historic_data +5 -8
  7. data/bin/market_data +2 -2
  8. data/bin/option_data +2 -2
  9. data/lib/ib-ruby/connection.rb +1 -9
  10. data/lib/ib-ruby/extensions.rb +8 -0
  11. data/lib/ib-ruby/messages/abstract_message.rb +89 -0
  12. data/lib/ib-ruby/messages/incoming.rb +415 -487
  13. data/lib/ib-ruby/messages/outgoing.rb +241 -305
  14. data/lib/ib-ruby/models/bar.rb +3 -3
  15. data/lib/ib-ruby/models/contract/bag.rb +1 -5
  16. data/lib/ib-ruby/models/contract.rb +50 -33
  17. data/lib/ib-ruby/models/execution.rb +6 -3
  18. data/lib/ib-ruby/models/order.rb +7 -5
  19. data/lib/ib-ruby/socket.rb +13 -0
  20. data/lib/ib-ruby/symbols/forex.rb +7 -14
  21. data/lib/ib-ruby/symbols/futures.rb +16 -20
  22. data/lib/ib-ruby/symbols/options.rb +6 -4
  23. data/lib/ib-ruby/symbols/stocks.rb +1 -1
  24. data/lib/ib-ruby.rb +1 -0
  25. data/spec/README.md +34 -0
  26. data/spec/ib-ruby/connection_spec.rb +4 -4
  27. data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
  28. data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
  29. data/spec/ib-ruby/models/contract_spec.rb +27 -25
  30. data/spec/ib-ruby/models/order_spec.rb +56 -23
  31. data/spec/integration/account_info_spec.rb +85 -0
  32. data/spec/integration/contract_info_spec.rb +209 -0
  33. data/spec/integration/depth_data_spec.rb +46 -0
  34. data/spec/integration/historic_data_spec.rb +82 -0
  35. data/spec/integration/market_data_spec.rb +97 -0
  36. data/spec/integration/option_data_spec.rb +96 -0
  37. data/spec/integration/orders/execution_spec.rb +135 -0
  38. data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
  39. data/spec/integration/orders/placement_spec.rb +150 -0
  40. data/spec/integration/orders/valid_ids_spec.rb +84 -0
  41. data/spec/integration_helper.rb +110 -0
  42. data/spec/message_helper.rb +13 -18
  43. data/spec/spec_helper.rb +35 -26
  44. metadata +33 -17
  45. data/spec/ib-ruby/messages/README.md +0 -16
  46. data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
  47. data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
  48. data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
  49. data/spec/ib-ruby/messages/orders_spec.rb +0 -219
  50. 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=(x)
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 == "SMART"
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=(x)
163
- x.upcase! if x.is_a?(String)
164
- x = nil if !x.nil? && x.empty?
165
- x = nil if x == "0" || x == "?"
166
- raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
167
- @right = x
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=(x)
171
- x = x.to_s
172
- if (x.nil? || !(x =~ /\d{6,8}/)) and !x.empty? then
173
- raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
174
- end
175
- @expiry = x
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=(x)
193
+ def sec_type= x
179
194
  x = nil if !x.nil? && x.empty?
180
- raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
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 reset
185
- @legs = Array.new
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 unless right == other.right &&
292
- strike == other.strike &&
293
- expiry == other.expiry &&
294
- multiplier == other.multiplier
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
- unless key == :@summary
308
- value = send(key[1..-1])
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
- :account_number, #String: The customer account number.
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
- :bought
33
+ :BUY
31
34
  when 'SLD'
32
- :sold
35
+ :SELL
33
36
  else
34
37
  value
35
38
  end
@@ -46,7 +46,7 @@ module IB
46
46
  Max_Value = 99999999
47
47
 
48
48
  # Main order fields
49
- attr_accessor :id, # int: m_orderId? Order id associated with client (volatile).
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
@@ -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 standard TWS API.
2
- # It is never transmitted to IB. It's purely used clientside, and you can store any arbitrary string that
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
- # Get the next valid quarter month >= today, for finding the front month of quarterly futures.
15
+ # Find the next front month of quarterly futures.
16
16
  #
17
- # N.B. This will not work as expected near/after expiration during that month, as
18
- # volume rolls over to the next quarter even though the current month is still valid!
19
- def self.next_quarter_month(time)
20
- sprintf("%02d", [3, 6, 9, 12].find { |month| month >= time.month })
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(time)
24
- if self.next_quarter_month(time).to_i < time.month
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(time)
43
- "#{ self.next_quarter_year(time) }#{ self.next_quarter_month(time) }"
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.nil? ? self.next_expiry(Time.now) : 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 => self.next_expiry(Time.now),
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 => self.next_expiry(Time.now),
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 => self.next_expiry(Time.now),
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 => self.next_expiry(Time.now),
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 => self.next_expiry(Time.now),
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 => self.next_expiry(Time.now),
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
@@ -17,7 +17,7 @@ module IB
17
17
  :exchange => "NYSE",
18
18
  :currency => "USD",
19
19
  :sec_type => SECURITY_TYPES[:stock],
20
- :description => "Inexistent stock"),
20
+ :description => "Non-existent stock"),
21
21
  }
22
22
  end
23
23
  end
data/lib/ib-ruby.rb CHANGED
@@ -3,6 +3,7 @@ end # module IB
3
3
  IbRuby = IB
4
4
 
5
5
  require 'ib-ruby/version'
6
+ require 'ib-ruby/extensions'
6
7
  require 'ib-ruby/constants'
7
8
  require 'ib-ruby/connection'
8
9
  require 'ib-ruby/models'
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 'spec_helper'
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 CONNECTION_OPTS
8
+ @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
9
9
  @ib.subscribe(:OpenOrderEnd) {}
10
10
  end
11
11
 
12
- after(:all) { @ib.close if @ib }
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(:subscribe => true)
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 => "put",
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.symbol = "TEST"
88
- x.sec_type = IB::SECURITY_TYPES[:stock]
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.strike = 1234
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", "CALL", "call", "C", "c"].each do |right|
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 { IB::Models::Contract.new.right = right }.to_not raise_error
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 { stock = IB::Models::Contract.new properties }
180
+ subject { IB::Models::Contract.new properties }
179
181
 
180
182
  it "serializes long" do
181
183
  subject.serialize_long.should ==