ib-ruby 0.5.19 → 0.5.21

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 (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
+