ib-ruby 0.7.6 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/HISTORY +8 -0
  2. data/Rakefile +8 -0
  3. data/VERSION +1 -1
  4. data/bin/fundamental_data +6 -9
  5. data/lib/ib-ruby/connection.rb +16 -19
  6. data/lib/ib-ruby/constants.rb +3 -1
  7. data/lib/ib-ruby/extensions.rb +5 -0
  8. data/lib/ib-ruby/messages/incoming/contract_data.rb +46 -45
  9. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +8 -5
  10. data/lib/ib-ruby/messages/incoming/execution_data.rb +2 -2
  11. data/lib/ib-ruby/messages/incoming/next_valid_id.rb +18 -0
  12. data/lib/ib-ruby/messages/incoming/open_order.rb +23 -16
  13. data/lib/ib-ruby/messages/incoming/order_status.rb +5 -3
  14. data/lib/ib-ruby/messages/incoming/scanner_data.rb +15 -11
  15. data/lib/ib-ruby/messages/incoming.rb +1 -5
  16. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +2 -1
  17. data/lib/ib-ruby/messages/outgoing/place_order.rb +1 -1
  18. data/lib/ib-ruby/messages/outgoing.rb +1 -1
  19. data/lib/ib-ruby/models/bag.rb +59 -0
  20. data/lib/ib-ruby/models/combo_leg.rb +10 -6
  21. data/lib/ib-ruby/models/contract.rb +278 -0
  22. data/lib/ib-ruby/models/contract_detail.rb +70 -0
  23. data/lib/ib-ruby/models/execution.rb +22 -16
  24. data/lib/ib-ruby/models/model.rb +75 -17
  25. data/lib/ib-ruby/models/model_properties.rb +40 -26
  26. data/lib/ib-ruby/models/option.rb +62 -0
  27. data/lib/ib-ruby/models/order.rb +122 -86
  28. data/lib/ib-ruby/models/order_state.rb +11 -12
  29. data/lib/ib-ruby/models/underlying.rb +36 -0
  30. data/lib/ib-ruby/models.rb +1 -4
  31. data/spec/account_helper.rb +2 -1
  32. data/spec/db.rb +1 -1
  33. data/spec/db_helper.rb +105 -0
  34. data/spec/ib-ruby/connection_spec.rb +3 -3
  35. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +5 -5
  36. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +3 -3
  37. data/spec/ib-ruby/models/bag_spec.rb +15 -23
  38. data/spec/ib-ruby/models/bar_spec.rb +0 -5
  39. data/spec/ib-ruby/models/combo_leg_spec.rb +18 -25
  40. data/spec/ib-ruby/models/contract_detail_spec.rb +54 -0
  41. data/spec/ib-ruby/models/contract_spec.rb +25 -37
  42. data/spec/ib-ruby/models/execution_spec.rb +64 -19
  43. data/spec/ib-ruby/models/option_spec.rb +12 -34
  44. data/spec/ib-ruby/models/order_spec.rb +107 -45
  45. data/spec/ib-ruby/models/order_state_spec.rb +12 -12
  46. data/spec/ib-ruby/models/underlying_spec.rb +36 -0
  47. data/spec/integration/contract_info_spec.rb +65 -55
  48. data/spec/integration/fundamental_data_spec.rb +2 -2
  49. data/spec/integration/orders/attached_spec.rb +3 -3
  50. data/spec/integration/orders/combo_spec.rb +3 -3
  51. data/spec/integration/orders/placement_spec.rb +8 -8
  52. data/spec/integration/orders/{execution_spec.rb → trades_spec.rb} +8 -12
  53. data/spec/integration/orders/valid_ids_spec.rb +3 -3
  54. data/spec/message_helper.rb +1 -1
  55. data/spec/model_helper.rb +150 -85
  56. data/spec/order_helper.rb +35 -18
  57. metadata +18 -10
  58. data/lib/ib-ruby/models/contracts/bag.rb +0 -62
  59. data/lib/ib-ruby/models/contracts/contract.rb +0 -320
  60. data/lib/ib-ruby/models/contracts/option.rb +0 -66
  61. data/lib/ib-ruby/models/contracts.rb +0 -27
