ib-ruby 0.7.4 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.gitignore +3 -0
  2. data/HISTORY +8 -0
  3. data/README.md +2 -2
  4. data/Rakefile +15 -0
  5. data/TODO +7 -2
  6. data/VERSION +1 -1
  7. data/bin/account_info +1 -1
  8. data/bin/cancel_orders +1 -1
  9. data/bin/contract_details +1 -1
  10. data/bin/depth_of_market +1 -1
  11. data/bin/fa_accounts +1 -1
  12. data/bin/fundamental_data +42 -0
  13. data/bin/historic_data +1 -1
  14. data/bin/historic_data_cli +1 -1
  15. data/bin/list_orders +1 -2
  16. data/bin/market_data +1 -1
  17. data/bin/option_data +1 -1
  18. data/bin/place_combo_order +1 -1
  19. data/bin/place_order +1 -1
  20. data/bin/template +1 -4
  21. data/bin/tick_data +2 -2
  22. data/bin/time_and_sales +1 -1
  23. data/lib/ib-ruby.rb +4 -0
  24. data/lib/ib-ruby/connection.rb +50 -34
  25. data/lib/ib-ruby/constants.rb +232 -37
  26. data/lib/ib-ruby/db.rb +25 -0
  27. data/lib/ib-ruby/extensions.rb +51 -1
  28. data/lib/ib-ruby/messages/abstract_message.rb +0 -8
  29. data/lib/ib-ruby/messages/incoming.rb +18 -493
  30. data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
  31. data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
  32. data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
  33. data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
  34. data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
  35. data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
  36. data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
  37. data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
  38. data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
  39. data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
  40. data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
  41. data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
  42. data/lib/ib-ruby/messages/outgoing.rb +25 -223
  43. data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
  44. data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
  45. data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
  46. data/lib/ib-ruby/models.rb +4 -0
  47. data/lib/ib-ruby/models/bar.rb +31 -14
  48. data/lib/ib-ruby/models/combo_leg.rb +48 -23
  49. data/lib/ib-ruby/models/contracts.rb +2 -2
  50. data/lib/ib-ruby/models/contracts/bag.rb +11 -7
  51. data/lib/ib-ruby/models/contracts/contract.rb +90 -66
  52. data/lib/ib-ruby/models/contracts/option.rb +16 -7
  53. data/lib/ib-ruby/models/execution.rb +34 -18
  54. data/lib/ib-ruby/models/model.rb +15 -7
  55. data/lib/ib-ruby/models/model_properties.rb +101 -44
  56. data/lib/ib-ruby/models/order.rb +176 -187
  57. data/lib/ib-ruby/models/order_state.rb +99 -0
  58. data/lib/ib-ruby/symbols/forex.rb +10 -10
  59. data/lib/ib-ruby/symbols/futures.rb +6 -6
  60. data/lib/ib-ruby/symbols/stocks.rb +3 -3
  61. data/spec/account_helper.rb +4 -5
  62. data/spec/combo_helper.rb +4 -4
  63. data/spec/db.rb +18 -0
  64. data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
  65. data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
  66. data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
  67. data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
  68. data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
  69. data/spec/ib-ruby/models/bag_spec.rb +97 -0
  70. data/spec/ib-ruby/models/bar_spec.rb +45 -0
  71. data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
  72. data/spec/ib-ruby/models/contract_spec.rb +134 -170
  73. data/spec/ib-ruby/models/execution_spec.rb +35 -50
  74. data/spec/ib-ruby/models/option_spec.rb +127 -0
  75. data/spec/ib-ruby/models/order_spec.rb +89 -68
  76. data/spec/ib-ruby/models/order_state_spec.rb +55 -0
  77. data/spec/integration/contract_info_spec.rb +4 -6
  78. data/spec/integration/fundamental_data_spec.rb +41 -0
  79. data/spec/integration/historic_data_spec.rb +4 -4
  80. data/spec/integration/market_data_spec.rb +1 -3
  81. data/spec/integration/orders/attached_spec.rb +8 -10
  82. data/spec/integration/orders/combo_spec.rb +2 -2
  83. data/spec/integration/orders/execution_spec.rb +0 -1
  84. data/spec/integration/orders/placement_spec.rb +1 -3
  85. data/spec/integration/orders/valid_ids_spec.rb +1 -2
  86. data/spec/message_helper.rb +1 -1
  87. data/spec/model_helper.rb +211 -0
  88. data/spec/order_helper.rb +44 -37
  89. data/spec/spec_helper.rb +36 -23
  90. data/spec/v.rb +7 -0
  91. data/tasks/doc.rake +1 -1
  92. metadata +116 -12
  93. data/spec/integration/orders/open_order +0 -98
