ib-ruby 0.5.19 → 0.5.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/HISTORY +4 -0
  2. data/TODO +0 -3
  3. data/VERSION +1 -1
  4. data/bin/contract_details +3 -3
  5. data/bin/depth_of_market +1 -1
  6. data/bin/historic_data +5 -8
  7. data/bin/market_data +2 -2
  8. data/bin/option_data +2 -2
  9. data/lib/ib-ruby/connection.rb +1 -9
  10. data/lib/ib-ruby/extensions.rb +8 -0
  11. data/lib/ib-ruby/messages/abstract_message.rb +89 -0
  12. data/lib/ib-ruby/messages/incoming.rb +415 -487
  13. data/lib/ib-ruby/messages/outgoing.rb +241 -305
  14. data/lib/ib-ruby/models/bar.rb +3 -3
  15. data/lib/ib-ruby/models/contract/bag.rb +1 -5
  16. data/lib/ib-ruby/models/contract.rb +50 -33
  17. data/lib/ib-ruby/models/execution.rb +6 -3
  18. data/lib/ib-ruby/models/order.rb +7 -5
  19. data/lib/ib-ruby/socket.rb +13 -0
  20. data/lib/ib-ruby/symbols/forex.rb +7 -14
  21. data/lib/ib-ruby/symbols/futures.rb +16 -20
  22. data/lib/ib-ruby/symbols/options.rb +6 -4
  23. data/lib/ib-ruby/symbols/stocks.rb +1 -1
  24. data/lib/ib-ruby.rb +1 -0
  25. data/spec/README.md +34 -0
  26. data/spec/ib-ruby/connection_spec.rb +4 -4
  27. data/spec/ib-ruby/messages/incoming_spec.rb +50 -0
  28. data/spec/ib-ruby/messages/outgoing_spec.rb +32 -0
  29. data/spec/ib-ruby/models/contract_spec.rb +27 -25
  30. data/spec/ib-ruby/models/order_spec.rb +56 -23
  31. data/spec/integration/account_info_spec.rb +85 -0
  32. data/spec/integration/contract_info_spec.rb +209 -0
  33. data/spec/integration/depth_data_spec.rb +46 -0
  34. data/spec/integration/historic_data_spec.rb +82 -0
  35. data/spec/integration/market_data_spec.rb +97 -0
  36. data/spec/integration/option_data_spec.rb +96 -0
  37. data/spec/integration/orders/execution_spec.rb +135 -0
  38. data/spec/{ib-ruby/messages → integration/orders}/open_order +9 -205
  39. data/spec/integration/orders/placement_spec.rb +150 -0
  40. data/spec/integration/orders/valid_ids_spec.rb +84 -0
  41. data/spec/integration_helper.rb +110 -0
  42. data/spec/message_helper.rb +13 -18
  43. data/spec/spec_helper.rb +35 -26
  44. metadata +33 -17
  45. data/spec/ib-ruby/messages/README.md +0 -16
  46. data/spec/ib-ruby/messages/account_info_spec.rb +0 -84
  47. data/spec/ib-ruby/messages/just_connect_spec.rb +0 -33
  48. data/spec/ib-ruby/messages/market_data_spec.rb +0 -92
  49. data/spec/ib-ruby/messages/orders_spec.rb +0 -219
  50. data/spec/ib-ruby_spec.rb +0 -0
