ib-ruby 0.5.18 → 0.5.19

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -117,3 +117,7 @@
117
117
  == 0.5.18 / 2012-02-15
118
118
 
119
119
  * Bugfix release
120
+
121
+ == 0.5.19 / 2012-02-23
122
+
123
+ * Contract
data/TODO CHANGED
@@ -1,29 +1,30 @@
1
- 1. IB Connection uses delays to prevent hitting 50 msgs/sec limit:
2
- http://finance.groups.yahoo.com/group/TWSAPI/message/25413
1
+ 1. Create integration tests for basic use cases
3
2
 
4
- 2. Decouple Broker-specific Adapter from universal high-level messaging layer
5
- (potentially adding other broker adapters)
3
+ Plan:
6
4
 
7
- 3. Tweak IB::Message API for speed (use class methods)
5
+ 1. Create integration tests (Brokerton?)
8
6
 
9
- 4. Create integration tests (Brokerton?)
7
+ 2. Make ActiveModel-like attributes Hash for easy attributes updating
10
8
 
11
- 5. IB#send_message method should accept block, thus compressing subscribe/send_message
9
+ 3. IB#send_message method should accept block, thus compressing subscribe/send_message
12
10
  pair into a single call - to simplify DSL.
13
11
 
12
+ 4. IB Connection uses delays to prevent hitting 50 msgs/sec limit:
13
+ http://finance.groups.yahoo.com/group/TWSAPI/message/25413
14
+
15
+ 5. IB Connection reconnects gracefully in case if TWS disconnects/reconnects
16
+
14
17
  6. Compatibility check for new TWS v.966
15
18
 
