ib-ruby 0.7.6 → 0.7.8
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 +8 -0
- data/Rakefile +8 -0
- data/VERSION +1 -1
- data/bin/fundamental_data +6 -9
- data/lib/ib-ruby/connection.rb +16 -19
- data/lib/ib-ruby/constants.rb +3 -1
- data/lib/ib-ruby/extensions.rb +5 -0
- data/lib/ib-ruby/messages/incoming/contract_data.rb +46 -45
- data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +8 -5
- data/lib/ib-ruby/messages/incoming/execution_data.rb +2 -2
- data/lib/ib-ruby/messages/incoming/next_valid_id.rb +18 -0
- data/lib/ib-ruby/messages/incoming/open_order.rb +23 -16
- data/lib/ib-ruby/messages/incoming/order_status.rb +5 -3
- data/lib/ib-ruby/messages/incoming/scanner_data.rb +15 -11
- data/lib/ib-ruby/messages/incoming.rb +1 -5
- data/lib/ib-ruby/messages/outgoing/abstract_message.rb +2 -1
- data/lib/ib-ruby/messages/outgoing/place_order.rb +1 -1
- data/lib/ib-ruby/messages/outgoing.rb +1 -1
- data/lib/ib-ruby/models/bag.rb +59 -0
- data/lib/ib-ruby/models/combo_leg.rb +10 -6
- data/lib/ib-ruby/models/contract.rb +278 -0
- data/lib/ib-ruby/models/contract_detail.rb +70 -0
- data/lib/ib-ruby/models/execution.rb +22 -16
- data/lib/ib-ruby/models/model.rb +75 -17
- data/lib/ib-ruby/models/model_properties.rb +40 -26
- data/lib/ib-ruby/models/option.rb +62 -0
- data/lib/ib-ruby/models/order.rb +122 -86
- data/lib/ib-ruby/models/order_state.rb +11 -12
- data/lib/ib-ruby/models/underlying.rb +36 -0
- data/lib/ib-ruby/models.rb +1 -4
- data/spec/account_helper.rb +2 -1
- data/spec/db.rb +1 -1
- data/spec/db_helper.rb +105 -0
- data/spec/ib-ruby/connection_spec.rb +3 -3
- data/spec/ib-ruby/messages/incoming/open_order_spec.rb +5 -5
- data/spec/ib-ruby/messages/incoming/order_status_spec.rb +3 -3
- data/spec/ib-ruby/models/bag_spec.rb +15 -23
- data/spec/ib-ruby/models/bar_spec.rb +0 -5
- data/spec/ib-ruby/models/combo_leg_spec.rb +18 -25
- data/spec/ib-ruby/models/contract_detail_spec.rb +54 -0
- data/spec/ib-ruby/models/contract_spec.rb +25 -37
- data/spec/ib-ruby/models/execution_spec.rb +64 -19
- data/spec/ib-ruby/models/option_spec.rb +12 -34
- data/spec/ib-ruby/models/order_spec.rb +107 -45
- data/spec/ib-ruby/models/order_state_spec.rb +12 -12
- data/spec/ib-ruby/models/underlying_spec.rb +36 -0
- data/spec/integration/contract_info_spec.rb +65 -55
- data/spec/integration/fundamental_data_spec.rb +2 -2
- data/spec/integration/orders/attached_spec.rb +3 -3
- data/spec/integration/orders/combo_spec.rb +3 -3
- data/spec/integration/orders/placement_spec.rb +8 -8
- data/spec/integration/orders/{execution_spec.rb → trades_spec.rb} +8 -12
- data/spec/integration/orders/valid_ids_spec.rb +3 -3
- data/spec/message_helper.rb +1 -1
- data/spec/model_helper.rb +150 -85
- data/spec/order_helper.rb +35 -18
- metadata +18 -10
- data/lib/ib-ruby/models/contracts/bag.rb +0 -62
- data/lib/ib-ruby/models/contracts/contract.rb +0 -320
- data/lib/ib-ruby/models/contracts/option.rb +0 -66
- 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
|
data/lib/ib-ruby/models/order.rb
CHANGED
@@ -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
|
-
|
217
|
-
|
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
|
-
:
|
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
|
-
#
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
316
|
+
when :short
|
317
|
+
'SSHORT'
|
318
|
+
when :short_exempt
|
319
|
+
'SSHORTX'
|
320
|
+
else
|
321
|
+
side.to_sup
|
302
322
|
end,
|
303
|
-
|
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
|
-
|
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
|
-
|
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} #{
|
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
|
-
"##{
|
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
|
-
|
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
|
-
:
|
26
|
-
:
|
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
|
-
|
62
|
-
|
63
|
-
validates_numericality_of :
|
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
|
-
|
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} ##{
|
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
|
data/lib/ib-ruby/models.rb
CHANGED
@@ -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
|
|
data/spec/account_helper.rb
CHANGED
@@ -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
|
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(:
|
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(:
|
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(:
|
222
|
+
its(:next_local_id) { should be_nil }
|
223
223
|
|
224
224
|
describe 'connecting idle conection' do
|
225
225
|
before(:all) do
|