ib-ruby 0.7.9 → 0.7.10

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -173,3 +173,7 @@
173
173
  == 0.7.9 / 2012-04-24
174
174
 
175
175
  * Order and OrderState API extended
176
+
177
+ == 0.7.10 / 2012-04-25
178
+
179
+ * Small fixes for DB backend and serialization
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.9
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
@@ -22,8 +22,8 @@ module IB
22
22
  end
23
23
 
24
24
  def default_attributes
25
- {:legs => Array.new,
26
- :sec_type => :bag}.merge super
25
+ super.merge :legs => Array.new,
26
+ :sec_type => :bag
27
27
  end
28
28
 
29
29
  def description
@@ -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
- {: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, }.merge super
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
- {:con_id => 0,
99
- :strike => 0.0,
100
- :right => :none, # Not an option
101
- :exchange => 'SMART',
102
- :include_expired => false, }.merge super
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
- {: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, }.merge super
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 [:local_id, :order_id], # int: order id. TWS orders have a fixed order id of 0.
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
- {:local_id => 0,
34
- :client_id => 0,
35
- :quantity => 0,
36
- :price => 0,
37
- :perm_id => 0,
38
- :liquidation => false, }.merge super
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
@@ -21,7 +21,7 @@ module IB
21
21
  end
22
22
  end
23
23
 
24
- attr_accessor :created_at, :updated_at, :attributes
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
- attrs = default_attributes.merge(opts)
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
- when '' # default getter and setter
86
- define_property_methods name
87
-
88
- when Array # [setter, getter, validators]
89
- define_property_methods name,
90
- :get => body[0],
91
- :set => body[1],
92
- :validate => body[2]
93
-
94
- when Hash # recursion base case
95
- getter = case # Define getter
96
- when body[:get].respond_to?(:call)
97
- body[:get]
98
- when body[:get]
99
- proc { self[name].send "to_#{body[:get]}" }
100
- when VALUES[name] # property is encoded
101
- proc { VALUES[name][self[name]] }
102
- #when respond_to?(:column_names) && column_names.include?(name.to_s)
103
- # # noop, ActiveRecord will take care of it...
104
- # p "#{name} => get noop"
105
- # p respond_to?(:column_names) && column_names
106
- else
107
- proc { self[name] }
108
- end
109
- define_method name, &getter if getter
110
-
111
- setter = case # Define setter
112
- when body[:set].respond_to?(:call)
113
- body[:set]
114
- when body[:set]
115
- proc { |value| self[name] = value.send "to_#{body[:set]}" }
116
- when CODES[name] # property is encoded
117
- proc { |value| self[name] = CODES[name][value] || value }
118
- else
119
- proc { |value| self[name] = value } # p name, value;
120
- end
121
- define_method "#{name}=", &setter if setter
122
-
123
- # Define validator(s)
124
- [body[:validate]].flatten.compact.each do |validator|
125
- case validator
126
- when Proc
127
- validates_each name, &validator
128
- when Hash
129
- validates name, validator.dup
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
- else # setter given
136
- define_property_methods name, :set => body, :get => body
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
@@ -49,7 +49,7 @@ module IB
49
49
  end
50
50
 
51
51
  def default_attributes
52
- {:sec_type => :option}.merge super
52
+ super.merge :sec_type => :option
53
53
  #self[:description] ||= osi ? osi : "#{symbol} #{strike} #{right} #{expiry}"
54
54
  end
55
55
 
@@ -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 [:local_id, :order_id], # int: Order id associated with client (volatile).
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
- when IB::OrderState
242
- state
243
- when Symbol, String
244
- IB::OrderState.new :status => state
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 (:order_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
- {:aux_price => 0.0,
286
- :discretionary_amount => 0.0,
287
- :parent_id => 0,
288
- :tif => :day,
289
- :order_type => :limit,
290
- :open_close => :open,
291
- :origin => :customer,
292
- :short_sale_slot => :default,
293
- :trigger_method => :default,
294
- :oca_type => :none,
295
- :auction_strategy => :none,
296
- :designated_location => '',
297
- :exempt_code => -1,
298
- :display_size => 0,
299
- :continuous_update => 0,
300
- :delta_neutral_con_id => 0,
301
- :algo_strategy => '',
302
- :transmit => true,
303
- :what_if => false,
304
- :order_state => IB::OrderState.new(:status => 'New'),
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
- when :short
323
- 'SSHORT'
324
- when :short_exempt
325
- 'SSHORTX'
326
- else
327
- side.to_sup
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 order_id.
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 [:local_id, :order_id], # int: Order id associated with client (volatile).
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
- status == 'PendingSubmit' || status == 'PreSubmitted' || status == 'Submitted'
86
+ submitted? || status == 'PendingSubmit'
75
87
  end
76
88
 
77
89
  # Order is in invalid state
@@ -16,7 +16,7 @@ module IB
16
16
  validates_numericality_of :con_id, :delta, :price #, :allow_nil => true
17
17
 
18
18
  def default_attributes
19
- {:con_id => 0}.merge super
19
+ super.merge :con_id => 0
20
20
  end
21
21
 
22
22
  # Serialize under_comp parameters
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
- owner_name = described_class.to_s.demodulize.tableize.singularize
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
- if proxy.collection?
85
- association = subject.send("#{assoc}")
86
- association << item
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
- #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
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] => numeric_assigns,
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
- ## TODO: Playing with associations!
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 if IB::DB }
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
- p subject.save
73
- p subject.errors.messages
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
- p subject.order
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
- p IB::Execution.new.from_json subject.to_json # TODO: Strings for keys!
81
+ execution.should == subject
84
82
 
85
- pending 'Still need to test associations properly'
83
+ execution.order.should == order
84
+ order.executions.first.should == execution
86
85
  end
86
+ end
87
87
 
88
- it 'loads associated execution' do
89
- pending 'Still need to test associations properly'
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
- #p o1 = IB::Order.find(:first)
96
- #p o1.execution
97
- #
98
- #p o1.execution.order_id
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(:associations) do
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 messagesubject.
131
- subject.filled.should be_nil
132
- subject.remaining.should be_nil
133
- subject.price.should be_nil
134
- subject.last_fill_price.should be_nil
135
- subject.average_price.should be_nil
136
- subject.average_fill_price.should be_nil
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 statesubject.
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
- {[:local_id, :order_id] => numeric_or_nil_assigns,
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 { IB::OrderState.new.update_missing(props) }
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 { IB::OrderState.new.update_missing(IB::OrderState.new(props)) }
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.9
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-24 00:00:00 Z
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: :development
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