ib-ruby 0.7.4 → 0.7.6

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.
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