@@ -13,8 +13,7 @@ describe "Request Contract Info", :connected => true, :integration => true do
13
13
  context "Request Stock data" do
14
14
 
15
15
  before(:all) do
16
- @contract = IB::Contract.new :symbol => 'AAPL',
17
- :sec_type => IB::SECURITY_TYPES[:stock]
16
+ @contract = IB::Contract.new :symbol => 'AAPL', :sec_type => :stock
18
17
  @ib.send_message :RequestContractData, :id => 111, :contract => @contract
19
18
  @ib.wait_for :ContractDataEnd, 3 # sec
20
19
  end
@@ -52,8 +51,7 @@ describe "Request Contract Info", :connected => true, :integration => true do
52
51
  contract.order_types.should be_a String
53
52
  contract.price_magnifier.should == 1
54
53
  contract.min_tick.should be <= 0.01
55
-
56
- contract.expiry.should be_nil
54
+ contract.expiry.should == ''
57
55
  end
58
56
  end
59
57
  end # Stock
@@ -108,7 +106,7 @@ describe "Request Contract Info", :connected => true, :integration => true do
108
106
  @contract = IB::Contract.new :symbol => 'EUR', # EURUSD pair
109
107
  :currency => "USD",
110
108
  :exchange => "IDEALPRO",
111
- :sec_type => IB::SECURITY_TYPES[:forex]
109
+ :sec_type => :forex
112
110
  @ib.send_message :RequestContractData, :id => 135, :contract => @contract
113
111
  @ib.wait_for :ContractDataEnd, 3 # sec
114
112
  end
@@ -136,7 +134,7 @@ describe "Request Contract Info", :connected => true, :integration => true do
136
134
  contract.industry.should == ''
137
135
  contract.category.should == ''
138
136
  contract.subcategory.should == ''
139
- contract.expiry.should be_nil
137
+ contract.expiry.should == ''
140
138
  contract.exchange.should == 'IDEALPRO'
141
139
  contract.con_id.should be_an Integer
142
140
  contract.trading_hours.should =~ /\d{8}:\d{4}-\d{4}/
@@ -0,0 +1,41 @@
1
+ require 'integration_helper'
2
+
3
+ describe 'Request Fundamental Data',
4
+ :connected => true, :integration => true, :reuters => true do
5
+
6
+ before(:all) do
7
+ verify_account
8
+ @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
9
+
10
+ @contract = IB::Contract.new :symbol => 'IBM',
11
+ :exchange => 'NYSE',
12
+ :currency => 'USD',
13
+ :sec_type => 'STK'
14
+
15
+ @ib.send_message :RequestFundamentalData,
16
+ :id => 456,
17
+ :contract => @contract,
18
+ :report_type => 'snapshot' # 'estimates', 'finstat'
19
+
20
+ @ib.wait_for :FundamentalData, 6 # sec
21
+ end
22
+
23
+ after(:all) do
24
+ close_connection
25
+ end
26
+
27
+ subject { @ib.received[:FundamentalData].first }
28
+
29
+ it { @ib.received[:FundamentalData].should have_at_least(1).data_message }
30
+
31
+ it { should be_an IB::Messages::Incoming::FundamentalData }
32
+ its(:request_id) { should == 456 }
33
+ its(:data) { should be_a String }
34
+
35
+ it 'responds with XML with relevand data' do
36
+ require 'xmlsimple'
37
+ data_xml = XmlSimple.xml_in(subject.data, 'ForceArray' => false) #, 'ContentKey' => 'content')
38
+ name = data_xml["CoIDs"]["CoID"].find {|tag| tag['Type'] == 'CompanyName'}['content']
39
+ name.should =~ /International Business Machines/
40
+ end
41
+ end
@@ -7,7 +7,7 @@ describe 'Request Historic Data', :connected => true, :integration => true do
7
7
  :end_date_time => Time.now.to_ib,