16
- 7. Fix iteration bug
17
- (probably, caused by new IB message arriving while we're adding new subscriber):
18
- RuntimeError: can't add a new key into hash during iteration
19
- []= at org/jruby/RubyHash.java:899
20
- subscribe at /Users/vb/.rvm/gems/jruby-head@option_mower/gems/ib-ruby-0.5.17/lib/ib-ruby/connection.rb:132
21
- each at org/jruby/RubyArray.java:1609
22
- subscribe at /Users/vb/.rvm/gems/jruby-head@option_mower/gems/ib-ruby-0.5.17/lib/ib-ruby/connection.rb:121
23
- subscribe_orders at /Users/vb/Dev/ib/option_mower/lib/option_mower/connection.rb:153
24
- start at /Users/vb/Dev/ib/option_mower/lib/option_mower/connection.rb:42
25
- start at /Users/vb/Dev/ib/option_mower/lib/option_mower/connection.rb:17
26
- start at /Users/vb/Dev/ib/option_mower/lib/option_mower/robot.rb:103
27
- run at /Users/vb/Dev/ib/option_mower/lib/option_mower/robot.rb:121
28
- (root) at bin/option_mower:46
19
+
20
+ Done:
21
+
22
+ 2. IB#subscribe should accept regexes.
23
+
24
+ Ideas for future:
25
+
26
+ 1. Decouple Broker-specific Adapter from universal high-level messaging layer
27
+ (potentially adding other broker adapters)
28
+
29
+ 2. Tweak IB::Message API for speed (use class methods)
29
30
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.18
1
+ 0.5.19
data/bin/cancel_orders CHANGED
@@ -21,7 +21,7 @@ ib.subscribe(:Alert, :OpenOrder, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.t
21
21
  if ARGV.empty?
22
22
  ib.send_message :RequestGlobalCancel
23
23
  else
24
- ARGV.each { |order_id| ib.send_message :CancelOrder, :id => order_id.to_i }
24
+ ib.cancel_order *ARGV
25
25
  end
26
26
 
27
27
  ib.send_message :RequestAllOpenOrders
data/bin/list_orders CHANGED
@@ -26,5 +26,4 @@ end
26
26
 
27
27
  ib.send_message :RequestAllOpenOrders
28
28
 
29
- puts "\n******** Press <Enter> to cancel... *********\n\n"
30
- STDIN.gets
29
+ sleep 2
@@ -92,7 +92,7 @@ module IB
92
92
  alias open connect # Legacy alias
93
93
 
94
94
  def disconnect
95
- if @server[:reader]
95
+ if reader_running?
96
96
  @reader_running = false
97
97
  @server[:reader].join
98
98
  end
@@ -109,6 +109,10 @@ module IB
109
109
  @connected
110
110
  end
111
111
 
112
+ def reader_running?
113
+ @reader_running && @server[:reader] && @server[:reader].alive?
114
+ end
115
+
112
116
  # Subscribe Proc or block to specific type(s) of incoming message events.
113
117
  # Listener will be called later with received message instance as its argument.
114
118
  # Returns subscriber id to allow unsubscribing
@@ -119,18 +123,21 @@ module IB
119
123
  raise ArgumentError.new "Need subscriber proc or block" unless subscriber.is_a? Proc
120
124
 
121
125
  args.each do |what|
122
- message_class =
126
+ message_classes =
123
127
  case
124
128
  when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
125
129
  what
126
130
  when what.is_a?(Symbol)
127
131
  Messages::Incoming.const_get(what)
132
+ when what.is_a?(Regexp)
133
+ Messages::Incoming::Table.values.find_all { |klass| klass.to_s =~ what }
128
134
  else
129
135
  raise ArgumentError.new "#{what} must represent incoming IB message class"
130
136
  end
131
-
132
- subscribers[message_class][subscriber_id] = subscriber
133
- # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
137
+ [message_classes].flatten.each do |message_class|
138
+ # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
139
+ subscribers[message_class][subscriber_id] = subscriber
140
+ end
134
141
  end
135
142
  subscriber_id
136
143
  end
@@ -190,14 +197,24 @@ module IB
190
197
  end
191
198
 
192
199
  # Place Order (convenience wrapper for message :PlaceOrder).
193
- # Returns TWS id of a placed order.
200
+ # Assigns client_id and order_id fields to placed order.
201
+ # Returns order_id.
194
202
  def place_order order, contract
195
- @next_order_id += 1
196
203
  send_message :PlaceOrder,
197
204
  :order => order,
198
205
  :contract => contract,
199
206
  :id => @next_order_id
200
- @next_order_id
207
+ order.client_id = @server[:client_id]
208
+ order.order_id = @next_order_id
209
+ @next_order_id += 1
210
+ order.order_id
211
+ end
212
+
213
+ # Cancel Orders by their id (convenience wrapper for message :CancelOrder).
214
+ def cancel_order *order_ids
215
+ order_ids.each do |order_id|
216
+ send_message :CancelOrder, :id => order_id.to_i
217
+ end
201
218
  end
202
219
 
203
220
  # Start reader thread that continuously reads messages from server in background.
@@ -38,6 +38,7 @@ module IB
38
38
  :index => "IND",
39
39
  :futures_option => "FOP",
40
40
  :forex => "CASH",
41
+ :bond => "BOND",
41
42
  :bag => "BAG"}
42
43
 
43
44
  ### These values are typically received from TWS in incoming messages
@@ -534,9 +534,9 @@ module IB
534
534
  @order.init_margin = @socket.read_string
535
535
  @order.maint_margin = @socket.read_string
536
536
  @order.equity_with_loan = @socket.read_string
537
- @order.commission = @socket.read_decimal_max
538
- @order.min_commission = @socket.read_decimal_max
539
- @order.max_commission = @socket.read_decimal_max
537
+ @order.commission = @socket.read_decimal_max # May be nil!
538
+ @order.min_commission = @socket.read_decimal_max # May be nil!
539
+ @order.max_commission = @socket.read_decimal_max # May be nil!
540
540
  @order.commission_currency = @socket.read_string
541
541
  @order.warning_text = @socket.read_string
542
542
  end
@@ -568,8 +568,8 @@ module IB
568
568
  [:market_price, :decimal],
569
569
  [:market_value, :decimal],
570
570
  [:average_cost, :decimal],
571
- [:unrealized_pnl, :decimal], # TODO: Check for Double.MAX_VALUE
572
- [:realized_pnl, :decimal], # TODO: Check for Double.MAX_VALUE
571
+ [:unrealized_pnl, :decimal_max], # May be nil!
572
+ [:realized_pnl, :decimal_max], # May be nil!
573
573
  [:account_name, :string]
574
574
  end
575
575
 
@@ -277,19 +277,12 @@ module IB
277
277
  # '30 min'
278
278
  # '1 hour'
279
279
  # '1 day'
280
- # :what_to_show => Symbol:
281
- # Determines the nature of data being extracted. Valid values:
282
- #one of :trades, :midpoint, :bid, or :ask -
283
- # TRADES
284
- #� MIDPOINT
285
- #� BID
286
- #� ASK
287
- #� BID_ASK
288
- #� HISTORICAL_VOLATILITY
289
- #� OPTION_IMPLIED_VOLATILITY
290
- #� OPTION_VOLUME
291
- #� OPTION_OPEN_INTEREST
292
- # converts to "TRADES," "MIDPOINT," "BID," or "ASK."
280
+ # :what_to_show => Symbol: Determines the nature of data being extracted.
281
+ # Valid values:
282
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
283
+ # :historical_volatility, :option_implied_volatility,
284
+ # :option_volume, :option_open_interest
285
+ # - converts to "TRADES," "MIDPOINT," "BID," etc...
293
286
  # :use_rth => int: 0 - all data available during the time span requested
294
287
  # is returned, even data bars covering time intervals where the
295
288
  # market in question was illiquid. 1 - only data within the
@@ -362,8 +355,12 @@ module IB
362
355
  # :contract => Contract ,
363
356
  # :bar_size => int/Symbol? Currently only 5 second bars (2?) are supported,
364
357
  # if any other value is used, an exception will be thrown.,
365
- # :what_to_show => Symbol: one of :trades, :midpoint, :bid, or :ask -
366
- # converts to "TRADES," "MIDPOINT," "BID," or "ASK."
358
+ # :what_to_show => Symbol: Determines the nature of data being extracted.
359
+ # Valid values:
360
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
361
+ # :historical_volatility, :option_implied_volatility,
362
+ # :option_volume, :option_open_interest
363
+ # - converts to "TRADES," "MIDPOINT," "BID," etc...
367
364
  # :use_rth => int: 0 - all data available during the time span requested
368
365
  # is returned, even data bars covering time intervals where the
369
366
  # market in question was illiquid. 1 - only data within the
@@ -374,20 +371,23 @@ module IB
374
371
  @message_id = 50
375
372
 
376
373
  def encode
377
- if @data.has_key?(:what_to_show) && @data[:what_to_show].is_a?(String)
378
- @data[:what_to_show] = @data[:what_to_show].downcase.to_sym
374
+ data_type = DATA_TYPES[@data[:what_to_show]] || @data[:what_to_show]
375
+ unless DATA_TYPES.values.include?(data_type)
376
+ raise ArgumentError(":what_to_show must be one of #{DATA_TYPES}.")
379
377
  end
380
378
 
381
- raise ArgumentError(":what_to_show must be one of #{DATA_TYPES}.") unless DATA_TYPES.include?(@data[:what_to_show])
382
- raise ArgumentError(":bar_size must be one of #{BAR_SIZES}.") unless BAR_SIZES.include?(@data[:bar_size])
379
+ bar_size = BAR_SIZES[@data[:bar_size]] || @data[:bar_size]
380
+ unless BAR_SIZES.values.include?(bar_size)
381
+ raise ArgumentError(":bar_size must be one of #{BAR_SIZES}.")
382
+ end
383
383
 
384
384
  contract = @data[:contract].is_a?(Models::Contract) ?
385
385
  @data[:contract] : Models::Contract.from_ib_ruby(@data[:contract])
386
386
 
387
387
  [super,
388
388
  contract.serialize_long,
389
- @data[:bar_size],
390
- @data[:what_to_show].to_s.upcase,
389
+ bar_size,
390
+ data_type.to_s.upcase,
391
391
  @data[:use_rth]]
392
392
  end
393
393
  end # RequestRealTimeBars
@@ -1,7 +1,5 @@
1
1
  require 'ib-ruby/models/model'
2
2
 
3
- # TODO: Implement equals() according to the criteria in IB's Java client.
4
-
5
3
  module IB
6
4
  module Models
7
5
  class Contract < Model
@@ -242,6 +240,8 @@ module IB
242
240
  end
243
241
  end
244
242
 
243
+ ### Leg-related methods (better suited to BAG subclass?)
244
+
245
245
  # Some messages send open_close too, some don't. WTF.
246
246
  # "BAG" is not really a contract, but a combination (combo) of securities.
247
247
  # AKA basket or bag of securities. Individual securities in combo are represented
@@ -252,6 +252,56 @@ module IB
252
252
  [legs.size, legs.map { |leg| leg.serialize *fields }]
253
253
  end
254
254
 
255
+ # Check if two Contracts have same legs (maybe in different order)
256
+ def same_legs? other
257
+ legs == other.legs ||
258
+ legs_description.split(',').sort == other.legs_description.split(',').sort
259
+ end
260
+
261
+ # IB-equivalent leg description. TODO: Rewrite with self[:legs_description]
262
+ def legs_description
263
+ @legs_description || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
264
+ end
265
+
266
+ # Contract comparison
267
+ def == other
268
+ # Different sec_id_type
269
+ return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
270
+
271
+ # Different sec_id
272
+ return false if sec_id && other.sec_id && sec_id != other.sec_id
273
+
274
+ # Different under_comp
275
+ return false if under_comp && other.under_comp && under_comp != other.under_comp
276
+
277
+ # Different legs
278
+ return false unless same_legs? other
279
+
280
+ # Same con_id for all Bags, but unknown for new Contracts...
281
+ # 0 or nil con_id matches any
282
+ return false if con_id != 0 && other.con_id != 0 &&
283
+ con_id && other.con_id && con_id != other.con_id
284
+
285
+ # SMART or nil exchange matches any
286
+ return false if exchange != 'SMART' && other.exchange != 'SMART' &&
287
+ exchange && other.exchange && exchange != other.exchange
288
+
289
+ # Comparison for Bonds and Options
290
+ 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
295
+ end
296
+
297
+ # All else being equal...
298
+ sec_type == other.sec_type &&
299
+ symbol == other.symbol &&
300
+ currency == other.currency
301
+ end
302
+
303
+
304
+ # TODO: Remove @summary into reader method
255
305
  def to_s
256
306
  "<Contract: " + instance_variables.map do |key|
257
307
  unless key == :@summary
@@ -14,6 +14,16 @@ module IB
14
14
 
15
15
  opts.keys.each { |key| self.send("#{key}=", opts[key]) }
16
16
  end
17
+
18
+ # ActiveModel-style attribute accessors
19
+ def [] key
20
+ self.send key
21
+ end
22
+
23
+ def []= key, val
24
+ self.send "#{key}=", val
25
+ end
26
+
17
27
  end # Model
18
28
  end # module Models
19
29
  end # module IB
@@ -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? The id for this order.
49
+ attr_accessor :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.
@@ -304,6 +304,9 @@ module IB
304
304
 
305
305
  :warning_text # String: Displays a warning message if warranted.
306
306
 
307
+ alias order_id id # TODO: Change due to ActiveRecord specifics
308
+ alias order_id= id= # TODO: Change due to ActiveRecord specifics
309
+
307
310
  # IB uses weird String with Java Double.MAX_VALUE to indicate no value here
308
311
  def init_margin= val
309
312
  @init_margin = val == "1.7976931348623157E308" ? nil : val
@@ -321,6 +324,10 @@ module IB
321
324
 
322
325
  def initialize opts = {}
323
326
  # Assign defaults first!
327
+ @aux_price = 0.0
328
+ @parent_id=0
329
+ @tif='DAY'
330
+
324
331
  @outside_rth = false
325
332
  @open_close = "O"
326
333
  @origin = Origin_Customer
@@ -429,6 +436,31 @@ module IB
429
436
  what_if]