@@ -0,0 +1,62 @@
1
+ require 'ib-ruby/models/contract'
2
+
3
+ module IB
4
+ module Models
5
+ class Option < Contract
6
+
7
+ validates_numericality_of :strike, :greater_than => 0
8
+ validates_format_of :sec_type, :with => /^option$/,
9
+ :message => "should be an option"
10
+ validates_format_of :local_symbol, :with => /^\w+\s*\d{15}$|^$/,
11
+ :message => "invalid OSI code"
12
+ validates_format_of :right, :with => /^put$|^call$/,
13
+ :message => "should be put or call"
14
+
15
+ # For Options, this is contract's OSI (Option Symbology Initiative) name/code
16
+ alias osi local_symbol
17
+
18
+ def osi= value
19
+ # Normalize to 21 char
20
+ self.local_symbol = value.sub(/ /, ' '*(22-value.size))
21
+ end
22
+
23
+ # Make valid IB Contract definition from OSI (Option Symbology Initiative) code.
24
+ # NB: Simply making a new Contract with *local_symbol* (osi) property set to a
25
+ # valid OSI code works just as well, just do NOT set *expiry*, *right* or
26
+ # *strike* properties in this case.
27
+ # This class method provided as a backup and shows how to analyse OSI codes.
28
+ def self.from_osi osi
29
+
30
+ # Parse contract's OSI (OCC Option Symbology Initiative) code
31
+ args = osi.match(/(\w+)\s?(\d\d)(\d\d)(\d\d)([pcPC])(\d+)/).to_a.drop(1)
32
+ symbol = args.shift
33
+ year = 2000 + args.shift.to_i
34
+ month = args.shift.to_i
35
+ day = args.shift.to_i
36
+ right = args.shift.upcase
37
+ strike = args.shift.to_i/1000.0
38
+
39
+ # Set correct expiry date - IB expiry date differs from OSI if expiry date
40
+ # falls on Saturday (see https://github.com/arvicco/option_mower/issues/4)
41
+ expiry_date = Time.utc(year, month, day)
42
+ expiry_date = Time.utc(year, month, day-1) if expiry_date.wday == 6
43
+
44
+ new :symbol => symbol,
45
+ :exchange => "SMART",
46
+ :expiry => expiry_date.to_ib[2..7], # YYMMDD
47
+ :right => right,
48
+ :strike => strike
49
+ end
50
+
51
+ def default_attributes
52
+ {:sec_type => :option}.merge super
53
+ #self[:description] ||= osi ? osi : "#{symbol} #{strike} #{right} #{expiry}"
54
+ end
55
+
56
+ def to_human
57
+ "<Option: " + [symbol, expiry, right, strike, exchange, currency].join(" ") + ">"
58
+ end
59
+
60
+ end # class Option
61
+ end # module Models
62
+ end # module IB
@@ -12,10 +12,10 @@ module IB
12
12
  # your own Order IDs to avoid conflicts between orders placed from your API application.
13
13
 
14
14
  # Main order fields
15
- prop :order_id, # int: Order id associated with client (volatile).
15
+ prop [:local_id, :order_id], # int: Order id associated with client (volatile).
16
16
  :client_id, # int: The id of the client that placed this order.
17
17
  :perm_id, # int: TWS permanent id, remains the same over TWS sessions.
18
- :total_quantity, # int: The order quantity.
18
+ [:quantity, :total_quantity], # int: The order quantity.
19
19
 
20
20
  :order_type, # String: Order type.
21
21
  # Limit Risk: MTL / MKT PRT / QUOTE / STP / STP LMT / TRAIL / TRAIL LIMIT / TRAIL LIT / TRAIL MIT