@@ -0,0 +1,96 @@
1
+ require 'integration_helper'
2
+
3
+ def wait_for_all_ticks
4
+ wait_for(5) do
5
+ received?(:TickPrice) && received?(:TickSize) &&
6
+ received?(:TickOption) && received?(:TickString)
7
+ end
8
+ end
9
+
10
+ describe 'Request Market Data for Options', :if => :us_trading_hours,
11
+ :connected => true, :integration => true do
12
+
13
+ before(:all) do
14
+ verify_account
15
+ connect_and_receive :Alert, :TickPrice, :TickSize, :TickOption, :TickString
16
+
17
+ @ib.send_message :RequestMarketData, :id => 456,
18
+ :contract => IB::Symbols::Options[:aapl500]
19
+ wait_for_all_ticks
20
+ end
21
+
22
+ after(:all) do
23
+ @ib.send_message :CancelMarketData, :id => 456
24
+ close_connection
25
+ end
26
+
27
+ context "received :Alert message " do
28
+ subject { @received[:Alert].first }
29
+
30
+ it { should be_an IB::Messages::Incoming::Alert }
31
+ it { should be_warning }
32
+ it { should_not be_error }
33
+ its(:code) { should be_an Integer }
34
+ its(:message) { should =~ /Market data farm connection is OK/ }
35
+ its(:to_human) { should =~ /TWS Warning/ }
36
+ end
37
+
38
+ context "received :TickPrice message" do
39
+ subject { @received[:TickPrice].first }
40
+
41
+ it { should be_an IB::Messages::Incoming::TickPrice }
42
+ its(:tick_type) { should be_an Integer }
43
+ its(:type) { should be_a Symbol }
44
+ its(:price) { should be_a Float }
45
+ its(:size) { should be_an Integer }
46
+ its(:can_auto_execute) { should be_an Integer }
47
+ its(:data) { should be_a Hash }
48
+ its(:ticker_id) { should == 456 } # ticker_id
49
+ its(:to_human) { should =~ /TickPrice/ }
50
+ end
51
+
52
+ context "received :TickSize message" do
53
+ subject { @received[:TickSize].first }
54
+
55
+ it { should be_an IB::Messages::Incoming::TickSize }
56
+ its(:type) { should_not be_nil }
57
+ its(:data) { should be_a Hash }
58
+ its(:tick_type) { should be_an Integer }
59
+ its(:type) { should be_a Symbol }
60
+ its(:size) { should be_an Integer }
61
+ its(:ticker_id) { should == 456 }
62
+ its(:to_human) { should =~ /TickSize/ }
63
+ end
64
+
65
+ context "received :TickOption message" do
66
+ subject { @received[:TickOption].first }
67
+
68
+ it { should be_an IB::Messages::Incoming::TickOption }
69
+ its(:type) { should_not be_nil }
70
+ its(:data) { should be_a Hash }
71
+ its(:tick_type) { should be_an Integer }
72
+ its(:type) { should be_a Symbol }
73
+ its(:under_price) { should be_a Float }
74
+ its(:option_price) { should be_a Float }
75
+ its(:pv_dividend) { should be_a Float }
76
+ its(:implied_volatility) { should be_a Float }
77
+ its(:gamma) { should be_a Float }
78
+ its(:vega) { should be_a Float }
79
+ its(:theta) { should be_a Float }
80
+ its(:ticker_id) { should == 456 }
81
+ its(:to_human) { should =~ /TickOption/ }
82
+ end
83
+
84
+ context "received :TickString message" do
85
+ subject { @received[:TickString].first }
86
+
87
+ it { should be_an IB::Messages::Incoming::TickString }
88
+ its(:type) { should_not be_nil }
89
+ its(:data) { should be_a Hash }
90
+ its(:tick_type) { should be_an Integer }
91
+ its(:type) { should be_a Symbol }
92
+ its(:value) { should be_a String }
93
+ its(:ticker_id) { should == 456 }
94
+ its(:to_human) { should =~ /TickString/ }
95
+ end
96
+ end # Request Options Market Data
@@ -0,0 +1,135 @@
1
+ require 'integration_helper'
2
+
3
+ # TODO: RequestExecutions (with filters?)
4
+
5
+ def wait_for_execution_and_commission
6
+ wait_for(5) do
7
+ received?(:ExecutionData) && received?(:OpenOrder) &&
8
+ @received[:OpenOrder].last.order.commission
9
+ end
10
+ end
11
+
12
+ describe "Trades", :connected => true, :integration => true, :slow => true do
13
+
14
+ before(:all) { verify_account }
15
+
16
+ context "Trading Forex", :if => :forex_trading_hours do
17
+
18
+ before(:all) do
19
+ @contract = IB::Symbols::Forex[:eurusd]
20
+ connect_and_receive :NextValidID, :Alert, :ExecutionData, :ExecutionDataEnd,
21
+ :OpenOrder, :OrderStatus, :OpenOrderEnd
22
+ wait_for { received? :NextValidID }
23
+ end
24
+
25
+ after(:all) { close_connection }
26
+
27
+ context "Placing BUY order" do
28
+
29
+ before(:all) do
30
+ place_order @contract,
31
+ :total_quantity => 20000,
32
+ :limit_price => 2,
33
+ :action => 'BUY'
34
+
35
+ wait_for_execution_and_commission
36
+ end
37
+
38
+ after(:all) do
39
+ clean_connection # Clear logs and message collector
40
+ @ib.cancel_order @order_id_placed # Just in case...
41
+ end
42
+
43
+ it 'changes client`s next_order_id' do
44
+ @order_id_placed = @order_id_before
45
+ @ib.next_order_id.should == @order_id_before + 1
46
+ end
47
+
48
+ it { @received[:OpenOrder].should have_at_least(1).open_order_message }
49
+ it { @received[:OrderStatus].should have_at_least(1).status_message }
50
+ it { @received[:ExecutionData].should have_exactly(1).execution_data }
51
+ it { @received[:ExecutionDataEnd].should be_empty }
52
+
53
+ it 'receives filled OpenOrder' do
54
+ open_order_should_be 'Filled', -1
55
+ msg = @received[:OpenOrder].last
56
+ msg.order.commission.should == 2.5
57
+ end
58
+
59
+ it 'receives Execution Data' do
60
+ execution_should_be 'BUY'
61
+ end
62
+
63
+ it 'receives OrderStatus with fill details' do
64
+ order_status_should_be 'Filled', -1
65
+ end
66
+ end # Placing BUY
67
+
68
+ context "Placing SELL order" do
69
+
70
+ before(:all) do
71
+ place_order @contract,
72
+ :total_quantity => 20000,
73
+ :limit_price => 1,
74
+ :action => 'SELL'
75
+
76
+ wait_for_execution_and_commission
77
+ end
78
+
79
+ after(:all) do
80
+ clean_connection # Clear logs and message collector
81
+ @ib.cancel_order @order_id_placed # Just in case...
82
+ end
83
+
84
+ it 'changes client`s next_order_id' do
85
+ @order_id_placed = @order_id_before
86
+ @ib.next_order_id.should == @order_id_before + 1
87
+ end
88
+
89
+ it { @received[:OpenOrder].should have_at_least(1).open_order_message }
90
+ it { @received[:OrderStatus].should have_at_least(1).status_message }
91
+ it { @received[:ExecutionData].should have_exactly(1).execution_data }
92
+
93
+ it 'receives filled OpenOrder' do
94
+ open_order_should_be 'Filled', -1
95
+ msg = @received[:OpenOrder].last
96
+ msg.order.commission.should == 2.5
97
+ end
98
+
99
+ it 'receives Execution Data' do
100
+ execution_should_be 'SELL'
101
+ end
102
+
103
+ it 'receives OrderStatus with fill details' do
104
+ order_status_should_be 'Filled', -1
105
+ end
106
+ end # Placing SELL
107
+
108
+ context "Request executions" do
109
+
110
+ before(:all) do
111
+ @ib.send_message :RequestExecutions,
112
+ :request_id => 456,
113
+ :client_id => OPTS[:connection][:client_id],
114
+ :time => (Time.now-10).to_ib
115
+ wait_for(3) { received?(:ExecutionData) }
116
+ end
117
+
118
+ #after(:all) { clean_connection }
119
+
120
+ it 'does not receive Order-related messages' do
121
+ @received[:OpenOrder].should be_empty
122
+ @received[:OrderStatus].should be_empty
123
+ end
124
+
125
+ it 'receives ExecutionData messages' do
126
+ @received[:ExecutionData].should have_at_least(1).execution_data
127
+ end
128
+
129
+ it 'receives Execution Data' do
130
+ execution_should_be 'SELL', :request_id => 456
131
+ end
132
+ end # Request executions
133
+ end # Forex order
134
+
135
+ end # Trades
@@ -19,211 +19,15 @@
19
19
  @socket=nil>, @socket=nil>, @socket=nil>]
