ib-ruby 0.5.18 → 0.5.19

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 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 }