430
437
  end
431
438
 
439
+ # Order comparison
440
+ def == other
441
+ perm_id == other.perm_id ||
442
+ order_id == other.order_id && # ((p __LINE__)||true) &&
443
+ client_id == other.client_id &&
444
+ parent_id == other.parent_id &&
445
+ tif == other.tif &&
446
+ action == other.action &&
447
+ order_type == other.order_type &&
448
+ total_quantity == other.total_quantity &&
449
+ limit_price == other.limit_price &&
450
+ aux_price == other.aux_price &&
451
+ outside_rth == other.outside_rth &&
452
+ origin == other.origin &&
453
+ transmit == other.transmit &&
454
+ designated_location == other.designated_location &&
455
+ exempt_code == other.exempt_code &&
456
+ what_if == other.what_if &&
457
+ not_held == other.not_held &&
458
+ algo_strategy == other.algo_strategy &&
459
+ algo_params == other.algo_params
460
+
461
+ # TODO: || compare all attributes!
462
+ end
463
+
432
464
  def serialize_algo
433
465
  if algo_strategy.nil? || algo_strategy.empty?
434
466
  ['']
@@ -448,7 +480,7 @@ module IB
448
480
 
449
481
  def to_human
450
482
  "<Order: #{order_type} #{tif} #{action} #{total_quantity} #{status} #{limit_price}" +