@@ -213,9 +213,40 @@ module IB
213
213
  # for ComboLeg compatibility: SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3;
214
214
  [:side, :action] => PROPS[:side] # String: Action/side: BUY/SELL/SSHORT/SSHORTX
215
215
 
216
- # Some properties received from IB are separated into OrderState object,
217
- # but they are still readable as Order properties through delegation.
216
+ prop :placed_at, :modified_at
217
+
218
+ # TODO: restore!
219
+ ## Returned in OpenOrder for Bag Contracts
220
+ ## public Vector<OrderComboLeg> m_orderComboLegs
221
+ #prop :algo_params, :leg_prices, :combo_params
222
+ #
223
+ #alias order_combo_legs leg_prices
224
+ #alias smart_combo_routing_params combo_params
218
225
  #
226
+ ##serialize :algo_params
227
+ ##serialize :leg_prices
228
+ ##serialize :combo_params
229
+
230
+ has_many :executions
231
+
232
+ # Order has a collection of OrderStates, last one is always current
233
+ has_many :order_states
234
+
235
+ def order_state
236
+ order_states.last
237
+ end
238
+
239
+ def order_state= state
240
+ self.order_states.push case state
241
+ when IB::OrderState
242
+ state
243
+ when Symbol, String
244
+ IB::OrderState.new :status => state
245
+ end
246
+ end
247
+
248
+ # Some properties received from IB are separated into OrderState object,
249
+ # but they are still readable as Order properties through delegation:
219
250
  # Properties arriving via OpenOrder message:
220
251
  [:commission, # double: Shows the commission amount on the order.
221
252
  :commission_currency, # String: Shows the currency of the commission.
@@ -229,78 +260,67 @@ module IB
229
260
  # Properties arriving via OrderStatus message:
230
261
  :filled, # int
231
262
  :remaining, # int
232
- :average_fill_price, # double
263
+ :price, # double
233
264
  :last_fill_price, # double
265
+ :average_price, # double
266
+ :average_fill_price, # double
234
267
  :why_held # String: comma-separated list of reasons for order to be held.
235
268
  ].each { |property| define_method(property) { order_state.send(property) } }
236
269
 
237
- # Returned in OpenOrder for Bag Contracts
238
- # public Vector<OrderComboLeg> m_orderComboLegs
239
- attr_accessor :leg_prices, :combo_params, :order_state
240
- alias order_combo_legs leg_prices
241
- alias smart_combo_routing_params combo_params
242
-
243
- # TODO: :created_at, :placed_at, :modified_at accessors
244
-
245
- # Order is not valid without correct :order_id
246
- validates_numericality_of :order_id, :only_integer => true
247
-
248
- DEFAULT_PROPS = {:aux_price => 0.0,
249
- :discretionary_amount => 0.0,
250
- :parent_id => 0,
251
- :tif => :day,
252
- :order_type => :limit,
253
- :open_close => :open,
254
- :origin => :customer,
255
- :short_sale_slot => :default,
256
- :trigger_method => :default,
257
- :oca_type => :none,
258
- :auction_strategy => :none,
259
- :designated_location => '',
260
- :exempt_code => -1,
261
- :display_size => 0,
262
- :continuous_update => 0,
263
- :delta_neutral_con_id => 0,
264
- :algo_strategy => '',
265
- # TODO: Add simple defaults to prop ?
266
- :transmit => true,
267
- :what_if => false,
268
- :hidden => false,
269
- :etrade_only => false,
270
- :firm_quote_only => false,
271
- :block_order => false,
272
- :all_or_none => false,
273
- :sweep_to_fill => false,
274
- :not_held => false,
275
- :outside_rth => false,
276
- :scale_auto_reset => false,
277
- :scale_random_percent => false,
278
- :opt_out_smart_routing => false,
279
- :override_percentage_constraints => false,
280
- }
281
-
282
- def initialize opts = {}
283
- @leg_prices = []
284
- @algo_params = {}
285
- @combo_params = {}
286
- @order_state = IB::OrderState.new
287
- super opts
270
+ # Order is not valid without correct :local_id (:order_id)
271
+ validates_numericality_of :local_id, :perm_id, :client_id, :parent_id,
272
+ :quantity, :min_quantity, :display_size,
273
+ :only_integer => true, :allow_nil => true
274
+
275
+ validates_numericality_of :limit_price, :aux_price, :allow_nil => true
276
+
277
+
278
+ def default_attributes
279
+ {:aux_price => 0.0,
280
+ :discretionary_amount => 0.0,
281
+ :parent_id => 0,
282
+ :tif => :day,
283
+ :order_type => :limit,
284
+ :open_close => :open,
285
+ :origin => :customer,
286
+ :short_sale_slot => :default,
287
+ :trigger_method => :default,
288
+ :oca_type => :none,
289
+ :auction_strategy => :none,
290
+ :designated_location => '',
291
+ :exempt_code => -1,
292
+ :display_size => 0,
293
+ :continuous_update => 0,
294
+ :delta_neutral_con_id => 0,
295
+ :algo_strategy => '',
296
+ :transmit => true,
297
+ :what_if => false,
298
+ :order_state => IB::OrderState.new(:status => 'New'),
299
+ # TODO: Add simple defaults to prop ?
300
+ }.merge super
288
301
  end