8
8
  :duration => '1 D',
9
9
  :bar_size => '15 mins',
10
- :what_to_show => :trades,
10
+ :data_type => :trades,
11
11
  :format_date => 1}
12
12
  before(:all) do
13
13
  verify_account
@@ -26,10 +26,10 @@ describe 'Request Historic Data', :connected => true, :integration => true do
26
26
  end.to raise_error /bar_size must be one of/
27
27
  end
28
28
 
29
- it 'raises if incorrect what_to_show' do
29
+ it 'raises if incorrect data_type' do
30
30
  expect do
31
- @ib.send_message :RequestHistoricalData, CORRECT_OPTS.merge(:what_to_show => :nonsense)
32
- end.to raise_error /:what_to_show must be one of/
31
+ @ib.send_message :RequestHistoricalData, CORRECT_OPTS.merge(:data_type => :nonsense)
32
+ end.to raise_error /:data_type must be one of/
33
33
  end
34
34
  end
35
35
 
@@ -1,9 +1,7 @@
1
1
  require 'integration_helper'
2
- #!/usr/bin/env ruby
3
2
 
4
3
  require 'ib-ruby'
5
4
 
6
- #OPTS[:silent] = false
7
5
  describe 'Request Market Data', :connected => true, :integration => true do
8
6
 
9
7
  require 'ib-ruby'
@@ -15,7 +13,7 @@ describe 'Request Market Data', :connected => true, :integration => true do
15
13
  @contract = IB::Contract.new(:symbol => 'AAPL',
16
14
  :exchange => "Smart",
17
15
  :currency => "USD",
18
- :sec_type => IB::SECURITY_TYPES[:stock],
16
+ :sec_type => :stock,
19
17
  :description => "Apple"
20
18
  )
21
19
  @ib.send_message :RequestMarketData, :id => 456, :contract => @contract
@@ -1,8 +1,6 @@
1
1
  require 'order_helper'
2
2
  require 'combo_helper'
3
3
 
4
- #OPTS[:silent] = false
5
-
6
4
  def define_contracts
7
5
  @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
