ib-ruby 0.7.9 → 0.7.10
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 +4 -0
- data/README.md +26 -1
- data/VERSION +1 -1
- data/db/migrate/101_add_executions.rb +24 -0
- data/db/migrate/111_add_bars.rb +18 -0
- data/db/migrate/121_add_order_states.rb +28 -0
- data/db/migrate/131_add_orders.rb +105 -0
- data/db/migrate/141_add_combo_legs.rb +18 -0
- data/db/migrate/151_add_underlyings.rb +13 -0
- data/db/migrate/161_add_contract_details.rb +40 -0
- data/db/migrate/171_add_contracts.rb +26 -0
- data/lib/ib-ruby/models/bag.rb +2 -2
- data/lib/ib-ruby/models/combo_leg.rb +6 -6
- data/lib/ib-ruby/models/contract.rb +5 -5
- data/lib/ib-ruby/models/contract_detail.rb +7 -7
- data/lib/ib-ruby/models/execution.rb +7 -7
- data/lib/ib-ruby/models/model.rb +6 -3
- data/lib/ib-ruby/models/model_properties.rb +58 -55
- data/lib/ib-ruby/models/option.rb +1 -1
- data/lib/ib-ruby/models/order.rb +34 -35
- data/lib/ib-ruby/models/order_state.rb +14 -2
- data/lib/ib-ruby/models/underlying.rb +1 -1
- data/spec/db_helper.rb +65 -17
- data/spec/ib-ruby/models/execution_spec.rb +45 -29
- data/spec/ib-ruby/models/order_spec.rb +12 -10
- data/spec/ib-ruby/models/order_state_spec.rb +14 -5
- data/spec/model_helper.rb +13 -0
- metadata +11 -3
data/HISTORY
CHANGED
data/README.md
CHANGED
@@ -71,7 +71,6 @@ other API implementations. The choice is yours.
|
|
71
71
|
| 0.6.1 | 921-923 | 966 |
|
72
72
|
| 0.7.1+ | 924+ | 967 |
|
73
73
|
|
74
|
-
|
75
74
|
4. Start Interactive Broker's Trader Work Station or Gateway before your code
|
76
75
|
attempts to connect to it. Note that TWS and Gateway listen to different ports,
|
77
76
|
this library assumes connection to Gateway on the same machine (localhost:4001)
|
@@ -143,6 +142,32 @@ You can easily create your own tests following the guide in 'spec/README'.
|
|
143
142
|
Help the development! See 'spec/TODO' for the list of use cases/scenarios
|
144
143
|
that still need to be tested.
|
145
144
|
|
145
|
+
## DB BACKEND:
|
146
|
+
|
147
|
+
Latest versions of the gem added (optional and experimental) support for data
|
148
|
+
persistance (ActiveRecord ORM). In order to use this support, you have to set up
|
149
|
+
the database (SQLite recommended for simplicity) and run migrations located at
|
150
|
+
'db/migrate' folder.
|
151
|
+
|
152
|
+
You further need to:
|
153
|
+
|
154
|
+
require 'ib-ruby/db'
|
155
|
+
|
156
|
+
IB::DB.connect :adapter => 'sqlite3',
|
157
|
+
:database => 'db/test.sqlite3'
|
158
|
+
|
159
|
+
require 'ib-ruby'
|
160
|
+
|
161
|
+
Only require 'ib-ruby' AFTER you connected to DB, otherwise your Models will not
|
162
|
+
inherit from ActiveRecord::Base and won't be persistent. If you are using Rails,
|
163
|
+
you don't need IB::DB.connect part, Rails will take care of it for you. So, just use:
|
164
|
+
|
165
|
+
require 'ib-ruby/db'
|
166
|
+
require 'ib-ruby'
|
167
|
+
|
168
|
+
Now, all your IB Models are just ActiveRecords and you can do whatever you want with them:
|
169
|
+
persist to DB, use in Rails applications, develop controllers and views.
|
170
|
+
|
146
171
|
## LICENSE:
|
147
172
|
|
148
173
|
This software is available under the LGPL.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.10
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class AddExecutions < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
create_table(:executions) do |t|
|
5
|
+
# TWS orders have fixed local_id of 0 AND client id of 0
|
6
|
+
t.references :order
|
7
|
+
t.integer :local_id # TWS orders have a fixed order id of 0
|
8
|
+
t.integer :client_id # Id of the client that placed the order
|
9
|
+
t.integer :perm_id # Permanent order id, remains the same over TWS sessions
|
10
|
+
t.string :order_ref # Order reference
|
11
|
+
t.string :exec_id # Unique order execution id
|
12
|
+
t.string :side, :limit => 1 # Was the transaction a buy or a sale: BOT|SLD
|
13
|
+
t.integer :quantity # The number of shares filled
|
14
|
+
t.integer :cumulative_quantity # Cumulative quantity
|
15
|
+
t.float :price # double: The order execution price
|
16
|
+
t.float :average_price # double: Average price (for all executions?)
|
17
|
+
t.string :exchange # Exchange that executed the order
|
18
|
+
t.string :account_name # The customer account number
|
19
|
+
t.boolean :liquidation, :limit => 1 # This position to be liquidated last should the need arise
|
20
|
+
t.string :time, :limit => 18 # String! The order execution time
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class AddBars < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# This is a single data point delivered by HistoricData or RealTimeBar messages.
|
5
|
+
create_table(:bars) do |t|
|
6
|
+
t.float :open # double:
|
7
|
+
t.float :high # double:
|
8
|
+
t.float :low # double:
|
9
|
+
t.float :close # double:
|
10
|
+
t.float :wap # double:
|
11
|
+
t.integer :volume #
|
12
|
+
t.integer :trades # Number of trades during the time period the bar covers
|
13
|
+
t.boolean :has_gaps, :limit => 1 # Whether or not there are gaps in the data
|
14
|
+
t.string :time, :limit => 18 # String! The order execution time
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class AddOrderStates < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# OrderState represents dynamic (changeable) info about a single Order
|
5
|
+
create_table(:order_states) do |t|
|
6
|
+
t.references :order
|
7
|
+
t.integer :local_id # int: Order id associated with client (volatile).
|
8
|
+
t.integer :client_id # int: The id of the client that placed this order.
|
9
|
+
t.integer :perm_id # int: TWS permanent id, remains the same over TWS sessions.
|
10
|
+
t.integer :parent_id # int: The order ID of the parent (original) order, used
|
11
|
+
t.string :status # String: Displays the order status.Possible values include:
|
12
|
+
t.integer :filled
|
13
|
+
t.integer :remaining
|
14
|
+
t.float :price # double
|
15
|
+
t.float :average_price # double
|
16
|
+
t.float :init_margin # Float: The impact the order would have on your initial margin.
|
17
|
+
t.float :maint_margin # Float: The impact the order would have on your maintenance margin.
|
18
|
+
t.float :equity_with_loan # Float: The impact the order would have on your equity
|
19
|
+
t.float :commission # double: Shows the commission amount on the order.
|
20
|
+
t.float :min_commission # The possible min range of the actual order commission.
|
21
|
+
t.float :max_commission # The possible max range of the actual order commission.
|
22
|
+
t.string :commission_currency, :limit => 4 # String: Shows the currency of the commission.
|
23
|
+
t.string :why_held # String: comma-separated list of reasons for order to be held.
|
24
|
+
t.string :warning_text # String: Displays a warning message if warranted.
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class AddOrders < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# OrderState represents dynamic (changeable) info about a single Order
|
5
|
+
create_table(:orders) do |t|
|
6
|
+
|
7
|
+
t.integer :local_id # int: Order id associated with client (volatile).
|
8
|
+
t.integer :client_id # int: The id of the client that placed this order.
|
9
|
+
t.integer :perm_id # int: TWS permanent id, remains the same over TWS sessions.
|
10
|
+
t.integer :parent_id # int: Order ID of the parent (original) order
|
11
|
+
t.string :order_ref # String: Order reference. Customer defined order ID tag.
|
12
|
+
t.string :order_type, :limit => 20 # Order type.
|
13
|
+
t.string :tif, :limit => 3 # Time in Force (time to market): DAY/GAT/GTD/GTC/IOC
|
14
|
+
t.string :side, :limit => 1 # Action/side: BUY/SELL/SSHORT/SSHORTX
|
15
|
+
t.integer :quantity # int: The order quantity.
|
16
|
+
t.float :limit_price # double: LIMIT price, used for limit, stop-limit and relative
|
17
|
+
t.float :aux_price # double: STOP price for stop-limit orders, and the OFFSET amount
|
18
|
+
t.integer :open_close # same as ComboLeg: SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3
|
19
|
+
t.integer :oca_type # int: Tells how to handle remaining orders in an OCA group
|
20
|
+
t.string :oca_group # String: Identifies a member of a one-cancels-all group.
|
21
|
+
|
22
|
+
t.boolean :transmit, :limit => 1 # If false, order will be created but not transmitted.
|
23
|
+
t.boolean :what_if, :limit => 1 # Only return pre-trade commissions and margin info, do not place
|
24
|
+
t.boolean :outside_rth, :limit => 1 # Order may trigger or fill outside of regular hours.
|
25
|
+
t.boolean :not_held, :limit => 1 # Not Held
|
26
|
+
t.boolean :hidden, :limit => 1 # Order will not be visible in market depth. ISLAND only.
|
27
|
+
t.boolean :block_order, :limit => 1 # This is an ISE Block order.
|
28
|
+
t.boolean :sweep_to_fill, :limit => 1 # This is a Sweep-to-Fill order.
|
29
|
+
t.boolean :all_or_none, :limit => 1 # AON
|
30
|
+
t.boolean :etrade_only, :limit => 1 # Trade with electronic quotes.
|
31
|
+
t.boolean :firm_quote_only, :limit => 1 # Trade with firm quotes.
|
32
|
+
t.boolean :opt_out_smart_routing, :limit => 1 # Australian exchange only, default false
|
33
|
+
t.boolean :override_percentage_constraints, :limit => 1
|
34
|
+
|
35
|
+
t.integer :min_quantity # int: Identifies a minimum quantity order type.
|
36
|
+
t.integer :display_size # int: publicly disclosed order size for Iceberg orders.
|
37
|
+
t.integer :trigger_method # Specifies how Simulated Stop, Stop-Limit and Trailing
|
38
|
+
t.integer :origin # 0=Customer, 1=Firm
|
39
|
+
|
40
|
+
t.string :good_after_time # Indicates that the trade should be submitted after the
|
41
|
+
t.string :good_till_date # Indicates that the trade should remain working until the
|
42
|
+
t.string :rule_80a # Individual = 'I', Agency = 'A', AgentOtherMember = 'W',
|
43
|
+
|
44
|
+
t.float :percent_offset # double: percent offset amount for relative (REL)orders only
|
45
|
+
t.float :trail_stop_price # double: for TRAILLIMIT orders only
|
46
|
+
t.float :trailing_percent
|
47
|
+
|
48
|
+
t.string :fa_group
|
49
|
+
t.string :fa_profile
|
50
|
+
t.string :fa_method
|
51
|
+
t.string :fa_percentage
|
52
|
+
|
53
|
+
t.integer :short_sale_slot # 1 - you hold the shares,
|
54
|
+
t.string :designated_location # String: set when slot==2 only
|
55
|
+
t.integer :exempt_code # int
|
56
|
+
t.string :account # String: The account. For institutional customers only.
|
57
|
+
t.string :settling_firm # String: Institutional only
|
58
|
+
t.string :clearing_account # String: For IBExecution customers: Specifies the
|
59
|
+
t.string :clearing_intent # IBExecution customers: "", IB, Away, PTA (post trade allocation).
|
60
|
+
t.float :discretionary_amount # double: The amount off the limit price
|
61
|
+
t.float :nbbo_price_cap # double: Maximum Smart order distance from the NBBO.
|
62
|
+
t.integer :auction_strategy # For BOX exchange only. Valid values:
|
63
|
+
t.float :starting_price # double: Starting price. Valid on BOX orders only.
|
64
|
+
t.float :stock_ref_price # double: The stock reference price, used for VOL
|
65
|
+
|
66
|
+
t.float :delta # double: Stock delta. Valid on BOX orders only.
|
67
|
+
t.float :stock_range_lower # double: The lower value for the acceptable
|
68
|
+
t.float :stock_range_upper # double The upper value for the acceptable
|
69
|
+
t.float :volatility # double: What the price is, computed via TWSs Options
|
70
|
+
t.integer :volatility_type # int: How the volatility is calculated: 1=daily, 2=annual
|
71
|
+
t.integer :reference_price_type # int: For dynamic management of volatility orders:
|
72
|
+
t.integer :continuous_update # int: Used for dynamic management of volatility orders.
|
73
|
+
t.string :delta_neutral_order_type # String: Enter an order type to instruct TWS
|
74
|
+
t.string :delta_neutral_aux_price # double: Use this field to enter a value if
|
75
|
+
t.integer :delta_neutral_con_id
|
76
|
+
t.string :delta_neutral_settling_firm
|
77
|
+
t.string :delta_neutral_clearing_account
|
78
|
+
t.string :delta_neutral_clearing_intent
|
79
|
+
t.string :hedge_type # String: D = Delta, B = Beta, F = FX or P = Pair
|
80
|
+
t.string :hedge_param # String; value depends on the hedgeType; sent from the API
|
81
|
+
t.float :basis_points # double: EFP orders only
|
82
|
+
t.float :basis_points_type # double: EFP orders only
|
83
|
+
t.string :algo_strategy
|
84
|
+
|
85
|
+
# t.string :algo_params # public Vector<TagValue> m_algoParams; ?!
|
86
|
+
# t.string :leg_prices # Vector<OrderComboLeg> m_orderComboLegs
|
87
|
+
# t.string :combo_params # not used yet
|
88
|
+
|
89
|
+
t.integer :scale_init_level_size # int: Size of the first (initial) order component.
|
90
|
+
t.integer :scale_subs_level_size # int: Order size of the subsequent scale order
|
91
|
+
t.float :scale_price_increment # double: Price increment between scale components.
|
92
|
+
t.float :scale_price_adjust_value
|
93
|
+
t.integer :scale_price_adjust_interval
|
94
|
+
t.float :scale_profit_offset
|
95
|
+
t.integer :scale_init_position
|
96
|
+
t.integer :scale_init_fill_qty
|
97
|
+
t.boolean :scale_auto_reset, :limit => 1
|
98
|
+
t.boolean :scale_random_percent, :limit => 1
|
99
|
+
|
100
|
+
t.timestamp :placed_at
|
101
|
+
t.timestamp :modified_at
|
102
|
+
t.timestamps
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class AddComboLegs < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# ComboLeg objects represent individual security legs in a "BAG"
|
5
|
+
create_table(:combo_legs) do |t|
|
6
|
+
t.references :contract
|
7
|
+
t.integer :con_id # # int: The unique contract identifier specifying the security.
|
8
|
+
t.integer :ratio # int: Select the relative number of contracts for the leg you
|
9
|
+
t.string :exchange # String: exchange to which the complete combo order will be routed.
|
10
|
+
t.string :side, :limit => 1 # Action/side: BUY/SELL/SSHORT/SSHORTX
|
11
|
+
t.integer :exempt_code # int:
|
12
|
+
t.integer :short_sale_slot # int: 0 - retail(default), 1 = clearing broker, 2 = third party
|
13
|
+
t.string :designated_location # Otherwise leave blank or orders will be rejected.:status # String: Displays the order status.Possible values include:
|
14
|
+
t.integer :open_close # SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AddUnderlyings < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# Calculated characteristics of underlying Contract (volatile)
|
5
|
+
create_table(:underlyings) do |t|
|
6
|
+
t.references :contract
|
7
|
+
t.integer :con_id # # int: The unique contract identifier specifying the security
|
8
|
+
t.float :delta # double: The underlying stock or future delta
|
9
|
+
t.float :price # double: The price of the underlying
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class AddContractDetails < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
# ComboLeg objects represent individual security legs in a "BAG"
|
5
|
+
create_table(:contract_details) do |t|
|
6
|
+
t.references :contract
|
7
|
+
t.string :market_name # The market name for this contract.
|
8
|
+
t.string :trading_class # The trading class name for this contract.
|
9
|
+
t.float :min_tick # double: The minimum price tick.
|
10
|
+
t.integer :price_magnifier # int: Z on LIFFE is reported in index points not GBP.
|
11
|
+
t.string :order_types # The list of valid order types for this contract.
|
12
|
+
t.string :valid_exchanges # The list of exchanges this contract is traded on.
|
13
|
+
t.integer :under_con_id # int: The underlying contract ID.
|
14
|
+
t.string :long_name # Descriptive name of the asset.
|
15
|
+
t.string :contract_month # The contract month of the underlying futures contract.
|
16
|
+
t.string :industry # Wide industry. For example Financial.
|
17
|
+
t.string :category # Industry category. For example InvestmentSvc.
|
18
|
+
t.string :subcategory # Subcategory. For example Brokerage.
|
19
|
+
t.string :time_zone # Time zone for the trading hours (e.g. EST)
|
20
|
+
t.string :trading_hours # The trading hours of the product. 20090507:0700-18301830-2330;20090508:CLOSED.
|
21
|
+
t.string :liquid_hours # The liquid trading hours of the product.
|
22
|
+
t.string :cusip # The nine-character bond CUSIP or the 12-character SEDOL.
|
23
|
+
t.string :ratings # Credit rating of the issuer. Higher rating is less risky investment.
|
24
|
+
t.string :desc_append # Additional descriptive information about the bond.
|
25
|
+
t.string :bond_type # The type of bond such as "CORP"
|
26
|
+
t.string :coupon_type # The type of bond coupon.
|
27
|
+
t.float :coupon # double: The interest rate used to calculate the amount you
|
28
|
+
t.string :maturity # The date on which the issuer must repay bond face value
|
29
|
+
t.string :issue_date # The date the bond was issued.
|
30
|
+
t.string :next_option_date # only if bond has embedded options.
|
31
|
+
t.string :next_option_type # only if bond has embedded options.
|
32
|
+
t.string :notes # Additional notes if populated for the bond in IB's database
|
33
|
+
t.boolean :callable, :limit => 1 # Can be called by the issuer under certain conditions.
|
34
|
+
t.boolean :puttable, :limit => 1 # Can be sold back to the issuer under certain conditions
|
35
|
+
t.boolean :convertible, :limit => 1 # Can be converted to stock under certain conditions.
|
36
|
+
t.boolean :next_option_partial, :limit => 1 # # only if bond has embedded options.
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class AddContracts < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
create_table(:contracts) do |t|
|
5
|
+
|
6
|
+
t.integer :con_id # int: The unique contract identifier.
|
7
|
+
t.string :sec_type, :limit => 5 # Security type. Valid values are: SECURITY_TYPES
|
8
|
+
t.float :strike # double: The strike price.
|
9
|
+
t.string :currency, :limit => 4 # Only needed if there is an ambiguity e.g. when SMART exchange
|
10
|
+
t.string :sec_id_type, :limit => 5 # Security identifier when querying contract details or
|
11
|
+
t.integer :sec_id # Unique identifier of the given secIdType.
|
12
|
+
t.string :legs_description # received in OpenOrder for all combos
|
13
|
+
t.string :symbol # This is the symbol of the underlying asset.
|
14
|
+
t.string :local_symbol # Local exchange symbol of the underlying asset
|
15
|
+
t.integer :multiplier
|
16
|
+
t.string :expiry # The expiration date. Use the format YYYYMM or YYYYMMDD
|
17
|
+
t.string :exchange # The order destination such as Smart.
|
18
|
+
t.string :primary_exchange # Non-SMART exchange where the contract trades.
|
19
|
+
t.boolean :include_expired, :limit => 1 # When true contract details requests and historical
|
20
|
+
t.string :right, :limit => 1 # Specifies a Put or Call. Valid input values are: P PUT C CALL
|
21
|
+
|
22
|
+
t.string :type # Contract Subclasses STI
|
23
|
+
t.timestamps
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ib-ruby/models/bag.rb
CHANGED
@@ -39,12 +39,12 @@ module IB
|
|
39
39
|
:message => "should be blank or orders will be rejected"
|
40
40
|
|
41
41
|
def default_attributes
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
super.merge :con_id => 0,
|
43
|
+
:open_close => :same, # The only option for retail customers.
|
44
|
+
:short_sale_slot => :default,
|
45
|
+
:designated_location => '',
|
46
|
+
:exchange => 'SMART', # Unless SMART, Order modification fails
|
47
|
+
:exempt_code => -1
|
48
48
|
end
|
49
49
|
|
50
50
|
# Leg's weight is a combination of action and ratio
|
@@ -95,11 +95,11 @@ module IB
|
|
95
95
|
validates_numericality_of :multiplier, :strike, :allow_nil => true
|
96
96
|
|
97
97
|
def default_attributes
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
98
|
+
super.merge :con_id => 0,
|
99
|
+
:strike => 0.0,
|
100
|
+
:right => :none, # Not an option
|
101
|
+
:exchange => 'SMART',
|
102
|
+
:include_expired => false
|
103
103
|
end
|
104
104
|
|
105
105
|
# This returns an Array of data from the given contract.
|
@@ -56,13 +56,13 @@ module IB
|
|
56
56
|
validates_format_of :time_zone, :with => /^\w{3}$/, :message => 'should be XXX'
|
57
57
|
|
58
58
|
def default_attributes
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
super.merge :coupon => 0.0,
|
60
|
+
:under_con_id => 0,
|
61
|
+
:min_tick => 0,
|
62
|
+
:callable => false,
|
63
|
+
:puttable => false,
|
64
|
+
:convertible => false,
|
65
|
+
:next_option_partial => false
|
66
66
|
end
|
67
67
|
|
68
68
|
end # class ContractDetail
|
@@ -7,7 +7,7 @@ module IB
|
|
7
7
|
|
8
8
|
belongs_to :order
|
9
9
|
|
10
|
-
prop
|
10
|
+
prop :local_id, # int: order id. TWS orders have a fixed order id of 0.
|
11
11
|
:client_id, # int: client id. TWS orders have a fixed client id of 0.
|
12
12
|
:perm_id, # int: TWS id used to identify orders over TWS sessions
|
13
13
|
:exec_id, # String: Unique order execution id over TWS sessions.
|
@@ -30,12 +30,12 @@ module IB
|
|
30
30
|
validates_numericality_of :local_id, :client_id, :perm_id, :only_integer => true
|
31
31
|
|
32
32
|
def default_attributes
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
super.merge :local_id => 0,
|
34
|
+
:client_id => 0,
|
35
|
+
:quantity => 0,
|
36
|
+
:price => 0,
|
37
|
+
:perm_id => 0,
|
38
|
+
:liquidation => false
|
39
39
|
end
|
40
40
|
|
41
41
|
# Comparison
|
data/lib/ib-ruby/models/model.rb
CHANGED
@@ -21,7 +21,7 @@ module IB
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
attr_accessor :
|
24
|
+
#attr_accessor :attributes
|
25
25
|
|
26
26
|
# If a opts hash is given, keys are taken as attribute names, values as data.
|
27
27
|
# The model instance fields are then set automatically from the opts Hash.
|
@@ -29,8 +29,7 @@ module IB
|
|
29
29
|
run_callbacks :initialize do
|
30
30
|
error "Argument must be a Hash", :args unless opts.is_a?(Hash)
|
31
31
|
|
32
|
-
|
33
|
-
attrs.keys.each { |key| self.send("#{key}=", attrs[key]) }
|
32
|
+
self.attributes = default_attributes.merge(opts)
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
@@ -40,6 +39,10 @@ module IB
|
|
40
39
|
@attributes ||= HashWithIndifferentAccess.new
|
41
40
|
end
|
42
41
|
|
42
|
+
def attributes= attrs
|
43
|
+
attrs.keys.each { |key| self.send("#{key}=", attrs[key]) }
|
44
|
+
end
|
45
|
+
|
43
46
|
# ActiveModel-style read/write_attribute accessors
|
44
47
|
def [] key
|
45
48
|
attributes[key.to_sym]
|
@@ -49,13 +49,6 @@ module IB
|
|
49
49
|
|
50
50
|
included do
|
51
51
|
|
52
|
-
# Extending AR-backed Model class with attribute defaults
|
53
|
-
if defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
54
|
-
def initialize opts={}
|
55
|
-
super default_attributes.merge(opts)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
52
|
### Class macros
|
60
53
|
|
61
54
|
def self.prop *properties
|
@@ -82,59 +75,69 @@ module IB
|
|
82
75
|
def self.define_property_methods name, body={}
|
83
76
|
#p name, body
|
84
77
|
case body
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
78
|
+
when '' # default getter and setter
|
79
|
+
define_property_methods name
|
80
|
+
|
81
|
+
when Array # [setter, getter, validators]
|
82
|
+
define_property_methods name,
|
83
|
+
:get => body[0],
|
84
|
+
:set => body[1],
|
85
|
+
:validate => body[2]
|
86
|
+
|
87
|
+
when Hash # recursion base case
|
88
|
+
getter = case # Define getter
|
89
|
+
when body[:get].respond_to?(:call)
|
90
|
+
body[:get]
|
91
|
+
when body[:get]
|
92
|
+
proc { self[name].send "to_#{body[:get]}" }
|
93
|
+
when VALUES[name] # property is encoded
|
94
|
+
proc { VALUES[name][self[name]] }
|
95
|
+
#when respond_to?(:column_names) && column_names.include?(name.to_s)
|
96
|
+
# # noop, ActiveRecord will take care of it...
|
97
|
+
# p "#{name} => get noop"
|
98
|
+
# p respond_to?(:column_names) && column_names
|
99
|
+
else
|
100
|
+
proc { self[name] }
|
101
|
+
end
|
102
|
+
define_method name, &getter if getter
|
103
|
+
|
104
|
+
setter = case # Define setter
|
105
|
+
when body[:set].respond_to?(:call)
|
106
|
+
body[:set]
|
107
|
+
when body[:set]
|
108
|
+
proc { |value| self[name] = value.send "to_#{body[:set]}" }
|
109
|
+
when CODES[name] # property is encoded
|
110
|
+
proc { |value| self[name] = CODES[name][value] || value }
|
111
|
+
else
|
112
|
+
proc { |value| self[name] = value } # p name, value;
|
113
|
+
end
|
114
|
+
define_method "#{name}=", &setter if setter
|
115
|
+
|
116
|
+
# Define validator(s)
|
117
|
+
[body[:validate]].flatten.compact.each do |validator|
|
118
|
+
case validator
|
119
|
+
when Proc
|
120
|
+
validates_each name, &validator
|
121
|
+
when Hash
|
122
|
+
validates name, validator.dup
|
123
|
+
end
|
130
124
|
end
|
131
|
-
end
|
132
125
|
|
133
126
|
# TODO define self[:name] accessors for :virtual and :flag properties
|
134
127
|
|
135
|
-
|
136
|
-
|
128
|
+
else # setter given
|
129
|
+
define_property_methods name, :set => body, :get => body
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Extending AR-backed Model class with attribute defaults
|
134
|
+
if defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
135
|
+
def initialize opts={}
|
136
|
+
super default_attributes.merge(opts)
|
137
137
|
end
|
138
|
+
else
|
139
|
+
# Timestamps
|
140
|
+
prop :created_at, :updated_at
|
138
141
|
end
|
139
142
|
|
140
143
|
end # included
|
data/lib/ib-ruby/models/order.rb
CHANGED
@@ -12,7 +12,7 @@ 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
|
15
|
+
prop :local_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
18
|
[:quantity, :total_quantity], # int: The order quantity.
|
@@ -238,10 +238,10 @@ module IB
|
|
238
238
|
|
239
239
|
def order_state= state
|
240
240
|
self.order_states.push case state
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
241
|
+
when IB::OrderState
|
242
|
+
state
|
243
|
+
when Symbol, String
|
244
|
+
IB::OrderState.new :status => state
|
245
245
|
end
|
246
246
|
end
|
247
247
|
|
@@ -267,13 +267,14 @@ module IB
|
|
267
267
|
:why_held, # String: comma-separated list of reasons for order to be held.
|
268
268
|
# Testing Order state:
|
269
269
|
:new?,
|
270
|
+
:submitted?,
|
270
271
|
:pending?,
|
271
272
|
:active?,
|
272
273
|
:inactive?,
|
273
274
|
:complete_fill?,
|
274
275
|
].each { |property| define_method(property) { order_state.send(property) } }
|
275
276
|
|
276
|
-
# Order is not valid without correct :local_id
|
277
|
+
# Order is not valid without correct :local_id
|
277
278
|
validates_numericality_of :local_id, :perm_id, :client_id, :parent_id,
|
278
279
|
:quantity, :min_quantity, :display_size,
|
279
280
|
:only_integer => true, :allow_nil => true
|
@@ -282,28 +283,26 @@ module IB
|
|
282
283
|
|
283
284
|
|
284
285
|
def default_attributes
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
# TODO: Add simple defaults to prop ?
|
306
|
-
}.merge super
|
286
|
+
super.merge :aux_price => 0.0,
|
287
|
+
:discretionary_amount => 0.0,
|
288
|
+
:parent_id => 0,
|
289
|
+
:tif => :day,
|
290
|
+
:order_type => :limit,
|
291
|
+
:open_close => :open,
|
292
|
+
:origin => :customer,
|
293
|
+
:short_sale_slot => :default,
|
294
|
+
:trigger_method => :default,
|
295
|
+
:oca_type => :none,
|
296
|
+
:auction_strategy => :none,
|
297
|
+
:designated_location => '',
|
298
|
+
:exempt_code => -1,
|
299
|
+
:display_size => 0,
|
300
|
+
:continuous_update => 0,
|
301
|
+
:delta_neutral_con_id => 0,
|
302
|
+
:algo_strategy => '',
|
303
|
+
:transmit => true,
|
304
|
+
:what_if => false,
|
305
|
+
:order_state => IB::OrderState.new(:status => 'New')
|
307
306
|
end
|
308
307
|
|
309
308
|
#after_initialize do #opts = {}
|
@@ -319,12 +318,12 @@ module IB
|
|
319
318
|
[contract.serialize_long(:con_id, :sec_id),
|
320
319
|
# main order fields
|
321
320
|
case side
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
321
|
+
when :short
|
322
|
+
'SSHORT'
|
323
|
+
when :short_exempt
|
324
|
+
'SSHORTX'
|
325
|
+
else
|
326
|
+
side.to_sup
|
328
327
|
end,
|
329
328
|
quantity,
|
330
329
|
self[:order_type], # Internal code, 'LMT' instead of :limit
|
@@ -469,7 +468,7 @@ module IB
|
|
469
468
|
modify contract, connection, self.placed_at
|
470
469
|
end
|
471
470
|
|
472
|
-
# Modify Order (convenience wrapper for send_message :PlaceOrder). Returns
|
471
|
+
# Modify Order (convenience wrapper for send_message :PlaceOrder). Returns local_id.
|
473
472
|
def modify contract, connection, time=Time.now
|
474
473
|
self.modified_at = time
|
475
474
|
connection.send_message :PlaceOrder,
|
@@ -27,7 +27,7 @@ module IB
|
|
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
|
30
|
+
prop :local_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
|
@@ -63,15 +63,27 @@ module IB
|
|
63
63
|
validates_numericality_of :local_id, :perm_id, :client_id, :parent_id, :filled,
|
64
64
|
:remaining, :only_integer => true, :allow_nil => true
|
65
65
|
|
66
|
+
def default_attributes
|
67
|
+
super.merge :filled => 0,
|
68
|
+
:remaining => 0,
|
69
|
+
:price => 0.0,
|
70
|
+
:average_price => 0.0
|
71
|
+
end
|
72
|
+
|
66
73
|
## Testing Order state:
|
67
74
|
|
68
75
|
def new?
|
69
76
|
status.empty? || status == 'New'
|
70
77
|
end
|
71
78
|
|
79
|
+
# Order is in a valid, working state on TWS side
|
80
|
+
def submitted?
|
81
|
+
status == 'PreSubmitted' || status == 'Submitted'
|
82
|
+
end
|
83
|
+
|
72
84
|
# Order is in a valid, working state on TWS side
|
73
85
|
def pending?
|
74
|
-
|
86
|
+
submitted? || status == 'PendingSubmit'
|
75
87
|
end
|
76
88
|
|
77
89
|
# Order is in invalid state
|
data/spec/db_helper.rb
CHANGED
@@ -73,29 +73,77 @@ shared_examples_for 'Invalid DB-backed Model' do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
shared_examples_for 'Model with associations' do
|
76
|
+
|
76
77
|
it 'works with associations, if any' do
|
77
78
|
if defined? associations
|
78
|
-
associations.each do |assoc, items|
|
79
|
-
proxy = subject.association(assoc).reflection
|
80
|
-
#pp proxy
|
81
79
|
|
82
|
-
|
80
|
+
subject_name_plural = described_class.to_s.demodulize.tableize
|
81
|
+
|
82
|
+
associations.each do |name, item|
|
83
|
+
puts "Testing single association #{name}"
|
84
|
+
subject.association(name).reflection.should_not be_collection
|
85
|
+
|
86
|
+
# Assign item to association
|
87
|
+
expect { subject.send "#{name}=", item }.to_not raise_error
|
88
|
+
|
89
|
+
association = subject.send name #, :reload
|
90
|
+
association.should == item
|
91
|
+
association.should be_new_record
|
92
|
+
|
93
|
+
# Reverse association does not include subject
|
94
|
+
reverse_association = association.send(subject_name_plural)
|
95
|
+
reverse_association.should be_empty
|
96
|
+
|
97
|
+
# Now let's save subject
|
98
|
+
if subject.valid?
|
99
|
+
subject.save
|
100
|
+
|
101
|
+
association = subject.send name
|
102
|
+
association.should_not be_new_record
|
103
|
+
|
104
|
+
# Reverse association now DOES include subject (if reloaded!)
|
105
|
+
reverse_association = association.send(subject_name_plural, :reload)
|
106
|
+
reverse_association.should include subject
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'works with associated collections, if any' do
|
113
|
+
if defined? collections
|
114
|
+
|
115
|
+
subject_name = described_class.to_s.demodulize.tableize.singularize
|
116
|
+
|
117
|
+
collections.each do |name, items|
|
118
|
+
puts "Testing associated collection #{name}"
|
119
|
+
subject.association(name).reflection.should be_collection
|
120
|
+
|
83
121
|
[items].flatten.each do |item|
|
84
|
-
|
85
|
-
|
86
|
-
|
122
|
+
association = subject.send name #, :reload
|
123
|
+
|
124
|
+
# Add item to collection
|
125
|
+
expect { association << item }.to_not raise_error
|
126
|
+
association.should include item
|
127
|
+
|
128
|
+
# Reverse association does NOT point to subject
|
129
|
+
reverse_association = association.first.send(subject_name)
|
130
|
+
#reverse_association.should be_nil # But not always!
|
131
|
+
|
132
|
+
#association.size.should == items.size # Not for Order, +1 OrderState
|
133
|
+
end
|
134
|
+
|
135
|
+
# Now let's save subject
|
136
|
+
if subject.valid?
|
137
|
+
subject.save
|
138
|
+
|
139
|
+
[items].flatten.each do |item|
|
140
|
+
association = subject.send name #, :reload
|
87
141
|
|
88
|
-
p 'collection'
|
89
142
|
association.should include item
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
else
|
95
|
-
subject.send "#{assoc}=", item
|
96
|
-
association = subject.send("#{assoc}")
|
97
|
-
p 'not a collection'
|
98
|
-
association.should == item
|
143
|
+
|
144
|
+
# Reverse association DOES point to subject now
|
145
|
+
reverse_association = association.first.send(subject_name)
|
146
|
+
reverse_association.should == subject
|
99
147
|
end
|
100
148
|
end
|
101
149
|
|
@@ -31,14 +31,14 @@ describe IB::Models::Execution do # AKA IB::Execution
|
|
31
31
|
end
|
32
32
|
|
33
33
|
let(:assigns) do
|
34
|
-
{[:perm_id, :client_id, :cumulative_quantity, :price, :average_price] =>
|
34
|
+
{[:local_id, :perm_id, :client_id, :cumulative_quantity, :price, :average_price] =>
|
35
|
+
numeric_assigns,
|
35
36
|
:liquidation => boolean_assigns,
|
36
37
|
}
|
37
38
|
end
|
38
39
|
|
39
40
|
let(:aliases) do
|
40
41
|
{[:side, :action] => buy_sell_assigns,
|
41
|
-
[:local_id, :order_id] => numeric_assigns,
|
42
42
|
[:quantity, :shares] => numeric_assigns,
|
43
43
|
[:account_name, :account_number]=> string_assigns,
|
44
44
|
}
|
@@ -50,54 +50,70 @@ describe IB::Models::Execution do # AKA IB::Execution
|
|
50
50
|
:client_id => 1111,
|
51
51
|
:parent_id => 0,
|
52
52
|
:quantity => 100,
|
53
|
+
:side => :buy,
|
53
54
|
:order_type => :market)
|
54
55
|
}
|
55
56
|
end
|
56
57
|
|
57
58
|
it_behaves_like 'Model'
|
58
59
|
|
59
|
-
|
60
|
-
context 'associations' do
|
60
|
+
context 'DB backed associations', :db => true do
|
61
61
|
subject { IB::Execution.new props }
|
62
62
|
|
63
|
-
before(:all) { DatabaseCleaner.clean
|
63
|
+
before(:all) { DatabaseCleaner.clean }
|
64
64
|
|
65
65
|
it 'saves associated order' do
|
66
66
|
order = associations[:order]
|
67
|
-
|
68
|
-
#p order.save
|
69
|
-
|
70
67
|
subject.order = order
|
68
|
+
subject.order.should == order
|
69
|
+
subject.order.should be_new_record
|
71
70
|
|
72
|
-
|
73
|
-
|
71
|
+
subject.save
|
72
|
+
subject.order.should_not be_new_record
|
73
|
+
subject.order.executions.should include subject
|
74
|
+
end
|
74
75
|
|
76
|
+
it 'loads saved association with execution' do
|
77
|
+
order = IB::Order.find(:first)
|
75
78
|
|
76
|
-
|
77
|
-
p subject.order.executions
|
78
|
-
p subject.to_xml
|
79
|
-
p subject.serializable_hash
|
80
|
-
p subject.to_json
|
81
|
-
p subject.as_json
|
79
|
+
execution = IB::Execution.first
|
82
80
|
|
83
|
-
|
81
|
+
execution.should == subject
|
84
82
|
|
85
|
-
|
83
|
+
execution.order.should == order
|
84
|
+
order.executions.first.should == execution
|
86
85
|
end
|
86
|
+
end
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
#s1 = IB::Execution.first
|
92
|
-
#p s1
|
93
|
-
#p s1.order.executions
|
88
|
+
context 'extra ActiveModel goodness' do
|
89
|
+
subject { IB::Execution.new props }
|
94
90
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
91
|
+
it 'correctly serializes Model into hash and json' do
|
92
|
+
{"account_name"=>"DU111110",
|
93
|
+
"average_price"=>1.31075,
|
94
|
+
"client_id"=>1111,
|
95
|
+
"cumulative_quantity"=>20000,
|
96
|
+
"exchange"=>"IDEALPRO",
|
97
|
+
"exec_id"=>"0001f4e8.4f5d48f1.01.01",
|
98
|
+
"id"=>nil,
|
99
|
+
"liquidation"=>true,
|
100
|
+
"local_id"=>373,
|
101
|
+
"order_ref"=>nil,
|
102
|
+
"perm_id"=>1695693619,
|
103
|
+
"price"=>1.31075,
|
104
|
+
"quantity"=>20000,
|
105
|
+
"time"=>"20120312 15:41:09",
|
106
|
+
"side"=>:buy, }.each do |key, value|
|
107
|
+
|
108
|
+
subject.serializable_hash[key].should == value
|
109
|
+
subject.as_json["execution"][key].should == value
|
110
|
+
end
|
111
|
+
|
112
|
+
subject.to_xml.should =~ /<account-name>DU111110<.account-name>\n <average-price type=\"float\">1.31075<.average-price>\n <client-id type=\"integer\">1111<.client-id>/
|
113
|
+
subject.to_json.should =~ /\{\"execution\":\{\"account_name\":\"DU111110\",\"average_price\":1.31075,\"client_id\":1111,\"/
|
114
|
+
|
115
|
+
IB::Execution.new.from_json(subject.to_json).should == subject
|
99
116
|
end
|
100
|
-
|
101
117
|
end
|
102
118
|
|
103
119
|
end # describe IB::Models::Contract
|
@@ -50,17 +50,18 @@ describe IB::Models::Order do
|
|
50
50
|
:sweep_to_fill, :override_percentage_constraints, :all_or_none,
|
51
51
|
:etrade_only, :firm_quote_only, :opt_out_smart_routing, :scale_auto_reset,
|
52
52
|
:scale_random_percent] => boolean_assigns,
|
53
|
+
|
54
|
+
[:local_id, :perm_id, :parent_id] => numeric_or_nil_assigns,
|
53
55
|
}
|
54
56
|
end
|
55
57
|
|
56
58
|
let(:aliases) do
|
57
59
|
{[:side, :action] => buy_sell_short_assigns,
|
58
|
-
[:local_id, :order_id] => numeric_or_nil_assigns,
|
59
60
|
[:quantity, :total_quantity] => numeric_or_nil_assigns,
|
60
61
|
}
|
61
62
|
end
|
62
63
|
|
63
|
-
let(:
|
64
|
+
let(:collections) do
|
64
65
|
{:order_states => [IB::OrderState.new(:status => :Foo),
|
65
66
|
IB::OrderState.new(:status => 'Bar'),],
|
66
67
|
|
@@ -127,16 +128,17 @@ describe IB::Models::Order do
|
|
127
128
|
subject.init_margin.should be_nil
|
128
129
|
subject.maint_margin.should be_nil
|
129
130
|
subject.equity_with_loan.should be_nil
|
130
|
-
# Properties arriving via OrderStatus
|
131
|
-
subject.filled.should
|
132
|
-
subject.remaining.should
|
133
|
-
subject.price.should
|
134
|
-
subject.last_fill_price.should
|
135
|
-
subject.average_price.should
|
136
|
-
subject.average_fill_price.should
|
131
|
+
# Properties arriving via OrderStatus message
|
132
|
+
subject.filled.should == 0
|
133
|
+
subject.remaining.should == 0
|
134
|
+
subject.price.should == 0
|
135
|
+
subject.last_fill_price.should == 0
|
136
|
+
subject.average_price.should == 0
|
137
|
+
subject.average_fill_price.should == 0
|
137
138
|
subject.why_held.should be_nil
|
138
|
-
# Testing Order
|
139
|
+
# Testing Order state
|
139
140
|
subject.should be_new
|
141
|
+
subject.should_not be_submitted
|
140
142
|
subject.should_not be_pending
|
141
143
|
subject.should be_active
|
142
144
|
subject.should_not be_inactive
|
@@ -38,13 +38,14 @@ describe IB::Models::OrderState do
|
|
38
38
|
let(:assigns) do
|
39
39
|
{[:status] =>
|
40
40
|
{[nil, ''] => /must not be empty/,
|
41
|
-
['Zorro', :Zorro] => 'Zorro'}
|
41
|
+
['Zorro', :Zorro] => 'Zorro'},
|
42
|
+
|
43
|
+
:local_id => numeric_or_nil_assigns,
|
42
44
|
}
|
43
45
|
end
|
44
46
|
|
45
47
|
let(:aliases) do
|
46
|
-
{[:
|
47
|
-
[:price, :last_fill_price] => float_or_nil_assigns,
|
48
|
+
{[:price, :last_fill_price] => float_or_nil_assigns,
|
48
49
|
[:average_price, :average_fill_price] => float_or_nil_assigns,
|
49
50
|
}
|
50
51
|
end
|
@@ -53,9 +54,11 @@ describe IB::Models::OrderState do
|
|
53
54
|
it_behaves_like 'Self-equal Model'
|
54
55
|
|
55
56
|
context '#update_missing' do
|
57
|
+
let(:nil_state) { IB::OrderState.new(:filled => nil, :remaining => nil,
|
58
|
+
:price => nil, :average_price => nil) }
|
56
59
|
context 'updating with Hash' do
|
57
60
|
|
58
|
-
subject {
|
61
|
+
subject { nil_state.update_missing(props) }
|
59
62
|
|
60
63
|
it_behaves_like 'Model instantiated with properties'
|
61
64
|
|
@@ -63,7 +66,7 @@ describe IB::Models::OrderState do
|
|
63
66
|
|
64
67
|
context 'updating with Model' do
|
65
68
|
|
66
|
-
subject {
|
69
|
+
subject { nil_state.update_missing(IB::OrderState.new(props)) }
|
67
70
|
|
68
71
|
it_behaves_like 'Model instantiated with properties'
|
69
72
|
|
@@ -74,6 +77,8 @@ describe IB::Models::OrderState do
|
|
74
77
|
it 'has extra test methods' do
|
75
78
|
empty_state = IB::OrderState.new
|
76
79
|
empty_state.should be_new
|
80
|
+
subject.should_not be_pending
|
81
|
+
subject.should_not be_submitted
|
77
82
|
empty_state.should be_active
|
78
83
|
empty_state.should_not be_inactive
|
79
84
|
empty_state.should_not be_complete_fill
|
@@ -86,6 +91,7 @@ describe IB::Models::OrderState do
|
|
86
91
|
state.should_not be_inactive
|
87
92
|
state.should_not be_complete_fill
|
88
93
|
state.should be_pending
|
94
|
+
status == 'PendingSubmit' ? state.should_not(be_submitted) : state.should(be_submitted)
|
89
95
|
end
|
90
96
|
|
91
97
|
['PendingCancel', 'Cancelled', 'ApiCancelled', 'Inactive'].each do |status|
|
@@ -95,6 +101,7 @@ describe IB::Models::OrderState do
|
|
95
101
|
state.should be_inactive
|
96
102
|
state.should_not be_complete_fill
|
97
103
|
state.should_not be_pending
|
104
|
+
subject.should_not be_submitted
|
98
105
|
end
|
99
106
|
|
100
107
|
state.status = 'Filled'
|
@@ -103,6 +110,7 @@ describe IB::Models::OrderState do
|
|
103
110
|
state.should be_inactive
|
104
111
|
state.should_not be_complete_fill
|
105
112
|
state.should_not be_pending
|
113
|
+
subject.should_not be_submitted
|
106
114
|
|
107
115
|
state.remaining = 0
|
108
116
|
state.should_not be_new
|
@@ -110,6 +118,7 @@ describe IB::Models::OrderState do
|
|
110
118
|
state.should be_inactive
|
111
119
|
state.should be_complete_fill
|
112
120
|
state.should_not be_pending
|
121
|
+
subject.should_not be_submitted
|
113
122
|
end
|
114
123
|
|
115
124
|
end # describe IB::Order
|
data/spec/model_helper.rb
CHANGED
@@ -190,6 +190,7 @@ end
|
|
190
190
|
shared_examples_for 'Model instantiated with properties' do
|
191
191
|
it 'auto-assigns all properties given to initializer' do
|
192
192
|
props.each do |name, value|
|
193
|
+
#p subject, name, value
|
193
194
|
subject.send(name).should == value
|
194
195
|
end
|
195
196
|
end
|
@@ -200,6 +201,18 @@ end
|
|
200
201
|
|
201
202
|
shared_examples_for 'Model properties' do
|
202
203
|
|
204
|
+
it 'leaves order_id alone, no aliasing' do
|
205
|
+
if subject.respond_to?(:order_id)
|
206
|
+
subject.order_id.should be_nil
|
207
|
+
if subject.respond_to?(:local_id=)
|
208
|
+
subject.local_id = 1313
|
209
|
+
subject.order_id.should be_nil
|
210
|
+
subject.order_id = 2222
|
211
|
+
subject.local_id.should == 1313
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
203
216
|
it 'allows setting properties' do
|
204
217
|
expect {
|
205
218
|
props.each do |name, value|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: ib-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.7.
|
5
|
+
version: 0.7.10
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Paul Legato
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2012-04-
|
14
|
+
date: 2012-04-25 00:00:00 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -77,7 +77,7 @@ dependencies:
|
|
77
77
|
- - ">="
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: "0"
|
80
|
-
type: :
|
80
|
+
type: :runtime
|
81
81
|
version_requirements: *id006
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
83
|
name: database_cleaner
|
@@ -248,6 +248,14 @@ files:
|
|
248
248
|
- tasks/git.rake
|
249
249
|
- tasks/spec.rake
|
250
250
|
- tasks/version.rake
|
251
|
+
- db/migrate/101_add_executions.rb
|
252
|
+
- db/migrate/111_add_bars.rb
|
253
|
+
- db/migrate/121_add_order_states.rb
|
254
|
+
- db/migrate/131_add_orders.rb
|
255
|
+
- db/migrate/141_add_combo_legs.rb
|
256
|
+
- db/migrate/151_add_underlyings.rb
|
257
|
+
- db/migrate/161_add_contract_details.rb
|
258
|
+
- db/migrate/171_add_contracts.rb
|
251
259
|
- Rakefile
|
252
260
|
- README.md
|
253
261
|
- LICENSE
|