289
302
 
303
+ #after_initialize do #opts = {}
304
+ # #self.leg_prices = []
305
+ # #self.algo_params = {}
306
+ # #self.combo_params = {}
307
+ # #self.order_state ||= IB::OrderState.new :status => 'New'
308
+ #end
309
+
290
310
  # This returns an Array of data from the given order,
291
311
  # mixed with data from associated contract. Ugly mix, indeed.
292
312
  def serialize_with server, contract
293
313
  [contract.serialize_long(:con_id, :sec_id),
294
314
  # main order fields
295
315
  case side
296
- when :short
297
- 'SSHORT'
298
- when :short_exempt
299
- 'SSHORTX'
300
- else
301
- side.to_sup
316
+ when :short
317
+ 'SSHORT'
318
+ when :short_exempt
319
+ 'SSHORTX'
320
+ else
321
+ side.to_sup
302
322
  end,
303
- total_quantity,
323
+ quantity,
304
324
  self[:order_type], # Internal code, 'LMT' instead of :limit
305
325
  limit_price,
306
326
  aux_price,
@@ -312,12 +332,12 @@ module IB
312
332
  order_ref,
313
333
  transmit,
314
334
  parent_id,
315
- block_order,
316
- sweep_to_fill,
335
+ block_order || false,
336
+ sweep_to_fill || false,
317
337
  display_size,
318
338
  self[:trigger_method],
319
- outside_rth, # was: ignore_rth
320
- hidden,
339
+ outside_rth || false, # was: ignore_rth
340
+ hidden || false,
321
341
  contract.serialize_legs(:extended),
322
342
 
323
343
  # This is specific to PlaceOrder v.38, NOT supported by API yet!
@@ -351,11 +371,11 @@ module IB
351
371
  self[:oca_type],
352
372
  rule_80a,
353
373
  settling_firm,
354
- all_or_none,
374
+ all_or_none || false,
355
375
  min_quantity,
356
376
  percent_offset,
357
- etrade_only,
358
- firm_quote_only,
377
+ etrade_only || false,
378
+ firm_quote_only || false,
359
379
  nbbo_price_cap,
360
380
  self[:auction_strategy],
361
381
  starting_price,
@@ -363,7 +383,7 @@ module IB
363
383
  delta,
364
384
  stock_range_lower,
365
385
  stock_range_upper,
366
- override_percentage_constraints,
386
+ override_percentage_constraints || false,
367
387
  volatility, # Volatility orders
368
388
  self[:volatility_type], #
369
389
  self[:delta_neutral_order_type],
@@ -398,28 +418,26 @@ module IB
398
418
  [scale_price_adjust_value,
399
419
  scale_price_adjust_interval,
400
420
  scale_profit_offset,
401
- scale_auto_reset,
421
+ scale_auto_reset || false,
402
422
  scale_init_position,
403
423
  scale_init_fill_qty,
404
- scale_random_percent
424
+ scale_random_percent || false
405
425
  ]
406
426
  else
407
427
  []
408
428
  end,
409
429
 
410
430
  # TODO: Need to add support for hedgeType, not working ATM - beta only
411
- #if (m_serverVersion >= MIN_SERVER_VER_HEDGE_ORDERS) {
431
+ # if (m_serverVersion >= MIN_SERVER_VER_HEDGE_ORDERS) {
412
432
  # send (order.m_hedgeType);
413
- # if (!IsEmpty(order.m_hedgeType)) send (order.m_hedgeParam);
414
- #}
433
+ # if (!IsEmpty(order.m_hedgeType)) send (order.m_hedgeParam); }
415
434
  #
416
- #if (m_serverVersion >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING) {
417
- # send (order.m_optOutSmartRouting);
418
- #}
435
+ # if (m_serverVersion >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING) {
436
+ # send (order.m_optOutSmartRouting) ; || false }
419
437
 
420
438
  clearing_account,
421
439
  clearing_intent,
422
- not_held,
440
+ not_held || false,
423
441
  contract.serialize_under_comp,
424
442
  serialize_algo(),
425
443
  what_if]
