Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 12 commits
  • 33 files changed
  • 0 commit comments
  • 1 contributor
View
8 HISTORY
@@ -177,3 +177,11 @@
== 0.7.10 / 2012-04-25
* Small fixes for DB backend and serialization
+
+== 0.7.11 / 2012-04-25
+
+* DB schema extended
+
+== 0.7.12 / 2012-04-27
+
+* ComboLeg is a join Model
View
62 README.md
@@ -122,26 +122,6 @@ The sample scripts in `bin` directory provide examples of how common tasks
can be achieved using ib-ruby. You may also want to look into `spec/integration`
directory for more scenarios and examples of handling IB messages.
-## RUNNING TESTS:
-
-The gem comes with a spec suit that may be used to test ib-ruby compatibility
-with your specific TWS/Gateway installation. The test suit should be run ONLY
-against your IB paper trading account. Running it against live account may result
-in financial losses.
-
-In order to run tests, you should set up your IB paper trading connection parameters
-in 'spec/spec_helper' file. Modify account_name, host and port under section
-'Your IB PAPER ACCOUNT'. Do not change the client_id.
-
-Before running tests, you need to start your TWS/Gateway and allow API connection.
-You should not have any open/pending orders on your IB paper trading account prior
-to running tests, otherwise some tests will fail. Use 'bin/cancel_orders' script for
-bulk cancelling of open orders before running tests as needed.
-
-You can easily create your own tests following the guide in 'spec/README'.
-Help the development! See 'spec/TODO' for the list of use cases/scenarios
-that still need to be tested.
-
## DB BACKEND:
Latest versions of the gem added (optional and experimental) support for data
@@ -168,6 +148,48 @@ you don't need IB::DB.connect part, Rails will take care of it for you. So, just
Now, all your IB Models are just ActiveRecords and you can do whatever you want with them:
persist to DB, use in Rails applications, develop controllers and views.
+
+## RUNNING TESTS:
+
+The gem comes with a spec suit that may be used to test ib-ruby compatibility
+with your specific TWS/Gateway installation. The test suit should be run ONLY
+against your IB paper trading account. Running it against live account may result
+in financial losses.
+
+In order to run tests, you should set up your IB paper trading connection parameters
+in 'spec/spec_helper' file. Modify account_name, host and port under section
+'Your IB PAPER ACCOUNT'. Do not change the client_id.
+
+Before running tests, you need to start your TWS/Gateway and allow API connection.
+You should not have any open/pending orders on your IB paper trading account prior
+to running tests, otherwise some tests will fail. Use 'bin/cancel_orders' script for
+bulk cancelling of open orders before running tests as needed.
+
+By default, specs are run without database support (tableless). In order to run them
+with database backend, use:
+
+ $ rspec -rdb [spec/specific_spec.rb]
+
+Also, by default, specs suppress logging output that is normally produced by IB::Connection.
+This may make it difficult to debug a failing spec. Following option will switch on verbose
+output (both logger output and content of all received IB messages is dumped). Do not use
+this mode to run a whole spec - you will be swamped! Use it to debug specific failing specs
+only:
+
+ $ rspec -rv [spec/specific_spec.rb]
+
+You can easily create your own tests following the guide in 'spec/README'.
+Help the development! See 'spec/TODO' for the list of use cases/scenarios
+that still need to be tested.
+
+## CONTRIBUTING:
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
+
## LICENSE:
This software is available under the LGPL.
View
22 TODO
@@ -1,24 +1,18 @@
Plan:
-0. Extract OrderState/UnderComp objects, for better record keeping
-
-1. Add ActiveRecord backend to all Models
-
-2. Make ActiveModel-like attributes Hash for easy attributes updating
-
-3. IB#send_message method should accept block, thus compressing subscribe/send_message
+1. IB#send_message method should accept block, thus compressing subscribe/send_message
pair into a single call - to simplify DSL.
-4. IB Connection uses delays to prevent hitting 50 msgs/sec limit:
+2. IB Connection uses delays to prevent hitting 50 msgs/sec limit:
http://finance.groups.yahoo.com/group/TWSAPI/message/25413
-5. IB Connection reconnects gracefully in case if TWS disconnects/reconnects
+3. IB Connection reconnects gracefully in case if TWS disconnects/reconnects
-6. @received_at timestamp in messages
+4. Messages should be Models as well (audit trail), @received_at timestamp in messages
-7. Detailed message documentation
+5. Detailed message documentation
-8. Move Float values to Decimal (roundoff errors showed in spec!)
+6. Move Float values to Decimal (roundoff errors showed in spec!)
Done:
@@ -33,6 +27,10 @@ Done:
5. Flow handlers: Connection#wait_for / Connection#received?
+6. Add ActiveRecord backend to all Models
+
+7. Make ActiveModel-like attributes Hash and serialization for tableless Models
+
Ideas for future:
View
2 VERSION
@@ -1 +1 @@
-0.7.10
+0.7.12
View
12 db/migrate/121_add_order_states.rb
@@ -13,15 +13,15 @@ def change
t.integer :remaining
t.float :price # double
t.float :average_price # double
- t.float :init_margin # Float: The impact the order would have on your initial margin.
- t.float :maint_margin # Float: The impact the order would have on your maintenance margin.
- t.float :equity_with_loan # Float: The impact the order would have on your equity
+ t.string :why_held # String: comma-separated list of reasons for order to be held.
+ t.string :warning_text # String: Displays a warning message if warranted.
+ t.string :commission_currency, :limit => 4 # String: Shows the currency of the commission.
t.float :commission # double: Shows the commission amount on the order.
t.float :min_commission # The possible min range of the actual order commission.
t.float :max_commission # The possible max range of the actual order commission.
- t.string :commission_currency, :limit => 4 # String: Shows the currency of the commission.
- t.string :why_held # String: comma-separated list of reasons for order to be held.
- t.string :warning_text # String: Displays a warning message if warranted.
+ t.float :init_margin # Float: The impact the order would have on your initial margin.
+ t.float :maint_margin # Float: The impact the order would have on your maintenance margin.
+ t.float :equity_with_loan # Float: The impact the order would have on your equity
t.timestamps
end
end
View
1 db/migrate/131_add_orders.rb
@@ -3,6 +3,7 @@ class AddOrders < ActiveRecord::Migration
def change
# OrderState represents dynamic (changeable) info about a single Order
create_table(:orders) do |t|
+ t.references :contract # Optional link of Order to its contract
t.integer :local_id # int: Order id associated with client (volatile).
t.integer :client_id # int: The id of the client that placed this order.
View
13 db/migrate/141_add_combo_legs.rb
@@ -3,15 +3,16 @@ class AddComboLegs < ActiveRecord::Migration
def change
# ComboLeg objects represent individual security legs in a "BAG"
create_table(:combo_legs) do |t|
- t.references :contract
+ t.references :combo
+ t.references :leg_contract
t.integer :con_id # # int: The unique contract identifier specifying the security.
- t.integer :ratio # int: Select the relative number of contracts for the leg you
- t.string :exchange # String: exchange to which the complete combo order will be routed.
t.string :side, :limit => 1 # Action/side: BUY/SELL/SSHORT/SSHORTX
- t.integer :exempt_code # int:
- t.integer :short_sale_slot # int: 0 - retail(default), 1 = clearing broker, 2 = third party
+ t.integer :ratio, :limit => 2 # int: Select the relative number of contracts for the leg you
+ t.string :exchange # String: exchange to which the complete combo order will be routed.
+ t.integer :exempt_code, :limit => 2 # int:
+ t.integer :short_sale_slot, :limit => 2 # int: 0 - retail(default), 1 = clearing broker, 2 = third party
+ t.integer :open_close, :limit => 2 # SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3
t.string :designated_location # Otherwise leave blank or orders will be rejected.:status # String: Displays the order status.Possible values include:
- t.integer :open_close # SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3
t.timestamps
end
end
View
26 db/schema.rb
@@ -27,15 +27,16 @@
end
create_table "combo_legs", :force => true do |t|
- t.integer "contract_id"
+ t.integer "combo_id"
+ t.integer "leg_contract_id"
t.integer "con_id"
- t.integer "ratio"
- t.string "exchange"
t.string "side", :limit => 1
- t.integer "exempt_code"
- t.integer "short_sale_slot"
+ t.integer "ratio", :limit => 2
+ t.string "exchange"
+ t.integer "exempt_code", :limit => 2
+ t.integer "short_sale_slot", :limit => 2
+ t.integer "open_close", :limit => 2
t.string "designated_location"
- t.integer "open_close"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
@@ -128,20 +129,21 @@
t.integer "remaining"
t.float "price"
t.float "average_price"
- t.float "init_margin"
- t.float "maint_margin"
- t.float "equity_with_loan"
+ t.string "why_held"
+ t.string "warning_text"
+ t.string "commission_currency", :limit => 4
t.float "commission"
t.float "min_commission"
t.float "max_commission"
- t.string "commission_currency", :limit => 4
- t.string "why_held"
- t.string "warning_text"
+ t.float "init_margin"
+ t.float "maint_margin"
+ t.float "equity_with_loan"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "orders", :force => true do |t|
+ t.integer "contract_id"
t.integer "local_id"
t.integer "client_id"
t.integer "perm_id"
View
4 ib-ruby.gemspec
@@ -23,8 +23,8 @@ Gem::Specification.new do |gem|
# Dependencies
gem.add_dependency 'bundler', '>= 1.1.3'
gem.add_dependency 'activerecord', '>= 0.0.1'
- gem.add_dependency 'activerecord-jdbcsqlite3-adapter', '>= 1.2.2'
- gem.add_dependency 'jdbc-sqlite3', '>= 3.7.2'
+ #gem.add_dependency 'activerecord-jdbcsqlite3-adapter', '>= 1.2.2'
+ #gem.add_dependency 'jdbc-sqlite3', '>= 3.7.2'
gem.add_dependency 'xml-simple', '>= 1.1.1'
gem.add_dependency 'standalone_migrations'
#gem.add_dependency 'pg', '>= 0.12.1'
View
9 lib/ib-ruby/models/bag.rb
@@ -15,15 +15,10 @@ class Bag < Contract
validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
validates_format_of :right, :with => /^none$/, :message => "should be none"
validates_format_of :expiry, :with => /^$/, :message => "should be blank"
- validate :legs_cannot_be_empty
-
- def legs_cannot_be_empty
- errors.add(:legs, "legs cannot be empty") if legs.empty?
- end
def default_attributes
- super.merge :legs => Array.new,
- :sec_type => :bag
+ super.merge :sec_type => :bag #,:legs => Array.new,
+
end
def description
View
12 lib/ib-ruby/models/combo_leg.rb
@@ -1,13 +1,15 @@
module IB
module Models
- # ComboLeg objects represent individual securities in a "BAG" contract - which
- # is not really a contract, but a combination (combo) of securities. AKA basket
- # or bag of securities.
+ # ComboLeg is essentially a join Model between Combo (BAG) Contract and
+ # individual Contracts (securities) that this BAG contains.
class ComboLeg < Model.for(:combo_leg)
include ModelProperties
- belongs_to :contract
+ # BAG Combo Contract that contains this Leg
+ belongs_to :combo, :class_name => 'Contract'
+ # Contract that constitutes this Leg
+ belongs_to :leg_contract, :class_name => 'Contract', :foreign_key => :leg_contract_id
# General Notes:
# 1. The exchange for the leg definition must match that of the combination order.
@@ -40,6 +42,8 @@ class ComboLeg < Model.for(:combo_leg)
def default_attributes
super.merge :con_id => 0,
+ :ratio => 1,
+ :side => :buy,
:open_close => :same, # The only option for retail customers.
:short_sale_slot => :default,
:designated_location => '',
View
47 lib/ib-ruby/models/contract.rb
@@ -49,14 +49,14 @@ class Contract < Model.for(:contract)
{:set => proc { |val|
self[:right] =
case val.to_s.upcase
- when 'NONE', '', '0', '?'
- ''
- when 'PUT', 'P'
- 'P'
- when 'CALL', 'C'
- 'C'
- else
- val
+ when 'NONE', '', '0', '?'
+ ''
+ when 'PUT', 'P'
+ 'P'
+ when 'CALL', 'C'
+ 'C'
+ else
+ val
end },
:validate => {:format => {:with => /^put$|^call$|^none$/,
:message => "should be put, call or none"}}
@@ -66,18 +66,27 @@ class Contract < Model.for(:contract)
### Associations
- has_one :contract_detail
+ has_many :orders # Placed for this Contract
- has_one :underlying # for Delta-Neutral Combo contracts only!
- alias under_comp underlying
- alias under_comp= underlying=
+ has_one :contract_detail # Volatile info about this Contract
+
+ # For Contracts that are part of BAG
+ has_one :leg, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
+ has_one :combo, :class_name => 'Contract', :through => :leg
- has_many :combo_legs
+ # for Combo/BAG Contracts that contain ComboLegs
+ has_many :combo_legs, :foreign_key => :combo_id
+ has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
alias legs combo_legs
alias legs= combo_legs=
alias combo_legs_description legs_description
alias combo_legs_description= legs_description=
+ # for Delta-Neutral Combo Contracts
+ has_one :underlying
+ alias under_comp underlying
+ alias under_comp= underlying=
+
### Extra validations
validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
@@ -140,12 +149,12 @@ def serialize_under_comp *args
# Defined in Contract, not BAG subclass to keep code DRY
def serialize_legs *fields
case
- when !bag?
- []
- when legs.empty?
- [0]
- else
- [legs.size, legs.map { |leg| leg.serialize *fields }].flatten
+ when !bag?
+ []
+ when legs.empty?
+ [0]
+ else
+ [legs.size, legs.map { |leg| leg.serialize *fields }].flatten
end
end
View
6 lib/ib-ruby/models/model.rb
@@ -25,11 +25,11 @@ def self.for subclass
# If a opts hash is given, keys are taken as attribute names, values as data.
# The model instance fields are then set automatically from the opts Hash.
- def initialize opts={}
+ def initialize attributes={}, opts={}
run_callbacks :initialize do
- error "Argument must be a Hash", :args unless opts.is_a?(Hash)
+ error "Argument must be a Hash", :args unless attributes.is_a?(Hash)
- self.attributes = default_attributes.merge(opts)
+ self.attributes = default_attributes.merge(attributes)
end
end
View
100 lib/ib-ruby/models/model_properties.rb
@@ -75,65 +75,65 @@ def self.define_property names, body
def self.define_property_methods name, body={}
#p name, body
case body
- when '' # default getter and setter
- define_property_methods name
-
- when Array # [setter, getter, validators]
- define_property_methods name,
- :get => body[0],
- :set => body[1],
- :validate => body[2]
-
- when Hash # recursion base case
- getter = case # Define getter
- when body[:get].respond_to?(:call)
- body[:get]
- when body[:get]
- proc { self[name].send "to_#{body[:get]}" }
- when VALUES[name] # property is encoded
- proc { VALUES[name][self[name]] }
- #when respond_to?(:column_names) && column_names.include?(name.to_s)
- # # noop, ActiveRecord will take care of it...
- # p "#{name} => get noop"
- # p respond_to?(:column_names) && column_names
- else
- proc { self[name] }
- end
- define_method name, &getter if getter
-
- setter = case # Define setter
- when body[:set].respond_to?(:call)
- body[:set]
- when body[:set]
- proc { |value| self[name] = value.send "to_#{body[:set]}" }
- when CODES[name] # property is encoded
- proc { |value| self[name] = CODES[name][value] || value }
- else
- proc { |value| self[name] = value } # p name, value;
- end
- define_method "#{name}=", &setter if setter
-
- # Define validator(s)
- [body[:validate]].flatten.compact.each do |validator|
- case validator
- when Proc
- validates_each name, &validator
- when Hash
- validates name, validator.dup
- end
+ when '' # default getter and setter
+ define_property_methods name
+
+ when Array # [setter, getter, validators]
+ define_property_methods name,
+ :get => body[0],
+ :set => body[1],
+ :validate => body[2]
+
+ when Hash # recursion base case
+ getter = case # Define getter
+ when body[:get].respond_to?(:call)
+ body[:get]
+ when body[:get]
+ proc { self[name].send "to_#{body[:get]}" }
+ when VALUES[name] # property is encoded
+ proc { VALUES[name][self[name]] }
+ #when respond_to?(:column_names) && column_names.include?(name.to_s)
+ # # noop, ActiveRecord will take care of it...
+ # p "#{name} => get noop"
+ # p respond_to?(:column_names) && column_names
+ else
+ proc { self[name] }
+ end
+ define_method name, &getter if getter
+
+ setter = case # Define setter
+ when body[:set].respond_to?(:call)
+ body[:set]
+ when body[:set]
+ proc { |value| self[name] = value.send "to_#{body[:set]}" }
+ when CODES[name] # property is encoded
+ proc { |value| self[name] = CODES[name][value] || value }
+ else
+ proc { |value| self[name] = value } # p name, value;
+ end
+ define_method "#{name}=", &setter if setter
+
+ # Define validator(s)
+ [body[:validate]].flatten.compact.each do |validator|
+ case validator
+ when Proc
+ validates_each name, &validator
+ when Hash
+ validates name, validator.dup
end
+ end
# TODO define self[:name] accessors for :virtual and :flag properties
- else # setter given
- define_property_methods name, :set => body, :get => body
+ else # setter given
+ define_property_methods name, :set => body, :get => body
end
end
# Extending AR-backed Model class with attribute defaults
if defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
- def initialize opts={}
- super default_attributes.merge(opts)
+ def initialize attributes={}, opts={}
+ super default_attributes.merge(attributes), opts
end
else
# Timestamps
View
2 lib/ib-ruby/models/option.rb
@@ -7,7 +7,7 @@ class Option < Contract
validates_numericality_of :strike, :greater_than => 0
validates_format_of :sec_type, :with => /^option$/,
:message => "should be an option"
- validates_format_of :local_symbol, :with => /^\w+\s*\d{15}$|^$/,
+ validates_format_of :local_symbol, :with => /^\w+\s*\d{6}[pcPC]\d{8}$|^$/,
:message => "invalid OSI code"
validates_format_of :right, :with => /^put$|^call$/,
:message => "should be put or call"
View
10 lib/ib-ruby/models/order.rb
@@ -227,6 +227,10 @@ class Order < Model.for(:order)
##serialize :leg_prices
##serialize :combo_params
+ # Order is always placed for a contract. Here, we explicitly set this link.
+ belongs_to :contract
+
+ # Order has a collection of Executions if it was filled
has_many :executions
# Order has a collection of OrderStates, last one is always current
@@ -302,7 +306,11 @@ def default_attributes
:algo_strategy => '',
:transmit => true,
:what_if => false,
- :order_state => IB::OrderState.new(:status => 'New')
+ :order_state => IB::OrderState.new(:status => 'New',
+ :filled => 0,
+ :remaining => 0,
+ :price => 0,
+ :average_price => 0)
end
#after_initialize do #opts = {}
View
9 lib/ib-ruby/models/order_state.rb
@@ -63,14 +63,7 @@ class OrderState < Model.for(:order_state)
validates_numericality_of :local_id, :perm_id, :client_id, :parent_id, :filled,
:remaining, :only_integer => true, :allow_nil => true
- def default_attributes
- super.merge :filled => 0,
- :remaining => 0,
- :price => 0.0,
- :average_price => 0.0
- end
-
- ## Testing Order state:
+ ## Testing Order state:
def new?
status.empty? || status == 'New'
View
54 spec/README.md
@@ -1,11 +1,40 @@
-# WRITING INTEGRATION SPECS
+## RUNNING TESTS:
-Pattern for writing integration specs is like this:
+The gem comes with a spec suit that may be used to test ib-ruby compatibility
+with your specific TWS/Gateway installation. The test suit should be run ONLY
+against your IB paper trading account. Running it against live account may result
+in financial losses.
-1. You define your user scenario (such as: subscribe for FUTURES market data).
+In order to run tests, you should set up your IB paper trading connection parameters
+in 'spec/spec_helper' file. Modify account_name, host and port under section
+'Your IB PAPER ACCOUNT'. Do not change the client_id.
-2. You find out experimentally, what messages should be sent to IB to accomplish it,
- and what messages are sent by IB in return.
+Before running tests, you need to start your TWS/Gateway and allow API connection.
+You should not have any open/pending orders on your IB paper trading account prior
+to running tests, otherwise some tests will fail. Use 'bin/cancel_orders' script for
+bulk cancelling of open orders before running tests as needed.
+
+By default, specs are run without database support (tableless). In order to run them
+with database backend, use:
+
+ $ rspec -rdb [spec/specific_spec.rb]
+
+Also, by default, specs suppress logging output that is normally produced by IB::Connection.
+This may make it difficult to debug a failing spec. Following option will switch on verbose
+output (both logger output and content of all received IB messages is dumped). Do not use
+this mode to run a whole spec - you will be swamped! Use it to debug specific failing specs
+only:
+
+ $ rspec -rv [spec/specific_spec.rb]
+
+# WRITING YOUR OWN INTEGRATION SPECS
+
+You can easily create your own integration specs. Pattern for writing specs is like this:
+
+1. You define your user scenario (such as: subscribe to FUTURES market data).
+
+2. You find out from documentation or experimentally, what messages should be sent to
+ IB to accomplish it, and what messages are sent by IB in return.
3. You start writing spec, requiring 'integration_helper'. Don't forget to
'verify_account'! Running tests against live IB account can be pretty painful.
@@ -25,10 +54,10 @@ Pattern for writing integration specs is like this:
@ib.received Hash, keyed by message type. The Hash has following structure:
{:MessageType1 => [msg1, msg2, msg3...], :MessageType2 => [msg1, msg2, msg3...] }.
-7. If you created @ib Connection with mock_logger, all log entries produced by IB
+7. If you created @ib Connection with a mock_logger, all log entries produced by IB
will be also caught and placed into log_entries Array.
-8. Your examples can thus test the content of @ib.received Hash to see what messages
+8. Your examples can thus check the content of @ib.received Hash to see what messages
were received, or log_entries Array to see what was logged.
9. When done with this context, you call 'close_connection' helper in a top-level
@@ -36,13 +65,14 @@ Pattern for writing integration specs is like this:
10. If you reuse the connection between contexts and requests, it is recommended to
call 'clean_connection' in after block to remove old content from @ib.received Hash,
- or otherwise manually clean it to remove old/not needed messages from it.
+ and log_entries Array or otherwise manually clean them to remove old/not needed
+ messages from and log entries. If your do not do this, your examples become coupled.
11. If you want to see exactly what's going on inside ib-ruby while your examples are
- running, set OPTS[:silent] = false in your context, and you'll see all the
- messages received and log entries made as as result of your examples. Be warned,
- output is very verbose, so don't forget to switch OPTS[:silent] = true after
- you've done debugging your examples.
+ running, run your specs with '-rv' option to switch on verbose outpset mode.
+ Now you will see all the messages received and log entries made as as result of
+ your examples running. Be warned, output is very verbose, so don't run big chunk of
+ specs with -rv option or you will be swamped!.
Help the development!
See 'spec/TODO' file for list of scenarios that still need to be tested.
View
2 spec/TODO
@@ -3,8 +3,8 @@ TODO: Add more scenarios:
1. RealTimeBars
2. BondContractData
3. RequestScannerParameters + RequestScannerSubscription
-4. RequestFundamentalData
5. ExerciseOptions
6. RequestMarketData + special tick list
7. RequestNewsBulletins
8. RequestImpliedVolatility / RequestOptionPrice
+9. Test more associations/associated collections
View
111 spec/db_helper.rb
@@ -32,11 +32,13 @@
end
it 'and with the same properties' do
- model = described_class.find(:first)
- #p model.attributes
- #p model.content_attributes
- props.each do |name, value|
- model.send(name).should == value
+ if init_with_props?
+ model = described_class.find(:first)
+ #p model.attributes
+ #p model.content_attributes
+ props.each do |name, value|
+ model.send(name).should == value
+ end
end
end
@@ -75,79 +77,80 @@
shared_examples_for 'Model with associations' do
it 'works with associations, if any' do
- if defined? associations
- subject_name_plural = described_class.to_s.demodulize.tableize
+ subject_name_plural = described_class.to_s.demodulize.tableize
- associations.each do |name, item|
- puts "Testing single association #{name}"
- subject.association(name).reflection.should_not be_collection
+ associations.each do |name, item_props|
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
+ #item = const_get("IB::#{name.to_s.classify}").new item_props
+ puts "Testing single association #{name}"
+ subject.association(name).reflection.should_not be_collection
- # Assign item to association
- expect { subject.send "#{name}=", item }.to_not raise_error
+ # Assign item to association
+ expect { subject.send "#{name}=", item }.to_not raise_error
- association = subject.send name #, :reload
- association.should == item
- association.should be_new_record
+ association = subject.send name #, :reload
+ association.should == item
+ association.should be_new_record
- # Reverse association does not include subject
- reverse_association = association.send(subject_name_plural)
- reverse_association.should be_empty
+ # Reverse association does not include subject
+ reverse_association = association.send(subject_name_plural)
+ reverse_association.should be_empty
- # Now let's save subject
- if subject.valid?
- subject.save
+ # Now let's save subject
+ if subject.valid?
+ subject.save
- association = subject.send name
- association.should_not be_new_record
+ association = subject.send name
+ association.should_not be_new_record
- # Reverse association now DOES include subject (if reloaded!)
- reverse_association = association.send(subject_name_plural, :reload)
- reverse_association.should include subject
- end
+ # Reverse association now DOES include subject (if reloaded!)
+ reverse_association = association.send(subject_name_plural, :reload)
+ reverse_association.should include subject
end
end
end
it 'works with associated collections, if any' do
- if defined? collections
-
- subject_name = described_class.to_s.demodulize.tableize.singularize
+ subject_name = described_class.to_s.demodulize.tableize.singularize
- collections.each do |name, items|
- puts "Testing associated collection #{name}"
- subject.association(name).reflection.should be_collection
+ collections.each do |name, items|
+ puts "Testing associated collection #{name}"
+ subject.association(name).reflection.should be_collection
- [items].flatten.each do |item|
- association = subject.send name #, :reload
+ [items].flatten.each do |item_props|
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
+ #item = item_class.new item_props
+ association = subject.send name #, :reload
- # Add item to collection
- expect { association << item }.to_not raise_error
- association.should include item
+ # Add item to collection
+ expect { association << item }.to_not raise_error
+ association.should include item
- # Reverse association does NOT point to subject
- reverse_association = association.first.send(subject_name)
- #reverse_association.should be_nil # But not always!
+ # Reverse association does NOT point to subject
+ reverse_association = association.first.send(subject_name)
+ #reverse_association.should be_nil # But not always!
- #association.size.should == items.size # Not for Order, +1 OrderState
- end
+ #association.size.should == items.size # Not for Order, +1 OrderState
+ end
- # Now let's save subject
- if subject.valid?
- subject.save
+ # Now let's save subject
+ if subject.valid?
+ subject.save
- [items].flatten.each do |item|
- association = subject.send name #, :reload
+ [items].flatten.each do |item_props|
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
+ association = subject.send name #, :reload
- association.should include item
+ association.should include item
- # Reverse association DOES point to subject now
- reverse_association = association.first.send(subject_name)
- reverse_association.should == subject
- end
+ # Reverse association DOES point to subject now
+ reverse_association = association.first.send(subject_name)
+ reverse_association.should == subject
end
-
end
end
end
+
+
end
View
89 spec/ib-ruby/models/bag_spec.rb
@@ -1,69 +1,56 @@
require 'model_helper'
-describe IB::Models::Bag do # AKA IB::Bag
-
- let(:props) do
- {:symbol => 'GOOG',
- :exchange => 'SMART',
- :currency => 'USD',
- :legs => [IB::ComboLeg.new(:con_id => 81032967, :weight => 1),
- IB::ComboLeg.new(:con_id => 81032968, :weight => -2),
- IB::ComboLeg.new(:con_id => 81032973, :weight => 1)]
- }
- end
+describe IB::Models::Bag,
- let(:human) do
- "<Bag: GOOG SMART USD legs: 81032967|1,81032968|-2,81032973|1 >"
- end
+ :props =>
+ {:symbol => 'GOOG',
+ :exchange => 'SMART',
+ :currency => 'USD',
+ :legs => [IB::ComboLeg.new(:con_id => 81032967, :weight => 1),
+ IB::ComboLeg.new(:con_id => 81032968, :weight => -2),
+ IB::ComboLeg.new(:con_id => 81032973, :weight => 1)]
+ },
- let(:errors) do
- {:legs => ["legs cannot be empty"],
- }
- end
+ :human => "<Bag: GOOG SMART USD legs: 81032967|1,81032968|-2,81032973|1 >",
- let(:assigns) do
- {:expiry =>
- {[nil, ''] => '',
- [20060913, '20060913', 200609, '200609', :foo, 2006, 42, 'bar'] =>
- /should be blank/},
+ :errors =>
+ {:legs => ["legs cannot be empty"]},
- :sec_type =>
- {['BAG', :bag] => :bag,
- IB::CODES[:sec_type].reject { |k, _| k == :bag }.to_a =>
- /should be a bag/},
+ :assigns =>
+ {:expiry =>
+ {[nil, ''] => '',
+ [20060913, '20060913', 200609, '200609', 2006, :foo, 'bar'] =>
+ /should be blank/},
- :right =>
- {['?', :none, '', '0'] => :none,
- ["PUT", :put, "CALL", "C", :call, :foo, 'BAR', 42] =>
- /should be none/},
+ :sec_type =>
+ {['BAG', :bag] => :bag,
+ IB::CODES[:sec_type].reject { |k, _| k == :bag }.to_a =>
+ /should be a bag/},
- :exchange => string_upcase_assigns.merge(
- [:smart, 'SMART', 'smArt'] => 'SMART'),
+ :right =>
+ {['?', :none, '', '0'] => :none,
+ ["PUT", :put, "CALL", "C", :call, :foo, 'BAR', 42] =>
+ /should be none/},
- :primary_exchange =>string_upcase_assigns.merge(
- [:SMART, 'SMART'] => /should not be SMART/),
+ :exchange => string_upcase_assigns.merge(
+ [:smart, 'SMART', 'smArt'] => 'SMART'),
- [:symbol, :local_symbol] => string_assigns,
+ :primary_exchange =>string_upcase_assigns.merge(
+ [:SMART, 'SMART'] => /should not be SMART/),
- :multiplier => to_i_assigns,
- }
- end
+ [:symbol, :local_symbol] => string_assigns,
- it 'does not allow empty legs' do
- bag = IB::Bag.new props
- bag.legs = []
- bag.should be_invalid
- bag.errors.messages[:legs].should include "legs cannot be empty"
- end
+ :multiplier => to_i_assigns,
+ } do # AKA IB::Bag
- context 'using shortest class name without properties' do
- subject { IB::Bag.new }
- it_behaves_like 'Model instantiated empty'
- end
-
- it_behaves_like 'Model'
+ it_behaves_like 'Model with valid defaults'
it_behaves_like 'Self-equal Model'
+ it 'has class name shortcut' do
+ IB::Bag.should == IB::Models::Bag
+ IB::Bag.new.should == IB::Models::Bag.new
+ end
+
context 'properly initiated' do
subject { IB::Bag.new props }
View
71 spec/ib-ruby/models/bar_spec.rb
@@ -1,40 +1,41 @@
require 'model_helper'
-describe IB::Models::Bar do # AKA IB::Bar
-
- let(:props) do
- {:open => 1.31,
- :high => 1.35,
- :low => 1.30,
- :close => 1.33,
- :wap => 1.32,
- :volume => 20000,
- :has_gaps => true,
- :trades => 50,
- :time => "20120312 15:41:09",
- }
+describe IB::Models::Bar,
+ :props =>
+ {:open => 1.31,
+ :high => 1.35,
+ :low => 1.30,
+ :close => 1.33,
+ :wap => 1.32,
+ :volume => 20000,
+ :has_gaps => true,
+ :trades => 50,
+ :time => "20120312 15:41:09",
+ },
+
+ :human =>
+ "<Bar: 20120312 15:41:09 wap 1.32 OHLC 1.31 1.35 1.3 1.33 trades 50 vol 20000 gaps true>",
+
+ :errors =>
+ {:close => ["is not a number"],
+ :high => ["is not a number"],
+ :low => ["is not a number"],
+ :open => ["is not a number"],
+ :volume => ["is not a number"]},
+
+ :assigns =>
+ {:has_gaps => {[1, true] => true, [0, false] => false},
+
+ [:open, :high, :low, :close, :volume] =>
+ {[:foo, 'BAR', nil] => /is not a number/}
+ } do # AKA IB::Bar
+
+ it_behaves_like 'Model with invalid defaults'
+ it_behaves_like 'Self-equal Model'
+
+ it 'has class name shortcut' do
+ IB::Bar.should == IB::Models::Bar
+ IB::Bar.new.should == IB::Models::Bar.new
end
- let(:errors) do
- {:close => ["is not a number"],
- :high => ["is not a number"],
- :low => ["is not a number"],
- :open => ["is not a number"],
- :volume => ["is not a number"]}
- end
-
- let(:assigns) do
- {:has_gaps => {[1, true] => true, [0, false] => false},
-
- [:open, :high, :low, :close, :volume] =>
- {[:foo, 'BAR', nil] => /is not a number/}
- }
- end
-
- let(:human) do
- "<Bar: 20120312 15:41:09 wap 1.32 OHLC 1.31 1.35 1.3 1.33 trades 50 vol 20000 gaps true>"
- end
-
- it_behaves_like 'Model'
-
end # describe IB::Bar
View
139 spec/ib-ruby/models/combo_leg_spec.rb
@@ -1,39 +1,31 @@
require 'model_helper'
-describe IB::Models::ComboLeg do
-
- let(:props) do
- {:con_id => 81032967,
- :ratio => 2,
- :side => :buy,
- :exchange => 'CBOE',
- :open_close => :open,
- :short_sale_slot => :broker,
- :designated_location => nil,
- :exempt_code => -1}
- end
+describe IB::Models::ComboLeg,
+ :props =>
+ {:con_id => 81032967,
+ :ratio => 2,
+ :side => :buy,
+ :exchange => 'CBOE',
+ :open_close => :open,
+ :short_sale_slot => :broker,
+ :designated_location => nil,
+ :exempt_code => -1},
- let(:human) do
- "<ComboLeg: buy 2 con_id 81032967 at CBOE>"
- end
+ :human => "<ComboLeg: buy 2 con_id 81032967 at CBOE>",
- let(:errors) do
- {:ratio => ["is not a number"],
- :side => ["should be buy/sell/short"]}
- end
+ :errors => {:ratio => ["is not a number"],
+ :side => ["should be buy/sell/short"]},
- let(:assigns) do
- {:open_close => open_close_assigns,
- :side => buy_sell_short_assigns,
- :designated_location =>
- {[42, 'FOO', :bar] => /should be blank or orders will be rejected/},
- }
- end
+ :assigns =>
+ {:open_close => open_close_assigns,
+ :side => buy_sell_short_assigns,
+ :designated_location =>
+ {[42, 'FOO', :bar] => /should be blank or orders will be rejected/},
+ },
- let(:aliases) do
- {[:side, :action] => buy_sell_short_assigns,
- }
- end
+ :aliases =>
+ {[:side, :action] => buy_sell_short_assigns,
+ } do
it 'has combined weight accessor' do
leg = IB::ComboLeg.new props
@@ -45,9 +37,15 @@
leg.ratio.should == 5
end
- it_behaves_like 'Model'
+ it_behaves_like 'Model with valid defaults'
it_behaves_like 'Self-equal Model'
+ it 'has class name shortcut' do
+ IB::ComboLeg.should == IB::Models::ComboLeg
+ IB::ComboLeg.new.should == IB::Models::ComboLeg.new
+ end
+
+
context "serialization" do
subject { IB::ComboLeg.new props }
@@ -61,4 +59,83 @@
end
end #serialization
+ context 'DB backed associations', :db => true do
+ before(:all) { DatabaseCleaner.clean }
+
+ #before(:all) do
+ # @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
+ # @ib.wait_for :ManagedAccounts
+ # @butterfly = butterfly 'GOOG', '201301', 'CALL', 500, 510, 520
+ # close_connection
+ #end
+
+ before(:each) do
+ @combo = IB::Bag.new
+
+ @google = IB::Option.new(:symbol => 'GOOG',
+ :expiry => 201301,
+ :right => :call,
+ :strike => 500)
+ end
+
+ it 'saves associated BAG and leg Contract' do
+
+ combo_leg = IB::ComboLeg.new :con_id => 454, :weight => 3
+ combo_leg.should be_valid
+
+ # Assigning both associations for a join model
+ combo_leg.combo = @combo
+ combo_leg.leg_contract = @google
+
+ combo_leg.save.should == true
+ @combo.should_not be_new_record
+ @google.should_not be_new_record
+
+ combo_leg.combo.should == @combo
+ combo_leg.leg_contract.should == @google
+ @combo.legs.should include combo_leg
+ @combo.leg_contracts.should include @google
+ @google.leg.should == combo_leg
+ @google.combo.should == @combo
+
+ end
+
+ it 'loads ComboLeg together with associated BAG and leg Contract' do
+
+ combo_leg = IB::ComboLeg.where(:con_id => 454).first
+ combo_leg.should be_valid
+
+ #p combo_leg.combo.attributes
+ #p combo_leg.combo.strike
+ #p combo_leg.combo.include_expired
+
+ combo = combo_leg.combo
+ google = combo_leg.leg_contract
+ #combo.should == @combo # NOT equal, different legs
+ google.should == @google
+
+ combo.legs.should include combo_leg
+ combo.leg_contracts.should include google
+ google.leg.should == combo_leg
+ google.combo.should == combo
+ end
+
+
+ it 'creates ComboLeg indirectly through associated BAG and leg Contract' do
+
+ @combo.leg_contracts << @google
+ @combo.leg_contracts.should include @google
+ p @combo.valid?
+ p @combo.save
+
+ #combo.legs.should_not be_empty
+ @combo.leg_contracts.should include @google
+ @google.combo.should == @combo
+
+ leg = @combo.legs.first
+ @google.leg.should == leg
+
+ end
+ end
+
end # describe IB::Models::Contract::ComboLeg
View
86 spec/ib-ruby/models/contract_detail_spec.rb
@@ -1,54 +1,46 @@
require 'model_helper'
-describe IB::Models::ContractDetail do # AKA IB::ContractDetail
-
- let(:props) do
- {:market_name => 'AAPL',
- :trading_class => 'AAPL',
- :min_tick => 0.01,
- :price_magnifier => 1,
- :order_types => 'ACTIVETIM,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,POSTONLY,RELSTK,SCALE,SCALERST,SMARTSTG,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF,',
- :valid_exchanges => 'SMART,AMEX,BATS,BOX,CBOE,CBOE2,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE', # The list of exchanges this contract is traded on.
- :under_con_id => 265598,
- :long_name => 'APPLE INC',
- :contract_month => '201301',
- :industry => 'Technology',
- :category => 'Computers',
- :subcategory => 'Computers',
- :time_zone => 'EST',
- :trading_hours => '20120422:0930-1600;20120423:0930-1600',
- :liquid_hours => '20120422:0930-1600;20120423:0930-1600',
- }
- end
-
- let(:human) do
- /<ContractDetail: callable: false category: Computers contract_month: 201301 convertible: false coupon: 0.0 .*industry: Technology liquid_hours: 20120422:0930-1600;20120423:0930-1600 long_name: APPLE INC market_name: AAPL min_tick: 0.01 next_option_partial: false order_types: ACTIVETIM,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,POSTONLY,RELSTK,SCALE,SCALERST,SMARTSTG,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF, price_magnifier: 1 puttable: false subcategory: Computers time_zone: EST trading_class: AAPL trading_hours: 20120422:0930-1600;20120423:0930-1600 under_con_id: 265598 .*valid_exchanges: SMART,AMEX,BATS,BOX,CBOE,CBOE2,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE>/
- end
-
- let(:errors) do
- {:time_zone => ['should be XXX'],
- }
- end
-
- let(:assigns) do
- {[:under_con_id, :min_tick, :coupon] => {123 => 123},
-
- [:callable, :puttable, :convertible, :next_option_partial] => boolean_assigns,
- }
- end
-
- let(:aliases) do
- {[:contract, :summary] => {IB::Contract.new => IB::Contract.new}
- }
- end
-
- it_behaves_like 'Model'
+describe IB::Models::ContractDetail,
+
+ :props =>
+ {:market_name => 'AAPL',
+ :trading_class => 'AAPL',
+ :min_tick => 0.01,
+ :price_magnifier => 1,
+ :order_types => 'ACTIVETIM,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,POSTONLY,RELSTK,SCALE,SCALERST,SMARTSTG,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF,',
+ :valid_exchanges => 'SMART,AMEX,BATS,BOX,CBOE,CBOE2,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE', # The list of exchanges this contract is traded on.
+ :under_con_id => 265598,
+ :long_name => 'APPLE INC',
+ :contract_month => '201301',
+ :industry => 'Technology',
+ :category => 'Computers',
+ :subcategory => 'Computers',
+ :time_zone => 'EST',
+ :trading_hours => '20120422:0930-1600;20120423:0930-1600',
+ :liquid_hours => '20120422:0930-1600;20120423:0930-1600',
+ },
+
+ :human =>/<ContractDetail: callable: false category: Computers contract_month: 201301 convertible: false coupon: 0.0 .*industry: Technology liquid_hours: 20120422:0930-1600;20120423:0930-1600 long_name: APPLE INC market_name: AAPL min_tick: 0.01 next_option_partial: false order_types: ACTIVETIM,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,POSTONLY,RELSTK,SCALE,SCALERST,SMARTSTG,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF, price_magnifier: 1 puttable: false subcategory: Computers time_zone: EST trading_class: AAPL trading_hours: 20120422:0930-1600;20120423:0930-1600 under_con_id: 265598 .*valid_exchanges: SMART,AMEX,BATS,BOX,CBOE,CBOE2,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE>/,
+
+ :errors =>
+ {:time_zone => ['should be XXX'],
+ },
+
+ :assigns =>
+ {[:under_con_id, :min_tick, :coupon] => {123 => 123},
+ [:callable, :puttable, :convertible, :next_option_partial] => boolean_assigns,
+ },
+
+ :aliases =>
+ {[:contract, :summary] => {IB::Contract.new => IB::Contract.new}
+ } do # AKA IB::ContractDetail
+
+ it_behaves_like 'Model with invalid defaults'
it_behaves_like 'Self-equal Model'
- context 'using shortest class name without properties' do
- subject { IB::ContractDetail.new }
- it_behaves_like 'Model instantiated empty'
- it_behaves_like 'Self-equal Model'
+ it 'has class name shortcut' do
+ IB::ContractDetail.should == IB::Models::ContractDetail
+ IB::ContractDetail.new.should == IB::Models::ContractDetail.new
end
end # describe IB::Contract
View
124 spec/ib-ruby/models/contract_spec.rb
@@ -1,69 +1,66 @@
require 'model_helper'
require 'combo_helper'
-describe IB::Models::Contract do # AKA IB::Contract
-
- let(:props) do
- {:symbol => 'AAPL',
- :sec_type => :option,
- :expiry => '201301',
- :strike => 600.5,
- :right => :put,
- :multiplier => 10,
- :exchange => 'SMART',
- :currency => 'USD',
- :local_symbol => 'AAPL 130119C00500000'}
- end
-
- let(:human) do
- "<Contract: AAPL option 201301 put 600.5 SMART USD>"
- end
-
- let(:errors) do
- {:sec_type => ["should be valid security type"],
- }
- end
-
- let(:assigns) do
- {:expiry =>
- {[200609, '200609'] => '200609',
- [20060913, '20060913'] => '20060913',
- [:foo, 2006, 42, 'bar'] => /should be YYYYMM or YYYYMMDD/},
-
- :sec_type => codes_and_values_for(:sec_type).
- merge([:foo, 'BAR', 42] => /should be valid security type/),
-
- :sec_id_type =>
- {[:isin, 'ISIN', 'iSin'] => 'ISIN',
- [:sedol, :SEDOL, 'sEDoL', 'SEDOL'] => 'SEDOL',
- [:cusip, :CUSIP, 'Cusip', 'CUSIP'] => 'CUSIP',
- [:ric, :RIC, 'rIC', 'RIC'] => 'RIC',
- [nil, ''] => '',
- [:foo, 'BAR', 'baz'] => /should be valid security identifier/},
-
- :right =>
- {["PUT", "put", "P", "p", :put] => :put,
- ["CALL", "call", "C", "c", :call] => :call,
- ['', '0', '?', :none] => :none,
- [:foo, 'BAR', 42] => /should be put, call or none/},
-
- :exchange => string_upcase_assigns.merge(
- [:smart, 'SMART', 'smArt'] => 'SMART'),
-
- :primary_exchange =>string_upcase_assigns.merge(
- [:SMART, 'SMART'] => /should not be SMART/),
-
- :multiplier => to_i_assigns,
-
- :strike => to_f_assigns,
+describe IB::Models::Contract,
+ :props =>
+ {:symbol => 'AAPL',
+ :sec_type => :option,
+ :expiry => '201301',
+ :strike => 600.5,
+ :right => :put,
+ :multiplier => 10,
+ :exchange => 'SMART',
+ :currency => 'USD',
+ :local_symbol => 'AAPL 130119C00500000'},
+
+ :human => "<Contract: AAPL option 201301 put 600.5 SMART USD>",
+
+ :errors => {:sec_type => ["should be valid security type"] },
+
+ :assigns =>
+ {:expiry =>
+ {[200609, '200609'] => '200609',
+ [20060913, '20060913'] => '20060913',
+ [:foo, 2006, 42, 'bar'] => /should be YYYYMM or YYYYMMDD/},
+
+ :sec_type => codes_and_values_for(:sec_type).
+ merge([:foo, 'BAR', 42] => /should be valid security type/),
+
+ :sec_id_type =>
+ {[:isin, 'ISIN', 'iSin'] => 'ISIN',
+ [:sedol, :SEDOL, 'sEDoL', 'SEDOL'] => 'SEDOL',
+ [:cusip, :CUSIP, 'Cusip', 'CUSIP'] => 'CUSIP',
+ [:ric, :RIC, 'rIC', 'RIC'] => 'RIC',
+ [nil, ''] => '',
+ [:foo, 'BAR', 'baz'] => /should be valid security identifier/},
+
+ :right =>
+ {["PUT", "put", "P", "p", :put] => :put,
+ ["CALL", "call", "C", "c", :call] => :call,
+ ['', '0', '?', :none] => :none,
+ [:foo, 'BAR', 42] => /should be put, call or none/},
+
+ :exchange => string_upcase_assigns.merge(
+ [:smart, 'SMART', 'smArt'] => 'SMART'),
+
+ :primary_exchange =>string_upcase_assigns.merge(
+ [:SMART, 'SMART'] => /should not be SMART/),
+
+ :multiplier => to_i_assigns,
+
+ :strike => to_f_assigns,
+
+ :include_expired => boolean_assigns,
+ } do # AKA IB::Contract
+
+ it_behaves_like 'Model with invalid defaults'
+ it_behaves_like 'Self-equal Model'
- :include_expired => boolean_assigns,
- }
+ it 'has class name shortcut' do
+ IB::Contract.should == IB::Models::Contract
+ IB::Contract.new.should == IB::Models::Contract.new
end
- it_behaves_like 'Model'
- it_behaves_like 'Self-equal Model'
-
context 'testing for Contract type (sec_type)' do
it 'correctly defines Contract type (sec_type) for Option contract' do
@@ -114,13 +111,6 @@
end
- context 'using shortest class name without properties' do
- subject { IB::Contract.new }
- it_behaves_like 'Model instantiated empty'
- it_behaves_like 'Self-equal Model'
- it_behaves_like 'Contract'
- end
-
context "serialization" do
before(:all) do
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
View
109 spec/ib-ruby/models/execution_spec.rb
@@ -1,69 +1,66 @@
require 'model_helper'
-describe IB::Models::Execution do # AKA IB::Execution
-
- let(:props) do
- {:account_name => "DU111110",
- :client_id => 1111,
- :exchange => "IDEALPRO",
- :exec_id => "0001f4e8.4f5d48f1.01.01",
- :liquidation => true,
- :local_id => 373,
- :perm_id => 1695693619,
- :price => 1.31075,
- :average_price => 1.31075,
- :shares => 20000,
- :cumulative_quantity => 20000,
- :side => :buy,
- :time => "20120312 15:41:09",
- }
+describe IB::Models::Execution,
+ :props =>
+ {:account_name => "DU111110",
+ :client_id => 1111,
+ :exchange => "IDEALPRO",
+ :exec_id => "0001f4e8.4f5d48f1.01.01",
+ :liquidation => true,
+ :local_id => 373,
+ :perm_id => 1695693619,
+ :price => 1.31075,
+ :average_price => 1.31075,
+ :shares => 20000,
+ :cumulative_quantity => 20000,
+ :side => :buy,
+ :time => "20120312 15:41:09",
+ },
+ :human =>
+ "<Execution: 20120312 15:41:09 buy 20000 at 1.31075 on IDEALPRO, " +
+ "cumulative 20000 at 1.31075, ids 373/1695693619/0001f4e8.4f5d48f1.01.01>",
+ :errors =>
+ {:side=>["should be buy/sell/short"],
+ :cumulative_quantity=>["is not a number"],
+ :average_price=>["is not a number"]},
+
+ :assigns =>
+ {[:local_id, :perm_id, :client_id, :cumulative_quantity, :price, :average_price] =>
+ numeric_assigns,
+ :liquidation => boolean_assigns,
+ },
+
+ :aliases =>
+ {[:side, :action] => buy_sell_assigns,
+ [:quantity, :shares] => numeric_assigns,
+ [:account_name, :account_number]=> string_assigns,
+ },
+
+ :associations =>
+ {:order => {:local_id => 23,
+ :perm_id => 173276893,
+ :client_id => 1111,
+ :parent_id => 0,
+ :quantity => 100,
+ :side => :buy,
+ :order_type => :market}
+ } do # AKA IB::Execution
+
+ it_behaves_like 'Model with invalid defaults'
+ it_behaves_like 'Self-equal Model'
+
+ it 'has class name shortcut' do
+ IB::Execution.should == IB::Models::Execution
+ IB::Execution.new.should == IB::Models::Execution.new
end
- let(:human) do
- "<Execution: 20120312 15:41:09 buy 20000 at 1.31075 on IDEALPRO, " +
- "cumulative 20000 at 1.31075, ids 373/1695693619/0001f4e8.4f5d48f1.01.01>"
- end
-
- let(:errors) do
- {:side=>["should be buy/sell/short"],
- :cumulative_quantity=>["is not a number"],
- :average_price=>["is not a number"]}
- end
-
- let(:assigns) do
- {[:local_id, :perm_id, :client_id, :cumulative_quantity, :price, :average_price] =>
- numeric_assigns,
- :liquidation => boolean_assigns,
- }
- end
-
- let(:aliases) do
- {[:side, :action] => buy_sell_assigns,
- [:quantity, :shares] => numeric_assigns,
- [:account_name, :account_number]=> string_assigns,
- }
- end
-
- let(:associations) do
- {:order => IB::Order.new(:local_id => 23,
- :perm_id => 173276893,
- :client_id => 1111,
- :parent_id => 0,
- :quantity => 100,
- :side => :buy,
- :order_type => :market)
- }
- end
-
- it_behaves_like 'Model'
-
context 'DB backed associations', :db => true do
subject { IB::Execution.new props }
before(:all) { DatabaseCleaner.clean }
it 'saves associated order' do
- order = associations[:order]
+ order = IB::Order.new associations[:order]
subject.order = order
subject.order.should == order
subject.order.should be_new_record
View
96 spec/ib-ruby/models/option_spec.rb
@@ -1,66 +1,59 @@
require 'model_helper'
-describe IB::Models::Option do # AKA IB::Option
-
- let(:props) do
- {:symbol => 'AAPL',
- :expiry => '201301',
- :strike => 600.5,
- :right => :put,
- }
- end
+describe IB::Models::Option,
+ :human => "<Option: AAPL 201301 put 600.5 SMART >",
- let(:human) do
- "<Option: AAPL 201301 put 600.5 SMART >"
- end
+ :errors => {:right => ["should be put or call"],
+ :strike => ["must be greater than 0"],
+ },
- let(:errors) do
- {:right => ["should be put or call"],
- :strike => ["must be greater than 0"],
- }
- end
+ :props => {:symbol => 'AAPL',
+ :expiry => '201301',
+ :strike => 600.5,
+ :right => :put,
+ },
- let(:assigns) do
- {:expiry =>
- {[200609, '200609'] => '200609',
- [20060913, '20060913'] => '20060913',
- [:foo, 2006, 42, 'bar'] => /should be YYYYMM or YYYYMMDD/},
+ :assigns => {
+ :local_symbol =>
+ {['AAPL 130119C00500000',
+ :'AAPL 130119C00500000'] => 'AAPL 130119C00500000',
+ 'BAR'=> /invalid OSI code/},
- :sec_type =>
- {['OPT', :option] => :option,
- IB::CODES[:sec_type].reject { |k, _| k == :option }.to_a =>
- /should be an option/},
+ :expiry =>
+ {[200609, '200609'] => '200609',
+ [20060913, '20060913'] => '20060913',
+ [:foo, 2006, 42, 'bar'] => /should be YYYYMM or YYYYMMDD/},
- :right =>
- {["PUT", "put", "P", "p", :put] => :put,
- ["CALL", "call", "C", "c", :call] => :call,
- ['', '0', '?', :none, :foo, 'BAR', 42] => /should be put or call/},
+ :sec_type =>
+ {['OPT', :option] => :option,
+ IB::CODES[:sec_type].reject { |k, _| k == :option }.to_a =>
+ /should be an option/},
- :exchange => string_upcase_assigns.merge(
- [:smart, 'SMART', 'smArt'] => 'SMART'),
+ :right =>
+ {["PUT", "put", "P", "p", :put] => :put,
+ ["CALL", "call", "C", "c", :call] => :call,
+ ['', '0', '?', :none, :foo, 'BAR', 42] => /should be put or call/},
- :primary_exchange =>string_upcase_assigns.merge(
- [:SMART, 'SMART'] => /should not be SMART/),
+ :exchange => string_upcase_assigns.merge(
+ [:smart, 'SMART', 'smArt'] => 'SMART'),
- :multiplier => to_i_assigns,
+ :primary_exchange =>string_upcase_assigns.merge(
+ [:SMART, 'SMART'] => /should not be SMART/),
- :symbol => string_assigns,
+ :multiplier => to_i_assigns,
- :local_symbol =>
- {['AAPL 130119C00500000', :'AAPL 130119C00500000'] => 'AAPL 130119C00500000',
- 'BAR'=> /invalid OSI code/},
-
- :strike => {[0, -30.0] => /must be greater than 0/},
- }
- end
+ :symbol => string_assigns,
- context 'using shortest class name without properties' do
- subject { IB::Option.new }
- it_behaves_like 'Model instantiated empty'
- end
+ :strike => {[0, -30.0] => /must be greater than 0/},
+ } do # AKA IB::Option
- it_behaves_like 'Model'
it_behaves_like 'Self-equal Model'
+ it_behaves_like 'Model with invalid defaults'
+
+ it 'has class name shortcut' do
+ IB::Option.should == IB::Models::Option
+ IB::Option.new.should == IB::Models::Option.new
+ end
context 'properly initiated' do
subject { IB::Option.new props }
@@ -86,6 +79,13 @@
end
end
+ it 'correctly validates OSI symbol' do
+ o = IB::Option.new props
+ o.should be_valid
+ o.local_symbol = "AAPL 130119C00500000"
+ o.should be_valid
+ end
+
context '.from_osi class builder' do
subject { IB::Option.from_osi 'AAPL130119C00500000' }
View
188 spec/ib-ruby/models/order_spec.rb
@@ -1,98 +1,96 @@
require 'model_helper'
-describe IB::Models::Order do
-
- let(:props) do
- {:local_id => 23,
- :order_ref => 'Test',
- :client_id => 1111,
- :perm_id => 173276893,
- :parent_id => 0,
- :side => :buy,
- :order_type => :market_if_touched,
- :limit_price => 0.1,
- :quantity => 100,
- :tif => :good_till_cancelled,
- :open_close => :close,
- :oca_group => '',
- :oca_type => :reduce_no_block,
- :origin => :firm,
- :designated_location => "WHATEVER",
- :exempt_code => 123,
- :delta_neutral_order_type => :market,
- :transmit => false,
- :outside_rth => true,
- :what_if => true,
- :not_held => true}
- end
-
- # TODO: :presents => { Object => "Formatted"}
- let(:human) do
- "<Order: Test MIT GTC buy 100 New 0.1 #23/173276893 from 1111>"
- end
-
- let(:errors) do
- {:side =>["should be buy/sell/short"]}
- end
-
- let(:assigns) do
- {[:order_type, :delta_neutral_order_type] => codes_and_values_for(:order_type),
-
- :open_close =>
- {[42, nil, 'Foo', :bar] => /should be same.open.close.unknown/,
- ['SAME', 'same', 'S', 's', :same, 0, '0'] => :same,
- ['OPEN', 'open', 'O', 'o', :open, 1, '1'] => :open,
- ['CLOSE', 'close', 'C', 'c', :close, 2, '2'] => :close,
- ['UNKNOWN', 'unknown', 'U', 'u', :unknown, 3, '3'] => :unknown,
- },
+describe IB::Models::Order,
+ :props =>
+ {:local_id => 23,
+ :order_ref => 'Test',
+ :client_id => 1111,
+ :perm_id => 173276893,
+ :parent_id => 0,
+ :side => :buy,
+ :order_type => :market_if_touched,
+ :limit_price => 0.1,
+ :quantity => 100,
+ :tif => :good_till_cancelled,
+ :open_close => :close,
+ :oca_group => '',
+ :oca_type => :reduce_no_block,
+ :origin => :firm,
+ :designated_location => "WHATEVER",
+ :exempt_code => 123,
+ :delta_neutral_order_type => :market,
+ :transmit => false,
+ :outside_rth => true,
+ :what_if => true,
+ :not_held => true},
+
+ # TODO: :presents => { Object => "Formatted"}
+ :human => "<Order: Test MIT GTC buy 100 New 0.1 #23/173276893 from 1111>",
+
+ :errors => {:side =>["should be buy/sell/short"]},
+
+ :assigns =>
+ {[:order_type, :delta_neutral_order_type] => codes_and_values_for(:order_type),
+
+ :open_close =>
+ {[42, nil, 'Foo', :bar] => /should be same.open.close.unknown/,
+ ['SAME', 'same', 'S', 's', :same, 0, '0'] => :same,
+ ['OPEN', 'open', 'O', 'o', :open, 1, '1'] => :open,
+ ['CLOSE', 'close', 'C', 'c', :close, 2, '2'] => :close,
+ ['UNKNOWN', 'unknown', 'U', 'u', :unknown, 3, '3'] => :unknown,
+ },
+
+ [:what_if, :not_held, :outside_rth, :hidden, :transmit, :block_order,
+ :sweep_to_fill, :override_percentage_constraints, :all_or_none,
+ :etrade_only, :firm_quote_only, :opt_out_smart_routing, :scale_auto_reset,
+ :scale_random_percent] => boolean_assigns,
+
+ [:local_id, :perm_id, :parent_id] => numeric_or_nil_assigns,
+ },
+
+ :aliases =>
+ {[:side, :action] => buy_sell_short_assigns,
+ [:quantity, :total_quantity] => numeric_or_nil_assigns,
+ },
+
+ :collections =>
+ {:order_states =>[{:status => :Foo},
+ {:status => 'Bar'},],
+
+ :executions =>
+ [{:local_id => 23,
+ :client_id => 1111,
+ :perm_id => 173276893,
+ :exchange => "IDEALPRO",
+ :exec_id => "0001f4e8.4f5d48f1.01.01",
+ :price => 0.1,
+ :average_price => 0.1,
+ :shares => 40,
+ :cumulative_quantity => 40,
+ :side => :buy,
+ :time => "20120312 15:41:09"},
+
+ {:local_id => 23,
+ :client_id => 1111,
+ :perm_id => 173276893,
+ :exchange => "IDEALPRO",
+ :exec_id => "0001f4e8.4f5d48f1.01.02",
+ :price => 0.1,
+ :average_price => 0.1,
+ :shares => 60,
+ :cumulative_quantity => 100,
+ :side => :buy,
+ :time => "20120312 15:41:10"}]
+ } do
- [:what_if, :not_held, :outside_rth, :hidden, :transmit, :block_order,
- :sweep_to_fill, :override_percentage_constraints, :all_or_none,
- :etrade_only, :firm_quote_only, :opt_out_smart_routing, :scale_auto_reset,
- :scale_random_percent] => boolean_assigns,
-
- [:local_id, :perm_id, :parent_id] => numeric_or_nil_assigns,
- }
- end
-
- let(:aliases) do
- {[:side, :action] => buy_sell_short_assigns,
- [:quantity, :total_quantity] => numeric_or_nil_assigns,
- }
- end
+ it_behaves_like 'Self-equal Model'
+ it_behaves_like 'Model with invalid defaults'
- let(:collections) do
- {:order_states => [IB::OrderState.new(:status => :Foo),
- IB::OrderState.new(:status => 'Bar'),],
-
- :executions => [IB::Execution.new(:local_id => 23,
- :client_id => 1111,
- :perm_id => 173276893,
- :exchange => "IDEALPRO",
- :exec_id => "0001f4e8.4f5d48f1.01.01",
- :price => 0.1,
- :average_price => 0.1,
- :shares => 40,
- :cumulative_quantity => 40,
- :side => :buy,
- :time => "20120312 15:41:09"),
- IB::Execution.new(:local_id => 23,
- :client_id => 1111,
- :perm_id => 173276893,
- :exchange => "IDEALPRO",
- :exec_id => "0001f4e8.4f5d48f1.01.02",
- :price => 0.1,
- :average_price => 0.1,
- :shares => 60,
- :cumulative_quantity => 100,
- :side => :buy,
- :time => "20120312 15:41:10")]
- }
+ it 'has class name shortcut' do
+ IB::Order.should == IB::Models::Order
+ IB::Order.new.should == IB::Models::Order.new
end
- it_behaves_like 'Model'
- it_behaves_like 'Self-equal Model'
-
context 'Order associations' do
after(:all) { DatabaseCleaner.clean }
@@ -129,12 +127,12 @@
subject.maint_margin.should be_nil
subject.equity_with_loan.should be_nil
# Properties arriving via OrderStatus message
- subject.filled.should == 0
- subject.remaining.should == 0
- subject.price.should == 0
- subject.last_fill_price.should == 0
- subject.average_price.should == 0
- subject.average_fill_price.should == 0
+ subject.filled.should == 0
+ subject.remaining.should == 0
+ subject.price.should == 0
+ subject.last_fill_price.should == 0
+ subject.average_price.should == 0
+ subject.average_fill_price.should == 0
subject.why_held.should be_nil
# Testing Order state