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