@@ -435,25 +453,43 @@ module IB
435
453
  end
436
454
  end
437
455
 
456
+ # Placement
457
+ def place contract, connection
458
+ error "Unable to place order, next_local_id not known" unless connection.next_local_id
459
+ self.client_id = connection.server[:client_id]
460
+ self.local_id = connection.next_local_id
461
+ connection.next_local_id += 1
462
+ self.placed_at = Time.now
463
+ modify contract, connection, self.placed_at
464
+ end
465
+
466
+ # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id.
467
+ def modify contract, connection, time=Time.now
468
+ self.modified_at = time
469
+ connection.send_message :PlaceOrder,
470
+ :order => self,
471
+ :contract => contract,
472
+ :local_id => local_id
473
+ local_id
474
+ end
475
+
438
476
  # Order comparison
439
477
  def == other
440
478
  perm_id && other.perm_id && perm_id == other.perm_id ||
441
- order_id == other.order_id && # ((p __LINE__)||true) &&
479
+ local_id == other.local_id && # ((p __LINE__)||true) &&
442
480
  (client_id == other.client_id || client_id == 0 || other.client_id == 0) &&
443
481
  parent_id == other.parent_id &&
444
482
  tif == other.tif &&
445
483
  action == other.action &&
446
484
  order_type == other.order_type &&
447
- total_quantity == other.total_quantity &&
485
+ quantity == other.quantity &&
448
486
  (limit_price == other.limit_price || # TODO Floats should be Decimals!
449
487
  (limit_price - other.limit_price).abs < 0.00001) &&
450
488
  aux_price == other.aux_price &&
451
- outside_rth == other.outside_rth &&
452
489
  origin == other.origin &&
453
490
  designated_location == other.designated_location &&
454
491
  exempt_code == other.exempt_code &&
455
492
  what_if == other.what_if &&
456
- not_held == other.not_held &&
457
493
  algo_strategy == other.algo_strategy &&
458
494
  algo_params == other.algo_params
459
495
 
@@ -469,10 +505,10 @@ module IB
469
505
 
470
506
  def to_human
471
507
  "<Order: " + ((order_ref && order_ref != '') ? "#{order_ref} " : '') +
