ib-ruby 0.7.11 → 0.7.12

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -181,3 +181,7 @@
181
181
  == 0.7.11 / 2012-04-25
182
182
 
183
183
  * DB schema extended
184
+
185
+ == 0.7.12 / 2012-04-27
186
+
187
+ * ComboLeg is a join Model
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.11
1
+ 0.7.12
@@ -13,15 +13,15 @@ class AddOrderStates < ActiveRecord::Migration
13
13
  t.integer :remaining
14
14
  t.float :price # double
15
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
16
+ t.string :why_held # String: comma-separated list of reasons for order to be held.
17
+ t.string :warning_text # String: Displays a warning message if warranted.
18
+ t.string :commission_currency, :limit => 4 # String: Shows the currency of the commission.
19
19
  t.float :commission # double: Shows the commission amount on the order.
20
20
  t.float :min_commission # The possible min range of the actual order commission.
21
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.
22
+ t.float :init_margin # Float: The impact the order would have on your initial margin.
23
+ t.float :maint_margin # Float: The impact the order would have on your maintenance margin.
24
+ t.float :equity_with_loan # Float: The impact the order would have on your equity
25
25
  t.timestamps
26
26
  end
27
27
  end
@@ -3,15 +3,16 @@ class AddComboLegs < ActiveRecord::Migration
3
3
  def change
4
4
  # ComboLeg objects represent individual security legs in a "BAG"
5
5
  create_table(:combo_legs) do |t|
6
- t.references :contract
6
+ t.references :combo
7
+ t.references :leg_contract
7
8
  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
9
  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
10
+ t.integer :ratio, :limit => 2 # int: Select the relative number of contracts for the leg you
11
+ t.string :exchange # String: exchange to which the complete combo order will be routed.
12
+ t.integer :exempt_code, :limit => 2 # int:
13
+ t.integer :short_sale_slot, :limit => 2 # int: 0 - retail(default), 1 = clearing broker, 2 = third party
14
+ t.integer :open_close, :limit => 2 # SAME = 0; OPEN = 1; CLOSE = 2; UNKNOWN = 3
13
15
  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
16
  t.timestamps
16
17
  end
17
18
  end
@@ -15,15 +15,10 @@ module IB
15
15
  validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
16
16
  validates_format_of :right, :with => /^none$/, :message => "should be none"
17
17
  validates_format_of :expiry, :with => /^$/, :message => "should be blank"
18
- validate :legs_cannot_be_empty
19
-
20
- def legs_cannot_be_empty
21
- errors.add(:legs, "legs cannot be empty") if legs.empty?
22
- end
23
18
 
24
19
  def default_attributes
25
- super.merge :legs => Array.new,
26
- :sec_type => :bag
20
+ super.merge :sec_type => :bag #,:legs => Array.new,
21
+
27
22
  end
28
23
 
29
24
  def description
@@ -1,13 +1,15 @@
1
1
  module IB
2
2
  module Models
3
3
 
4
- # ComboLeg objects represent individual securities in a "BAG" contract - which
5
- # is not really a contract, but a combination (combo) of securities. AKA basket
6
- # or bag of securities.
4
+ # ComboLeg is essentially a join Model between Combo (BAG) Contract and
5
+ # individual Contracts (securities) that this BAG contains.
7
6
  class ComboLeg < Model.for(:combo_leg)
8
7
  include ModelProperties
9
8
 
10
- belongs_to :contract
9
+ # BAG Combo Contract that contains this Leg
10
+ belongs_to :combo, :class_name => 'Contract'
11
+ # Contract that constitutes this Leg
12
+ belongs_to :leg_contract, :class_name => 'Contract', :foreign_key => :leg_contract_id
11
13
 
12
14
  # General Notes:
13
15
  # 1. The exchange for the leg definition must match that of the combination order.
@@ -40,6 +42,8 @@ module IB
40
42
 
41
43
  def default_attributes
42
44
  super.merge :con_id => 0,
45
+ :ratio => 1,
46
+ :side => :buy,
43
47
  :open_close => :same, # The only option for retail customers.
44
48
  :short_sale_slot => :default,
45
49
  :designated_location => '',