451
- " id: #{id}/#{perm_id} from: #{client_id}/#{account}>"
483
+ " id: #{order_id}/#{perm_id} from: #{client_id}/#{account}>"
452
484
  end
453
485
  end # class Order
454
486
  end # module Models
@@ -45,7 +45,7 @@ module IB
45
45
  # Floating-point numbers shouldn't be used to store money...
46
46
  # ...but BigDecimals are too unwieldy to use in this case... maybe later
47
47
  # str.nil? || str.empty? ? nil : str.to_d
48
- str.nil? || str.empty? ? nil : str.to_f
48
+ str.to_f unless str.nil? || str.empty? || str.to_f > 1.797 * 10.0 ** 306
49
49
  end
50
50
  end # class IBSocket
51
51
 
@@ -16,10 +16,10 @@ describe IB::Connection do
16
16
 
17
17
  it { should_not be_nil }
18
18
  it { should be_connected }
19
- its(:server) {should be_a Hash}
20
- its(:server) {should have_key :reader}
21
- its(:subscribers) {should have_at_least(1).item} # :NextValidID and empty Hashes
22
- its(:next_order_id) {should be_a Fixnum} # Not before :NextValidID arrives
19
+ its(:server) { should be_a Hash }
20
+ its(:server) { should have_key :reader }
21
+ its(:subscribers) { should have_at_least(1).item } # :NextValidID and empty Hashes
22
+ its(:next_order_id) { should be_a Fixnum } # Not before :NextValidID arrives
23
23
  end