472
- "#{self[:order_type]} #{self[:tif]} #{side} #{total_quantity} " +
508
+ "#{self[:order_type]} #{self[:tif]} #{side} #{quantity} " +
473
509
  "#{status} " + (limit_price ? "#{limit_price} " : '') +
474
510
  ((aux_price && aux_price != 0) ? "/#{aux_price}" : '') +
475
- "##{order_id}/#{perm_id} from #{client_id}" +
511
+ "##{local_id}/#{perm_id} from #{client_id}" +
476
512
  (account ? "/#{account}" : '') +
477
513
  (commission ? " fee #{commission}" : '') + ">"
478
514
  end
@@ -7,7 +7,7 @@ module IB
7
7
  include ModelProperties
8
8
 
9
9
  #p column_names
10
- # has_one :order
10
+ belongs_to :order
11
11
 
12
12
  # Properties arriving via OpenOrder message
13
13
  prop :init_margin, # Float: The impact the order would have on your initial margin.
@@ -22,16 +22,16 @@ module IB
22
22
  # Properties arriving via OrderStatus message:
23
23
  prop :filled, # int
24
24
  :remaining, # int
25
- :average_fill_price, # double
26
- :last_fill_price, # double
25
+ [:price, :last_fill_price,], # double
26
+ [:average_price, :average_fill_price], # double
27
27
  :why_held # String: comma-separated list of reasons for order to be held.
28
28
 
29
29
  # Properties arriving in both messages:
30
- prop :order_id, # int: Order id associated with client (volatile).
30
+ prop [:local_id, :order_id], # int: Order id associated with client (volatile).
31
31
  :perm_id, # int: TWS permanent id, remains the same over TWS sessions.
32
32
  :client_id, # int: The id of the client that placed this order.
33
33
  :parent_id, # int: The order ID of the parent (original) order, used
34
- :status # String: Displays the order status. Possible values include:
34
+ :status => :s # String: Displays the order status. Possible values include:
35
35
  # - PendingSubmit - indicates that you have transmitted the order, but
36
36
  # have not yet received confirmation that it has been accepted by the
37
37
  # order destination. NOTE: This order status is NOT sent back by TWS
@@ -58,17 +58,16 @@ module IB
58
58
  # (simulated orders) or an exchange (native orders) but that currently
59
59
  # the order is inactive due to system, exchange or other issues.
60
60
 
61
- prop :tester
62
-
63
- validates_numericality_of :order_id, :perm_id, :client_id, :only_integer => true
64
-
65
- DEFAULT_PROPS = {:status => 'New'} # Starting new Orders with this status
61
+ validates_format_of :status, :without => /^$/, :message => 'must not be empty'
62
+ validates_numericality_of :price, :average_price, :allow_nil => true
63
+ validates_numericality_of :local_id, :perm_id, :client_id, :parent_id, :filled,
64
+ :remaining, :only_integer => true, :allow_nil => true
66
65
 
67
66
  # Comparison
68
67
  def == other
69
68
  other && other.is_a?(OrderState) &&
70
69
  status == other.status &&
71
- order_id == other.order_id &&
70
+ local_id == other.local_id &&
72
71
  perm_id == other.perm_id &&
73
72
  client_id == other.client_id &&
74
73
  filled == other.filled &&
@@ -83,7 +82,7 @@ module IB
83
82
  end
84
83
 
85
84
  def to_human
86
- "<OrderState: #{status} ##{order_id}/#{perm_id} from #{client_id}" +
85
+ "<OrderState: #{status} ##{local_id}/#{perm_id} from #{client_id}" +
87
86
  (filled ? " filled #{filled}/#{remaining}" : '') +
88
87
  (last_fill_price ? " at #{last_fill_price}/#{average_fill_price}" : '') +
89
88
  (init_margin ? " margin #{init_margin}/#{maint_margin}" : '') +