@@ -49,14 +49,14 @@ module IB
49
49
  {:set => proc { |val|
50
50
  self[:right] =
51
51
  case val.to_s.upcase
52
- when 'NONE', '', '0', '?'
53
- ''
54
- when 'PUT', 'P'
55
- 'P'
56
- when 'CALL', 'C'
57
- 'C'
58
- else
59
- val
52
+ when 'NONE', '', '0', '?'
53
+ ''
54
+ when 'PUT', 'P'
55
+ 'P'
56
+ when 'CALL', 'C'
57
+ 'C'
58
+ else
59
+ val
60
60
  end },
61
61
  :validate => {:format => {:with => /^put$|^call$|^none$/,
62
62
  :message => "should be put, call or none"}}
@@ -70,13 +70,20 @@ module IB
70
70
 
71
71
  has_one :contract_detail # Volatile info about this Contract
72
72
 
73
- has_many :combo_legs # for Combo/BAG Contracts only
73
+ # For Contracts that are part of BAG
74
+ has_one :leg, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
75
+ has_one :combo, :class_name => 'Contract', :through => :leg
76
+
77
+ # for Combo/BAG Contracts that contain ComboLegs
78
+ has_many :combo_legs, :foreign_key => :combo_id
79
+ has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
74
80
  alias legs combo_legs
75
81
  alias legs= combo_legs=
76
82
  alias combo_legs_description legs_description
77
83
  alias combo_legs_description= legs_description=
78
84
 
79
- has_one :underlying # for Delta-Neutral Combo Contracts only
85
+ # for Delta-Neutral Combo Contracts
86
+ has_one :underlying
80
87
  alias under_comp underlying
81
88
  alias under_comp= underlying=
82
89
 
@@ -142,12 +149,12 @@ module IB
142
149
  # Defined in Contract, not BAG subclass to keep code DRY
143
150
  def serialize_legs *fields
144
151
  case
145
- when !bag?
146
- []
147
- when legs.empty?
148
- [0]
149
- else
150
- [legs.size, legs.map { |leg| leg.serialize *fields }].flatten
152
+ when !bag?
153
+ []
154
+ when legs.empty?
155
+ [0]
156
+ else
157
+ [legs.size, legs.map { |leg| leg.serialize *fields }].flatten
151
158
  end
152
159
  end
153
160
 
@@ -25,11 +25,11 @@ module IB
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.
28
- def initialize opts={}
28
+ def initialize attributes={}, opts={}
29
29
  run_callbacks :initialize do
30
- error "Argument must be a Hash", :args unless opts.is_a?(Hash)
30
+ error "Argument must be a Hash", :args unless attributes.is_a?(Hash)
31
31
 
32
- self.attributes = default_attributes.merge(opts)
32
+ self.attributes = default_attributes.merge(attributes)
33
33
  end
34
34
  end
35
35
 
@@ -75,65 +75,65 @@ module IB
75
75
  def self.define_property_methods name, body={}
76
76
  #p name, body
77
77
  case body
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
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
124
123
  end
124
+ end
125
125
 
126
126
  # TODO define self[:name] accessors for :virtual and :flag properties
127
127
 
128
- else # setter given
129
- define_property_methods name, :set => body, :get => body
128
+ else # setter given
129
+ define_property_methods name, :set => body, :get => body
130
130
  end
131
131
  end
132
132
 
133
133
  # Extending AR-backed Model class with attribute defaults
134
134
  if defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
135
- def initialize opts={}
136
- super default_attributes.merge(opts)
135
+ def initialize attributes={}, opts={}
136
+ super default_attributes.merge(attributes), opts
137
137
  end
138
138
  else
139
139
  # Timestamps
@@ -7,7 +7,7 @@ module IB
7
7
  validates_numericality_of :strike, :greater_than => 0
8
8
  validates_format_of :sec_type, :with => /^option$/,
9
9
  :message => "should be an option"
10
- validates_format_of :local_symbol, :with => /^\w+\s*\d{15}$|^$/,
10
+ validates_format_of :local_symbol, :with => /^\w+\s*\d{6}[pcPC]\d{8}$|^$/,
11
11
  :message => "invalid OSI code"
12
12
  validates_format_of :right, :with => /^put$|^call$/,
13
13
  :message => "should be put or call"
data/spec/db_helper.rb CHANGED
@@ -32,11 +32,13 @@ shared_examples_for 'Valid DB-backed Model' do
32
32
  end
33
33
 
34
34
  it 'and with the same properties' do
35
- model = described_class.find(:first)
36
- #p model.attributes
37
- #p model.content_attributes
38
- props.each do |name, value|
39
- model.send(name).should == value
35
+ if init_with_props?
36
+ model = described_class.find(:first)
37
+ #p model.attributes
38
+ #p model.content_attributes
39
+ props.each do |name, value|
40
+ model.send(name).should == value
41
+ end
40
42
  end
41
43
  end
42
44
 
@@ -75,79 +77,80 @@ end
75
77
  shared_examples_for 'Model with associations' do
76
78
 
77
79
  it 'works with associations, if any' do
78
- if defined? associations
79
80
 
80
- subject_name_plural = described_class.to_s.demodulize.tableize
81
+ subject_name_plural = described_class.to_s.demodulize.tableize
81
82
 
82
- associations.each do |name, item|
83
- puts "Testing single association #{name}"
84
- subject.association(name).reflection.should_not be_collection
83
+ associations.each do |name, item_props|
84
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
85
+ #item = const_get("IB::#{name.to_s.classify}").new item_props
86
+ puts "Testing single association #{name}"
87
+ subject.association(name).reflection.should_not be_collection
85
88
 
86
- # Assign item to association
87
- expect { subject.send "#{name}=", item }.to_not raise_error
89
+ # Assign item to association
90
+ expect { subject.send "#{name}=", item }.to_not raise_error
88
91
 
89
- association = subject.send name #, :reload
90
- association.should == item
91
- association.should be_new_record
92
+ association = subject.send name #, :reload
93
+ association.should == item
94
+ association.should be_new_record
92
95
 
93
- # Reverse association does not include subject
94
- reverse_association = association.send(subject_name_plural)
95
- reverse_association.should be_empty
96
+ # Reverse association does not include subject
97
+ reverse_association = association.send(subject_name_plural)
98
+ reverse_association.should be_empty
96
99
 
97
- # Now let's save subject
98
- if subject.valid?
99
- subject.save
100
+ # Now let's save subject
101
+ if subject.valid?
102
+ subject.save
100
103
 
101
- association = subject.send name
102
- association.should_not be_new_record
104
+ association = subject.send name
105
+ association.should_not be_new_record
103
106
 
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
107
+ # Reverse association now DOES include subject (if reloaded!)
108
+ reverse_association = association.send(subject_name_plural, :reload)
109
+ reverse_association.should include subject
108
110
  end
109
111
  end
110
112
  end
111
113
 
112
114
  it 'works with associated collections, if any' do
113
- if defined? collections
114
-
115
- subject_name = described_class.to_s.demodulize.tableize.singularize
115
+ subject_name = described_class.to_s.demodulize.tableize.singularize
116
116
 
117
- collections.each do |name, items|
118
- puts "Testing associated collection #{name}"
119
- subject.association(name).reflection.should be_collection
117
+ collections.each do |name, items|
118
+ puts "Testing associated collection #{name}"
119
+ subject.association(name).reflection.should be_collection
120
120
 
121
- [items].flatten.each do |item|
122
- association = subject.send name #, :reload
121
+ [items].flatten.each do |item_props|
122
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
123
+ #item = item_class.new item_props
124
+ association = subject.send name #, :reload
123
125
 
124
- # Add item to collection
125
- expect { association << item }.to_not raise_error
126
- association.should include item
126
+ # Add item to collection
127
+ expect { association << item }.to_not raise_error
128
+ association.should include item
127
129
 
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!
130
+ # Reverse association does NOT point to subject
131
+ reverse_association = association.first.send(subject_name)
132
+ #reverse_association.should be_nil # But not always!
131
133
 
132
- #association.size.should == items.size # Not for Order, +1 OrderState
133
- end
134
+ #association.size.should == items.size # Not for Order, +1 OrderState
135
+ end
134
136
 
135
- # Now let's save subject
136
- if subject.valid?
137
- subject.save
137
+ # Now let's save subject
138
+ if subject.valid?
139
+ subject.save
138
140
 
139
- [items].flatten.each do |item|
140
- association = subject.send name #, :reload
141
+ [items].flatten.each do |item_props|
142
+ item = "IB::Models::#{name.to_s.classify}".constantize.new item_props
143
+ association = subject.send name #, :reload
141
144
 
142
- association.should include item
145
+ association.should include item
143
146
 
144
- # Reverse association DOES point to subject now
145
- reverse_association = association.first.send(subject_name)
146
- reverse_association.should == subject
147
- end
147
+ # Reverse association DOES point to subject now
148
+ reverse_association = association.first.send(subject_name)
149
+ reverse_association.should == subject
148
150
  end
149
-
150
151
  end
151
152
  end
152
153
  end
154
+
155
+
153
156
  end
@@ -1,69 +1,56 @@
1
1
  require 'model_helper'
2
2
 
3
- describe IB::Models::Bag do # AKA IB::Bag
4
-
5
- let(:props) do
6
- {:symbol => 'GOOG',
7
- :exchange => 'SMART',
8
- :currency => 'USD',
9
- :legs => [IB::ComboLeg.new(:con_id => 81032967, :weight => 1),
10
- IB::ComboLeg.new(:con_id => 81032968, :weight => -2),
11
- IB::ComboLeg.new(:con_id => 81032973, :weight => 1)]
12
- }
13
- end
3
+ describe IB::Models::Bag,
14
4
 
15
- let(:human) do
16
- "<Bag: GOOG SMART USD legs: 81032967|1,81032968|-2,81032973|1 >"
17
- end
5
+ :props =>
6
+ {:symbol => 'GOOG',
7
+ :exchange => 'SMART',
8
+ :currency => 'USD',
9
+ :legs => [IB::ComboLeg.new(:con_id => 81032967, :weight => 1),
10
+ IB::ComboLeg.new(:con_id => 81032968, :weight => -2),
11
+ IB::ComboLeg.new(:con_id => 81032973, :weight => 1)]
12
+ },
18
13
 
19
- let(:errors) do
20
- {:legs => ["legs cannot be empty"],
21
- }
22
- end
14
+ :human => "<Bag: GOOG SMART USD legs: 81032967|1,81032968|-2,81032973|1 >",
23
15
 
24
- let(:assigns) do
25
- {:expiry =>
26
- {[nil, ''] => '',
27
- [20060913, '20060913', 200609, '200609', :foo, 2006, 42, 'bar'] =>
28
- /should be blank/},
16
+ :errors =>
17
+ {:legs => ["legs cannot be empty"]},
29
18
 
30
- :sec_type =>
31
- {['BAG', :bag] => :bag,
32
- IB::CODES[:sec_type].reject { |k, _| k == :bag }.to_a =>
33
- /should be a bag/},
19
+ :assigns =>
20
+ {:expiry =>
21
+ {[nil, ''] => '',
22
+ [20060913, '20060913', 200609, '200609', 2006, :foo, 'bar'] =>
23
+ /should be blank/},
34
24
 
35
- :right =>
36
- {['?', :none, '', '0'] => :none,
37
- ["PUT", :put, "CALL", "C", :call, :foo, 'BAR', 42] =>
38
- /should be none/},
25
+ :sec_type =>
26
+ {['BAG', :bag] => :bag,
27
+ IB::CODES[:sec_type].reject { |k, _| k == :bag }.to_a =>
28
+ /should be a bag/},
39
29
 
40
- :exchange => string_upcase_assigns.merge(
41
- [:smart, 'SMART', 'smArt'] => 'SMART'),
30
+ :right =>
31
+ {['?', :none, '', '0'] => :none,
32
+ ["PUT", :put, "CALL", "C", :call, :foo, 'BAR', 42] =>
33
+ /should be none/},
42
34
 
43
- :primary_exchange =>string_upcase_assigns.merge(
44
- [:SMART, 'SMART'] => /should not be SMART/),
35
+ :exchange => string_upcase_assigns.merge(
36
+ [:smart, 'SMART', 'smArt'] => 'SMART'),
45
37
 
46
- [:symbol, :local_symbol] => string_assigns,
38
+ :primary_exchange =>string_upcase_assigns.merge(
39
+ [:SMART, 'SMART'] => /should not be SMART/),
47
40
 
48
- :multiplier => to_i_assigns,
49
- }
50
- end
41
+ [:symbol, :local_symbol] => string_assigns,
51
42
 
52
- it 'does not allow empty legs' do
53
- bag = IB::Bag.new props
54
- bag.legs = []
55
- bag.should be_invalid
56
- bag.errors.messages[:legs].should include "legs cannot be empty"
57
- end
43
+ :multiplier => to_i_assigns,
44
+ } do # AKA IB::Bag
58
45
 
59
- context 'using shortest class name without properties' do
60
- subject { IB::Bag.new }
61
- it_behaves_like 'Model instantiated empty'
62
- end
63
-
64
- it_behaves_like 'Model'
46
+ it_behaves_like 'Model with valid defaults'
65
47
  it_behaves_like 'Self-equal Model'
66
48
 
49
+ it 'has class name shortcut' do
50
+ IB::Bag.should == IB::Models::Bag
51
+ IB::Bag.new.should == IB::Models::Bag.new
52
+ end
53
+
67
54
  context 'properly initiated' do
68
55
  subject { IB::Bag.new props }
69
56