24
24
 
25
25
  describe '#send_message', 'sending messages' do
@@ -46,24 +46,31 @@ describe IB::Connection do
46
46
  context "subscriptions" do
47
47
 
48
48
  it '#subscribe, adds(multiple) subscribers' do
49
- @subscriber_id = @ib.subscribe(IB::Messages::Incoming::Alert, :AccountValue) do
49
+ @subscriber_id = @ib.subscribe(IB::Messages::Incoming::Alert, :OpenOrder, /Value/) do
50
50
  end
51
51
 
52
52
  @subscriber_id.should be_a Fixnum
53
53
 
54
- @ib.subscribers.should have_key(IB::Messages::Incoming::Alert)
55
- @ib.subscribers.should have_key(IB::Messages::Incoming::AccountValue)
56
- @ib.subscribers[IB::Messages::Incoming::Alert].should have_key(@subscriber_id)
57
- @ib.subscribers[IB::Messages::Incoming::AccountValue].should have_key(@subscriber_id)
58
- @ib.subscribers[IB::Messages::Incoming::Alert][@subscriber_id].should be_a Proc
59
- @ib.subscribers[IB::Messages::Incoming::AccountValue][@subscriber_id].should be_a Proc
54
+ [IB::Messages::Incoming::Alert,
55
+ IB::Messages::Incoming::OpenOrder,
56
+ IB::Messages::Incoming::AccountValue,
57
+ IB::Messages::Incoming::PortfolioValue
58
+ ].each do |message_class|
59
+ @ib.subscribers.should have_key(message_class)
60
+ @ib.subscribers[message_class].should have_key(@subscriber_id)
61
+ end
60
62
  end
61
63
 
62
64
  it '#unsubscribe, removes all subscribers at this id' do
63
65
  @ib.unsubscribe(@subscriber_id)
64
66
 
65
- @ib.subscribers[IB::Messages::Incoming::Alert].should_not have_key(@subscriber_id)
66
- @ib.subscribers[IB::Messages::Incoming::AccountValue].should_not have_key(@subscriber_id)
67
+ [IB::Messages::Incoming::Alert,
68
+ IB::Messages::Incoming::OpenOrder,
69
+ IB::Messages::Incoming::AccountValue,
70
+ IB::Messages::Incoming::PortfolioValue
71
+ ].each do |message_class|
72
+ @ib.subscribers[message_class].should_not have_key(@subscriber_id)
73
+ end
67
74
  end
68
75
 
69
76
  end # subscriptions
@@ -77,10 +84,10 @@ describe IB::Connection do
77
84
 
78
85
  it { should_not be_nil }
79
86
  it { should_not be_connected }
80
- its(:server) {should be_a Hash}
81
- its(:server) {should_not have_key :reader}
82
- its(:subscribers) {should be_empty}
83
- its(:next_order_id) {should be_nil}
87
+ its(:server) { should be_a Hash }
88
+ its(:server) { should_not have_key :reader }
89
+ its(:subscribers) { should be_empty }
90
+ its(:next_order_id) { should be_nil }
84
91
  end
85
92
 
86
93
  end # not connected