@@ -0,0 +1,36 @@
1
+ require 'ib-ruby/models/contract_detail'
2
+
3
+ module IB
4
+ module Models
5
+
6
+ # Calculated characteristics of underlying Contract (volatile)
7
+ class Underlying < Model.for(:underlying)
8
+ include ModelProperties
9
+
10
+ has_one :contract
11
+
12
+ prop :con_id, # Id of the Underlying Contract
13
+ :delta, # double: The underlying stock or future delta.
14
+ :price # double: The price of the underlying.
15
+
16
+ validates_numericality_of :con_id, :delta, :price #, :allow_nil => true
17
+
18
+ def default_attributes
19
+ {:con_id => 0}.merge super
20
+ end
21
+
22
+ # Serialize under_comp parameters
23
+ def serialize
24
+ [true, con_id, delta, price]
25
+ end
26
+
27
+ # Comparison
28
+ def == other
29
+ con_id == other.con_id && delta == other.delta && price == other.price
30
+ end
31
+
32
+ end # class Contract
33
+ UnderComp = Underlying
34
+
35
+ end # module Models
36
+ end # module IB
@@ -3,16 +3,13 @@ module IB
3
3
 
4
4
  require 'ib-ruby/models/model_properties'
5
5
  require 'ib-ruby/models/model'
6
- require 'ib-ruby/models/contracts'
7
- # Flatten namespace (IB::Models::Option instead of IB::Models::Contracts::Option)
8
- include Contracts
9
6
 
7
+ require 'ib-ruby/models/contract'
10
8
  require 'ib-ruby/models/order_state'
11
9
  require 'ib-ruby/models/order'
12
10
  require 'ib-ruby/models/combo_leg'
13
11
  require 'ib-ruby/models/execution'
14
12
  require 'ib-ruby/models/bar'
15
-
16
13
  end
17
14
  end
18
15
 
@@ -1,3 +1,5 @@
1
+ require 'message_helper'
2
+
1
3
  # Make sure integration tests are only run against the pre-configured PAPER ACCOUNT
2
4
  def verify_account
3
5
  return OPTS[:account_verified] if OPTS[:account_verified]
@@ -16,7 +18,6 @@ def verify_account
16
18
  @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
17
19
 
18
20
  @ib.wait_for :ManagedAccounts, 5
19
- puts @ib.received.map { |type, msg| [" #{type}:", msg.map(&:to_human)] }
20
21
  raise "Unable to verify IB PAPER ACCOUNT" unless @ib.received?(:ManagedAccounts)
21
22
 
22
23
  received = @ib.received[:ManagedAccounts].first.accounts_list
data/spec/db.rb CHANGED
@@ -10,7 +10,7 @@ require 'database_cleaner'
10
10
  db_file = Pathname.new(__FILE__).realpath.dirname + '../db/config.yml'
11
11
  raise "Unable to find DB config file: #{db_file}" unless db_file.exist?
12
12
 
13
- env = RUBY_PLATFORM =~ /java/ ? 'test-jruby' : 'test'
13
+ env = RUBY_PLATFORM =~ /java/ ? 'test' : 'test-mri'
14
14
  db_config = YAML::load_file(db_file)[env]
15
15
 
16
16
  # Establish connection to test DB
