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.
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 ==