@@ -24,7 +24,7 @@ describe IB::Messages do
24
24
 
25
25
  @ib.send_message :RequestAccountData, :subscribe => true
26
26
 
27
- wait_for(5) { not @received[:AccountDownloadEnd].empty? }
27
+ wait_for(5) { received? :AccountDownloadEnd }
28
28
  end
29
29
 
30
30
  after(:all) do
@@ -35,7 +35,7 @@ describe IB::Messages do
35
35
  context "received :Alert message " do
36
36
  subject { @received[:Alert].first }
37
37
 
38
- it { should_not be_nil }
38
+ it { should be_an IB::Messages::Incoming::Alert }
39
39
  it { should be_warning }
40
40
  it { should_not be_error }
41
41
  its(:code) { should be_a Integer }
@@ -47,7 +47,7 @@ describe IB::Messages do
47
47
  subject { @received[:AccountValue].first }
48
48
 
49
49
  #ps
50
- it { should_not be_nil }
50
+ it { should be_an IB::Messages::Incoming::AccountValue }
51
51
  its(:data) { should be_a Hash }
52
52
  its(:account_name) { should =~ /\w\d/ }
53
53
  its(:key) { should be_a String }
@@ -59,7 +59,7 @@ describe IB::Messages do
59
59
  context "received :AccountDownloadEnd message" do
60
60
  subject { @received[:AccountDownloadEnd].first }
61
61
 
62
- it { should_not be_nil }
62
+ it { should be_an IB::Messages::Incoming::AccountDownloadEnd }
63
63
  its(:data) { should be_a Hash }
64
64
  its(:account_name) { should =~ /\w\d/ }
65
65
  its(:to_human) { should =~ /AccountDownloadEnd/ }
@@ -68,7 +68,7 @@ describe IB::Messages do
68
68
  context "received :PortfolioValue message" do
69
69
  subject { @received[:PortfolioValue].first }
70
70
 
71
- it { should_not be_nil }
71
+ it { should be_an IB::Messages::Incoming::PortfolioValue }
72
72
  its(:contract) { should be_a IB::Models::Contract }
73
73
  its(:data) { should be_a Hash }
74
74
  its(:position) { should be_a Integer }
@@ -6,17 +6,19 @@ describe IB::Messages do
6
6
 
7
7
  before(:all) do
8
8
  connect_and_receive :NextValidID, :OpenOrderEnd, :Alert
9
- wait_for(2) { not @received[:OpenOrderEnd].empty? }
9
+ wait_for(2) { received? :OpenOrderEnd }
10
10
  end
11
11
 
12
12
  after(:all) { close_connection }
13
13
 
14
14
  it 'receives :NextValidID message' do
15
15
  @received[:NextValidID].should_not be_empty
16
+ @received[:NextValidID].first.should be_an IB::Messages::Incoming::NextValidID
16
17
  end
17
18
 
18
19
  it 'receives :OpenOrderEnd message' do
19
20
  @received[:OpenOrderEnd].should_not be_empty
21
+ @received[:OpenOrderEnd].first.should be_an IB::Messages::Incoming::OpenOrderEnd
20
22
  end
21
23
 
22
24
  it 'logs connection notification' do
@@ -23,7 +23,7 @@ describe IB::Messages do
23
23
  context "received :Alert message " do
24
24
  subject { @received[:Alert].first }
25
25
 
26
- it { should_not be_nil }
26
+ it { should be_an IB::Messages::Incoming::Alert }
27
27
  it { should be_warning }
28
28
  it { should_not be_error }
29
29
  its(:code) { should be_an Integer }
@@ -34,7 +34,7 @@ describe IB::Messages do
34
34
  context "received :TickPrice message" do
35
35
  subject { @received[:TickPrice].first }
36
36
 
37
- it { should_not be_nil }
37
+ it { should be_an IB::Messages::Incoming::TickPrice}
38
38
  its(:tick_type) { should be_an Integer }
39
39
  its(:type) { should be_a Symbol }
40
40
  its(:price) { should be_a Float }
@@ -47,7 +47,7 @@ describe IB::Messages do
47
47
  context "received :TickSize message" do
48
48
  subject { @received[:TickSize].first }
49
49
 
50
- it { should_not be_nil }
50
+ it { should be_an IB::Messages::Incoming::TickSize }
51
51
  its(:type) { should_not be_nil }
52
52
  its(:data) { should be_a Hash }
53
53
  its(:tick_type) { should be_an Integer }