data/spec/db_helper.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'Valid DB-backed Model' do
4
+
5
+ context 'with DB backend', :db => true do
6
+ after(:all) { DatabaseCleaner.clean }
7
+
8
+ it_behaves_like 'Model with associations'
9
+
10
+ it 'is saved' do
11
+ subject.save.should be_true
12
+ @saved = subject
13
+ end
14
+
15
+ it 'does not set created and updated properties to SAVED model' do
16
+ subject.created_at.should be_a Time
17
+ subject.updated_at.should be_a Time
18
+ end
19
+
20
+ it 'saves a single model' do
21
+ all_models = described_class.find(:all)
22
+ all_models.should have_exactly(1).model
23
+ end
24
+
25
+ it 'loads back in the same valid state as saved' do
26
+ model = described_class.find(:first)
27
+ model.object_id.should_not == subject.object_id
28
+ #model.valid?
29
+ #p model.errors
30
+ model.should be_valid
31
+ model.should == subject
32
+ end
33
+
34
+ it 'and with the same properties' do
35
+ model = described_class.find(:first)
36
+ #p model.attributes
37
+ #p model.content_attributes
38
+ props.each do |name, value|
39
+ model.send(name).should == value
40
+ end
41
+ end
42
+
43
+ it 'updates timestamps when saving the model' do
44
+ model = described_class.find(:first)
45
+ model.created_at.usec.should_not == subject.created_at.utc.usec #be_a Time
46
+ model.updated_at.usec.should_not == subject.updated_at.utc.usec #be_a Time
47
+ end
48
+
49
+ it 'is loads back with associations, if any' do
50
+ if defined? associations
51
+ end
52
+ end
53
+
54
+ end # DB
55
+ end
56
+
57
+ shared_examples_for 'Invalid DB-backed Model' do
58
+
59
+ context 'with DB backend', :db => true do
60
+ after(:all) { DatabaseCleaner.clean }
61
+
62
+ it_behaves_like 'Model with associations'
63
+
64
+ it 'is not saved' do
65
+ subject.save.should be_false
66
+ end
67
+
68
+ it 'is not loaded' do
69
+ models = described_class.find(:all)
70
+ models.should have_exactly(0).model
71
+ end
72
+ end # DB
73
+ end
74
+
75
+ shared_examples_for 'Model with associations' do
76
+ it 'works with associations, if any' do
77
+ if defined? associations
78
+ associations.each do |assoc, items|
79
+ proxy = subject.association(assoc).reflection
80
+ #pp proxy
81
+
82
+ owner_name = described_class.to_s.demodulize.tableize.singularize
83
+ [items].flatten.each do |item|
84
+ if proxy.collection?
85
+ association = subject.send("#{assoc}")
86
+ association << item
87
+
88
+ p 'collection'
89
+ association.should include item
90
+ #p association.first.send(owner_name)
91
+ #.should include item
92
+ #association.
93
+ #association.size.should == items.size # Not for Order, +1 OrderState
94
+ else
95
+ subject.send "#{assoc}=", item
96
+ association = subject.send("#{assoc}")
97
+ p 'not a collection'
98
+ association.should == item
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end
105
+ end
@@ -21,7 +21,7 @@ shared_examples_for 'Connected Connection without receiver' do
21
21
  its(:server) { should be_a Hash }
22
22
  its(:server) { should have_key :reader }
23
23
  its(:subscribers) { should have_at_least(1).item } # :NextValidId and empty Hashes
24
- its(:next_order_id) { should be_a Fixnum } # Not before :NextValidId arrives
24
+ its(:next_local_id) { should be_a Fixnum } # Not before :NextValidId arrives
25
25
  end
26
26
 
27
27
  # Need top level method to access instance var (@received) in nested context
@@ -192,7 +192,7 @@ describe IB::Connection do
192
192
  its(:server) { should_not have_key :reader }
193
193
  its(:received) { should be_empty }
194
194
  its(:subscribers) { should be_empty }
195
- its(:next_order_id) { should be_nil }
195
+ its(:next_local_id) { should be_nil }
196
196
 
197
197
  describe 'connecting idle conection' do
198
198
  before(:all) do
@@ -219,7 +219,7 @@ describe IB::Connection do
219
219
  its(:server) { should_not have_key :reader }
220
220
  its(:received) { should be_empty }
221
221
  its(:subscribers) { should be_empty }
222
- its(:next_order_id) { should be_nil }
222
+ its(:next_local_id) { should be_nil }
223
223
 
224
224
  describe 'connecting idle conection' do
225
225
  before(:all) do