20
20
 
21
21
 
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
22
+ #<IB::Models::Order:0xfa0 #<IB::Models::Order:0x1040 #<IB::Models::Order:0x10e0
23
+ @commission=nil, @commission=nil, @commission=2.5,
24
+ @commission_currency="USD", @commission_currency="USD", @commission_currency="USD",
25
+ @limit_price=1.0, @limit_price=1.0, @limit_price=1.0,
26
+ @status="Filled", @status="Filled", @status="Filled",
27
+ @tif="DAY", @tif="DAY", @tif="DAY",
28
+ @total_quantity=20000, @total_quantity=20000, @total_quantity=20000,
29
+ @transmit=true, @transmit=true, @transmit=true,
30
+ @what_if=false> @what_if=false> @what_if=false>
227
31
 
228
32
  [#<IB::Messages::Incoming::OrderStatus:0x127c
229
33
  @created_at=Thu Feb 23 12:17:48 +0400 2012,
@@ -0,0 +1,150 @@
1
+ require 'integration_helper'
2
+
3
+ describe "Orders", :connected => true, :integration => true do
4
+
5
+ before(:all) { verify_account }
6
+
7
+ context "Placing wrong order", :slow => true do
8
+
9
+ before(:all) do
10
+ connect_and_receive :NextValidID, :Alert, :OpenOrder, :OrderStatus
11
+ wait_for { received? :NextValidID }
12
+
13
+ place_order IB::Symbols::Stocks[:wfc],
14
+ :limit_price => 9.131313 # Weird non-acceptable price
15
+ wait_for 1
16
+ end
17
+
18
+ after(:all) { close_connection }
19
+
20
+ it 'does not place new Order' do
21
+ @received[:OpenOrder].should be_empty
22
+ @received[:OrderStatus].should be_empty
23
+ end
24
+
25
+ it 'still changes client`s next_order_id' do
26
+ @order_id_placed.should == @order_id_before
27
+ @ib.next_order_id.should == @order_id_before + 1
28
+ end
29
+
30
+ context 'received :Alert message' do
31
+ subject { @received[:Alert].last }
32
+
33
+ it { should be_an IB::Messages::Incoming::Alert }
34
+ it { should be_error }
35
+ its(:code) { should be_a Integer }
36
+ its(:message) { should =~ /The price does not conform to the minimum price variation for this contract/ }
37
+ end
38
+
39
+ end # Placing wrong order
40
+
41
+ context "Off-market stock order" do
42
+ before(:all) do
43
+ connect_and_receive :NextValidID, :Alert, :OpenOrder, :OrderStatus, :OpenOrderEnd
44
+ wait_for { received? :NextValidID }
45
+
46
+ place_order IB::Symbols::Stocks[:wfc],
47
+ :limit_price => 9.13 # Set acceptable price
48
+ wait_for { @received[:OpenOrder].size > 2 && @received[:OpenOrder].size > 1 }
49
+ end
50
+
51
+ after(:all) { close_connection }
52
+
53
+ context "Placing" do
54
+ after(:all) { clean_connection } # Clear logs and message collector
55
+
56
+ it 'changes client`s next_order_id' do
57
+ @order_id_placed.should == @order_id_before
58
+ @ib.next_order_id.should == @order_id_before + 1
59
+ end
60
+
61
+ it { @received[:OpenOrder].should have_at_least(1).open_order_message }
62
+ it { @received[:OrderStatus].should have_at_least(1).status_message }
63
+
64
+ it 'receives confirmation of Order submission' do
65
+ open_order_should_be /Submitted/ # ()Pre)Submitted
66
+ order_status_should_be /Submitted/
67
+ end
68
+ end # Placing
69
+
70
+ context "Retrieving placed orders" do
71
+ before(:all) do
72
+ @ib.send_message :RequestAllOpenOrders
73
+
74
+ wait_for { received?(:OpenOrderEnd) }
75
+ end
76
+
77
+ after(:all) { clean_connection } # Clear logs and message collector
78
+
79
+ it 'does not increase client`s next_order_id further' do
80
+ @ib.next_order_id.should == @order_id_after
81
+ end
82
+
83
+ it { @received[:OpenOrder].should have_exactly(1).open_order_message }
84
+ it { @received[:OrderStatus].should have_exactly(1).status_message }
85
+ it { @received[:OpenOrderEnd].should have_exactly(1).order_end_message }
86
+ it { @received[:Alert].should have_exactly(0).alert_messages }
87
+
88
+ it 'receives OpenOrder and OrderStatus for placed order' do
89
+ open_order_should_be /Submitted/
90
+ order_status_should_be /Submitted/
91
+ end
92
+ end # Retrieving
93
+
94
+ context "Cancelling placed order" do
95
+ before(:all) do
96
+ @ib.cancel_order @order_id_placed
97
+
98
+ wait_for { received?(:OrderStatus) && received?(:Alert) }
99
+ end
100
+
101
+ after(:all) { clean_connection } # Clear logs and message collector
102
+
103
+ it 'does not increase client`s next_order_id further' do
104
+ @ib.next_order_id.should == @order_id_after
105
+ end
106
+
107
+ it 'does not receive OpenOrder message' do
108
+ received?(:OpenOrder).should be_false
109
+ end
110
+
111
+ it { @received[:OrderStatus].should have_exactly(1).status_message }
112
+ it { @received[:Alert].should have_exactly(1).alert_message }
113
+
114
+ it 'receives cancellation Order Status' do
115
+ order_status_should_be /Cancel/ # Cancelled / PendingCancel
116
+ end
117
+
118
+ it 'receives Order cancelled Alert' do
119
+ alert = @received[:Alert].first
120
+ alert.should be_an IB::Messages::Incoming::Alert
121
+ alert.message.should =~ /Order Canceled - reason:/
122
+ end
123
+ end # Cancelling
124
+
125
+ context "Cancelling wrong order" do
126
+ before(:all) do
127
+ @ib.cancel_order rand(99999999)
128
+
129
+ wait_for { received?(:Alert) }
130
+ end
131
+
132
+ it { @received[:Alert].should have_exactly(1).alert_message }
133
+
134
+ it 'does not increase client`s next_order_id further' do
135
+ @ib.next_order_id.should == @order_id_after
136
+ end
137
+
138
+ it 'does not receive Order messages' do
139
+ received?(:OrderStatus).should be_false
140
+ received?(:OpenOrder).should be_false
141
+ end
142
+
143
+ it 'receives unable to find Order Alert' do
144
+ alert = @received[:Alert].first
145
+ alert.should be_an IB::Messages::Incoming::Alert
146
+ alert.message.should =~ /Can't find order with id =/
147
+ end
148
+ end # Cancelling
149
+ end # Off-market order
150
+ end # Orders
@@ -0,0 +1,84 @@
1
+ require 'integration_helper'
2
+
3
+ shared_examples_for 'Received single id' do
4
+ subject { @received[:NextValidID].first }
5
+
6
+ after(:all) { clean_connection }
7
+
8
+ it { @received[:NextValidID].should have_exactly(1).message }
9
+
10
+ it 'receives next valid for Order placement' do
11
+ subject.should be_an IB::Messages::Incoming::NextValidID
12
+ subject.order_id.should be_an Integer
13
+ @id[:at_connect] ||= subject.order_id # just assign once
14
+ end
15
+
16
+ it 'logs next valid order id' do
17
+ should_log /Got next valid order id/
18
+ end
19
+ end
20
+
21
+ shared_examples_for 'Received single id after request' do
22
+ subject { @received[:NextValidID].first }
23
+
24
+ it_behaves_like 'Received single id'
25
+
26
+ it 'no new id is generated by this request' do
27
+ subject.order_id.should == @id[:at_connect]
28
+ end
29
+
30
+ it 'does not receive :OpenOrderEnd message' do
31
+ @received[:OpenOrderEnd].should be_empty
32
+ end
33
+
34
+ it 'does not reconnect to server' do
35
+ should_not_log /Connected to server/
36
+ end
37
+ end
38
+
39
+ describe 'Ids valid for Order placement', :connected => true, :integration => true do
40
+
41
+ before(:all) do
42
+ verify_account
43
+ connect_and_receive :NextValidID, :OpenOrderEnd, :Alert
44
+ wait_for(2) { received? :OpenOrderEnd }
45
+ @id = {} # Moving id between contexts. Feels dirty.
46
+ end
47
+
48
+ after(:all) { close_connection }
49
+
50
+ context 'at connect' do
51
+
52
+ it_behaves_like 'Received single id'
53
+
54
+ it { @received[:OpenOrderEnd].should have_exactly(1).message }
55
+
56
+ it 'receives also :OpenOrderEnd message' do
57
+ @received[:OpenOrderEnd].first.should be_an IB::Messages::Incoming::OpenOrderEnd
58
+ end
59
+
60
+ it 'logs connection notification' do
61
+ should_log /Connected to server, version: 53, connection time/
62
+ end
63
+ end # at connect
64
+
65
+ context 'Requesting valid order id' do
66
+ before(:all) do
67
+ @ib.send_message :RequestIds
68
+ wait_for 1 # sec
69
+ end
70
+
71
+ it_behaves_like 'Received single id after request'
72
+ end # Requesting valid order ids
73
+
74
+ context 'Requested number of valid ids is just silently ignored by TWS' do
75
+ before(:all) do
76
+ @ib.send_message :RequestIds, :number => 5
77
+ wait_for 1 # sec
78
+ end
79
+
80
+ it_behaves_like 'Received single id after request'
81
+ end # number of ids is silently ignored
82
+
83
+ end # Ids valid for Order placement
84
+