8
6
  @contracts = {
@@ -21,12 +19,12 @@ describe 'Attached Orders', :connected => true, :integration => true do
21
19
 
22
20
  # Testing different combinations of Parent + Attached Orders:
23
21
  [
24
- #[:stock, 100, 'DAY', 'LMT', 9.13, 20.0], # Parent + takeprofit target
25
- [:stock, 100, 'DAY', 'STP', 9.13, 0.0, 8.0], # Parent + stoploss
26
- [:stock, 100, 'GTC', 'LMT', 9.13, 20.0], # GTC Parent + target
22
+ [:stock, 100, 'DAY', 'LMT', 9.13, 20.0], # Parent + takeprofit target
23
+ #[:stock, 100, 'DAY', 'STP', 9.13, 0.0, 8.0], # Parent + stoploss
24
+ #[:stock, 100, 'GTC', 'LMT', 9.13, 20.0], # GTC Parent + target
27
25
  [:butterfly, 10, 'DAY', 'LMT', 0.05, 1.0], # Combo Parent + target
28
- #[:butterfly, 10, 'GTC', 'LMT', 0.05, 1.0], # GTC Combo Parent + target
29
- [:butterfly, 100, 'GTC', 'STPLMT', 0.05, 0.05, 1.0], # GTC Combo Parent + stoplimit target
26
+ #[:butterfly, 10, 'GTC', 'LMT', 0.05, 1.0], # GTC Combo Parent + target
27
+ #[:butterfly, 100, 'GTC', 'STPLMT', 0.05, 0.05, 1.0], # GTC Combo Parent + stoplimit target
30
28
 
31
29
  ].each do |(contract, qty, tif, attach_type, limit_price, attach_price, aux_price)|
32
30
  context "#{tif} BUY (#{contract}) limit order with attached #{attach_type} SELL" do
@@ -57,10 +55,10 @@ describe 'Attached Orders', :connected => true, :integration => true do
57
55
 
58
56
  context "Attaching #{attach_type} order" do
59
57
  before(:all) do
60
- @attached_order = IB::Order.new :total_quantity => qty,
61
- :limit_price => attach_price,
58
+ @attached_order = IB::Order.new :limit_price => attach_price,
62
59
  :aux_price => aux_price || 0,
63
- :action => 'SELL',
60
+ :total_quantity => qty,
61
+ :side => :sell,
64
62
  :tif => tif,
65
63
  :order_type => attach_type,
66
64
  :parent_id => @order_id_placed
@@ -1,8 +1,6 @@
1
1
  require 'order_helper'
2
2
  require 'combo_helper'
3
3
 
4
- #OPTS[:silent] = false
5
-
6
4
  describe "Combo Order", :connected => true, :integration => true, :slow => true do
7
5
 
8
6
  let(:contract_type) { :butterfly }
@@ -17,6 +15,7 @@ describe "Combo Order", :connected => true, :integration => true, :slow => true
17
15
  @contract = butterfly 'GOOG', '201301', 'CALL', 500, 510, 520
18
16
 
19
17
  place_order @contract,
18
+ :order_ref => 'What_if',
20
19
  :limit_price => 0.01,
21
20
  :total_quantity => 10,
22
21
  :what_if => true
@@ -70,6 +69,7 @@ describe "Combo Order", :connected => true, :integration => true, :slow => true
70
69
  @contract = butterfly 'GOOG', '201301', 'CALL', 500, 510, 520
71
70
 
72
71
  place_order @contract,
72
+ :order_ref => 'Original',
73
73
  :limit_price => 0.01,
74
74
  :total_quantity => 10
75
75
 
@@ -1,6 +1,5 @@
1
1
  require 'order_helper'
2
2
 
3
- #OPTS[:silent] = false
4
3
  describe "Trades", :connected => true, :integration => true, :slow => true do
5
4
 
6
5
  before(:all) { verify_account }
@@ -1,7 +1,5 @@
1
1
  require 'order_helper'
2
2
 
3
- #OPTS[:silent] = false
4
-
5
3
  describe 'Orders', :connected => true, :integration => true do
6
4
  let(:contract_type) { :stock }
7
5
 
@@ -89,7 +87,7 @@ describe 'Orders', :connected => true, :integration => true do
89
87
  @ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
90
88
  @ib.wait_for :NextValidId
91
89
  place_order IB::Symbols::Stocks[:wfc], :limit_price => 9.13 # Acceptable price
92
- @ib.wait_for [:OpenOrder, 3], [:OrderStatus, 2], 10
90
+ @ib.wait_for [:OpenOrder, 3], [:OrderStatus, 2], 6
93
91
  end
94
92
 
95
93
  after(:all) { close_connection }
@@ -1,6 +1,5 @@
1
1
  require 'order_helper'
2
2
 
3
- #OPTS[:silent] = false
4
3
  shared_examples_for 'Received single id' do
5
4
  subject { @ib.received[:NextValidId].first }
6
5
 
@@ -52,7 +51,7 @@ describe 'Ids valid for Order placement', :connected => true, :integration => tr
52
51
 
53
52
  it_behaves_like 'Received single id'
54
53
 
55
- it 'receives also :OpenOrderEnd message', :pending => 'not in GW 924.2e' do
54
+ it 'receives also :OpenOrderEnd message', :pending => 'not in GW 924.3a' do
56
55
  @ib.received[:OpenOrderEnd].should have_exactly(1).message
57
56
  @ib.received[:OpenOrderEnd].first.should be_an IB::Messages::Incoming::OpenOrderEnd
58
57
  end
@@ -35,7 +35,7 @@ end
35
35
 
36
36
  # Clear logs and message collector. Output may be silenced.
37
37
  def clean_connection
38
- unless OPTS[:silent]
38
+ if OPTS[:verbose]
39
39
  #puts @received.map { |type, msg| [" #{type}:", msg.map(&:to_human)] } if @received
40
40
  puts @ib.received.map { |type, msg| [" #{type}:", msg.map(&:to_human)] }
41
41
  puts " Logs:", log_entries if @stdout
@@ -0,0 +1,211 @@
1
+ require 'spec_helper'
2
+
3
+ def codes_and_values_for property
4
+ Hash[IB::VALUES[property].map { |code, value| [[code, value], value] }]
5
+ end
6
+
7
+ shared_examples_for 'Model' do
8
+ context 'instantiation without properties' do
9
+ subject { described_class.new }
10
+
11
+ it_behaves_like 'Model instantiated empty'
12
+ end
13
+
14
+ context 'instantiation with properties' do
15
+ subject { described_class.new props }
16
+
17
+ it_behaves_like 'Model instantiated with properties'
18
+
19
+
20
+ it 'has correct human-readeable format' do
21
+ subject.to_human.should == human
22
+ end
23
+ end
24
+ end
25
+
26
+ shared_examples_for 'Self-equal Model' do
27
+ subject { described_class.new(props) }
28
+
29
+ it 'is self-equal ' do
30
+ should == subject
31
+ end
32
+
33
+ it 'is equal to Model with the same properties' do
34
+ should == described_class.new(props)
35
+ end
36
+ end
37
+
38
+ shared_examples_for 'Model instantiated empty' do
39
+ it { should_not be_nil }
40
+
41
+ it 'sets all properties to defaults' do
42
+ defined?(defaults) && defaults.each do |name, value|
43
+ case value
44
+ when Module, Class
45
+ subject.send(name).should be_a value
46
+ else
47
+ subject.send(name).should == value
48
+ end
49
+ end
50
+ end
51
+
52
+ it_behaves_like 'Model properties'
53
+ it_behaves_like 'Invalid Model'
54
+ end
55
+
56
+ shared_examples_for 'Model instantiated with properties' do
57
+ it 'auto-assigns all properties given to initializer' do
58
+ props.each do |name, value|
59
+ subject.send(name).should == value
60
+ end
61
+ end
62
+
63
+ it_behaves_like 'Model properties'
64
+ it_behaves_like 'Valid Model'
65
+ end
66
+
67
+ shared_examples_for 'Model properties' do
68
+ context 'essential properties are still set, even if not given explicitely' do
69
+ its(:created_at) { should be_a Time }
70
+ end
71
+
72
+ it 'allows setting properties' do
73
+ expect {
74
+ props.each do |name, value|
75
+ subject.send("#{name}=", value)
76
+ subject.send(name).should == value
77
+ end
78
+ }.to_not raise_error
79
+ end
80
+
81
+ it 'sets values to properties as directed by its setters' do
82
+ defined?(assigns) && assigns.each do |props, cases|
83
+ #p props, cases
84
+
85
+ # For each given property ...
86
+ [props].flatten.each do |prop|
87
+
88
+ # For all test cases given as an Array [res1, res2] or Hash {val => res} ...
89
+ (cases.is_a?(Array) ? cases.map { |e| [e, e] } : cases).each do |values, result|
90
+
91
+ # For all values in this test case ...
92
+ [values].flatten.each do |value|
93
+
94
+ # Assigning this value to property results in ...
95
+ case result
96
+ when Exception # ... Exception
97
+ expect { subject.send "#{prop}=", value }.
98
+ to raise_error result
99
+
100
+ when Regexp # ... Non-exceptional error, making model invalid
101
+ expect { subject.send "#{prop}=", value }.to_not raise_error
102
+ subject.should be_invalid
103
+
104
+ #pp subject.errors.messages
105
+ #p value, result
106
+
107
+ subject.errors.messages[prop].should_not be_nil
108
+ msg = subject.errors.messages[prop].find { |msg| msg =~ result }
109
+ msg.should =~ result
110
+
111
+ else # ... correct uniform assignment to result
112
+
113
+ was_valid = subject.valid?
114
+ expect { subject.send "#{prop}=", value }.to_not raise_error
115
+ subject.send("#{prop}").should == result
116
+
117
+ was_valid && (subject.should be_valid) # assignment keeps validity
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ shared_examples_for 'Valid Model' do
128
+ it 'validates' do
129
+ #subject.valid?
130
+ #pp subject.errors.messages
131
+ subject.should be_valid
132
+ subject.errors.should be_empty
133
+ end
134
+
135
+ context 'with DB backend', :db => true do
136
+ after(:all) do
137
+ #DatabaseCleaner.clean
138
+ end
139
+
140
+ it 'is saved' do
141
+ subject.save.should be_true
142
+ end
143
+
144
+ it 'is loaded just right' do
145
+ models = described_class.find(:all)
146
+ model = models.first
147
+ #pp model
148
+ models.should have_exactly(1).model
149
+ model.should == subject
150
+ model.should be_valid
151
+ props.each do |name, value|
152
+ model.send(name).should == value
153
+ end
154
+ end
155
+ end # DB
156
+ end
157
+
158
+ shared_examples_for 'Invalid Model' do
159
+ it 'does not validate' do
160
+ subject.should_not be_valid
161
+ subject.should be_invalid
162
+ subject.errors.should_not be_empty
163
+ subject.errors.messages.should == errors if defined? errors
164
+ end
165
+
166
+ context 'with DB backend', :db => true do
167
+ after(:all) do
168
+ #DatabaseCleaner.clean
169
+ end
170
+
171
+ it 'is not saved' do
172
+ subject.save.should be_false
173
+ end
174
+
175
+ it 'is not loaded' do
176
+ models = described_class.find(:all)
177
+ models.should have_exactly(0).model
178
+ end
179
+ end # DB
180
+ end
181
+
182
+ shared_examples_for 'Contract' do
183
+ it 'summary points to itself (ContractDetails artifact' do
184
+ subject.summary.should == subject
185
+ end
186
+
187
+ it 'becomes invalid if assigned wrong :sec_type property' do
188
+ subject.sec_type = 'FOO'
189
+ subject.should be_invalid
190
+ subject.errors.messages[:sec_type].should include "should be valid security type"
191
+ end
192
+
193
+ it 'becomes invalid if assigned wrong :right property' do
194
+ subject.right = 'BAR'
195
+ subject.should be_invalid
196
+ subject.errors.messages[:right].should include "should be put, call or none"
197
+ end
198
+
199
+ it 'becomes invalid if assigned wrong :expiry property' do
200
+ subject.expiry = 'BAR'
201
+ subject.should be_invalid
202
+ subject.errors.messages[:expiry].should include "should be YYYYMM or YYYYMMDD"
203
+ end
204
+
205
+ it 'becomes invalid if primary_exchange is set to SMART' do
206
+ subject.primary_exchange = 'SMART'
207
+ subject.should be_invalid
208
+ subject.errors.messages[:primary_exchange].should include "should not be SMART"
209
+ end
210
+
211
+ end