ib-ruby 0.7.11 → 0.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/HISTORY 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