ib-ruby 0.5.21 → 0.6.1
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 +8 -0
- data/README.md +46 -27
- data/TODO +13 -2
- data/VERSION +1 -1
- data/bin/generic_data.rb +26 -0
- data/bin/place_order +1 -1
- data/lib/ib-ruby/connection.rb +126 -65
- data/lib/ib-ruby/messages/incoming.rb +3 -3
- data/lib/ib-ruby/models/bar.rb +11 -11
- data/lib/ib-ruby/models/combo_leg.rb +23 -29
- data/lib/ib-ruby/models/contract/bag.rb +34 -2
- data/lib/ib-ruby/models/contract/option.rb +2 -2
- data/lib/ib-ruby/models/contract.rb +151 -197
- data/lib/ib-ruby/models/execution.rb +27 -45
- data/lib/ib-ruby/models/model.rb +10 -4
- data/lib/ib-ruby/models/model_properties.rb +63 -0
- data/lib/ib-ruby/models/order.rb +274 -320
- data/lib/ib-ruby/symbols/stocks.rb +11 -5
- data/spec/account_helper.rb +80 -0
- data/spec/ib-ruby/connection_spec.rb +195 -52
- data/spec/ib-ruby/messages/incoming_spec.rb +4 -4
- data/spec/ib-ruby/models/combo_leg_spec.rb +1 -0
- data/spec/ib-ruby/models/contract_spec.rb +1 -1
- data/spec/ib-ruby/models/execution_spec.rb +73 -0
- data/spec/integration/account_info_spec.rb +12 -59
- data/spec/integration/contract_info_spec.rb +23 -37
- data/spec/integration/depth_data_spec.rb +4 -4
- data/spec/integration/historic_data_spec.rb +15 -27
- data/spec/integration/market_data_spec.rb +74 -61
- data/spec/integration/option_data_spec.rb +5 -48
- data/spec/integration/orders/execution_spec.rb +26 -31
- data/spec/integration/orders/open_order +2 -0
- data/spec/integration/orders/placement_spec.rb +28 -28
- data/spec/integration/orders/valid_ids_spec.rb +11 -11
- data/spec/integration_helper.rb +46 -32
- data/spec/message_helper.rb +4 -38
- data/spec/spec_helper.rb +2 -3
- metadata +9 -2
@@ -13,11 +13,17 @@ module IB
|
|
13
13
|
:currency => "USD",
|
14
14
|
:sec_type => SECURITY_TYPES[:stock],
|
15
15
|
:description => "Wells Fargo"),
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
|
17
|
+
:aapl => Models::Contract.new(:symbol => "AAPL",
|
18
|
+
:currency => "USD",
|
19
|
+
:sec_type => SECURITY_TYPES[:stock],
|
20
|
+
:description => "Apple Inc."),
|
21
|
+
|
22
|
+
:wrong => Models::Contract.new(:symbol => "QEEUUE",
|
23
|
+
:exchange => "NYSE",
|
24
|
+
:currency => "USD",
|
25
|
+
:sec_type => SECURITY_TYPES[:stock],
|
26
|
+
:description => "Non-existent stock"),
|
21
27
|
}
|
22
28
|
end
|
23
29
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Make sure integration tests are only run against the pre-configured PAPER ACCOUNT
|
2
|
+
def verify_account
|
3
|
+
return OPTS[:account_verified] if OPTS[:account_verified]
|
4
|
+
|
5
|
+
puts
|
6
|
+
puts 'WARNING: MAKE SURE TO RUN INTEGRATION TESTS AGAINST IB PAPER ACCOUNT ONLY!'
|
7
|
+
puts 'WARNING: FINANCIAL LOSSES MAY RESULT IF YOU RUN TESTS WITH REAL IB ACCOUNT!'
|
8
|
+
puts 'WARNING: YOU HAVE BEEN WARNED!'
|
9
|
+
puts
|
10
|
+
puts 'Configure your connection to IB PAPER ACCOUNT in spec/spec_helper.rb'
|
11
|
+
puts
|
12
|
+
|
13
|
+
account = OPTS[:connection][:account] || OPTS[:connection][:account_name]
|
14
|
+
raise "Please configure IB PAPER ACCOUNT in spec/spec_helper.rb" unless account
|
15
|
+
|
16
|
+
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
|
17
|
+
@ib.send_message :RequestAccountData, :subscribe => true
|
18
|
+
|
19
|
+
@ib.wait_for 3, :AccountValue
|
20
|
+
raise "Unable to verify IB PAPER ACCOUNT" unless @ib.received?(:AccountValue)
|
21
|
+
|
22
|
+
received = @ib.received[:AccountValue].first.account_name
|
23
|
+
raise "Connected to wrong account #{received}, expected #{account}" if account != received
|
24
|
+
|
25
|
+
close_connection
|
26
|
+
OPTS[:account_verified] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
### Helpers for placing and verifying orders
|
30
|
+
|
31
|
+
shared_examples_for 'Valid account data request' do
|
32
|
+
|
33
|
+
context "received :AccountUpdateTime message" do
|
34
|
+
subject { @ib.received[:AccountUpdateTime].first }
|
35
|
+
|
36
|
+
it { should be_an IB::Messages::Incoming::AccountUpdateTime }
|
37
|
+
its(:data) { should be_a Hash }
|
38
|
+
its(:time_stamp) { should =~ /\d\d:\d\d/ }
|
39
|
+
its(:to_human) { should =~ /AccountUpdateTime/ }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "received :AccountValue message" do
|
43
|
+
subject { @ib.received[:AccountValue].first }
|
44
|
+
|
45
|
+
#ps
|
46
|
+
it { should be_an IB::Messages::Incoming::AccountValue }
|
47
|
+
its(:data) { should be_a Hash }
|
48
|
+
its(:account_name) { should =~ /\w\d/ }
|
49
|
+
its(:key) { should be_a String }
|
50
|
+
its(:value) { should be_a String }
|
51
|
+
its(:currency) { should be_a String }
|
52
|
+
its(:to_human) { should =~ /AccountValue/ }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "received :PortfolioValue message" do
|
56
|
+
subject { @ib.received[:PortfolioValue].first }
|
57
|
+
|
58
|
+
it { should be_an IB::Messages::Incoming::PortfolioValue }
|
59
|
+
its(:contract) { should be_a IB::Models::Contract }
|
60
|
+
its(:data) { should be_a Hash }
|
61
|
+
its(:position) { should be_a Integer }
|
62
|
+
its(:market_price) { should be_a Float }
|
63
|
+
its(:market_value) { should be_a Float }
|
64
|
+
its(:average_cost) { should be_a Float }
|
65
|
+
its(:unrealized_pnl) { should be_a Float }
|
66
|
+
its(:realized_pnl) { should be_a Float }
|
67
|
+
its(:account_name) { should =~ /\w\d/ }
|
68
|
+
its(:to_human) { should =~ /PortfolioValue/ }
|
69
|
+
end
|
70
|
+
|
71
|
+
context "received :AccountDownloadEnd message" do
|
72
|
+
subject { @ib.received[:AccountDownloadEnd].first }
|
73
|
+
|
74
|
+
it { should be_an IB::Messages::Incoming::AccountDownloadEnd }
|
75
|
+
its(:data) { should be_a Hash }
|
76
|
+
its(:account_name) { should =~ /\w\d/ }
|
77
|
+
its(:to_human) { should =~ /AccountDownloadEnd/ }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -1,94 +1,237 @@
|
|
1
1
|
require 'message_helper'
|
2
|
+
require 'account_helper'
|
3
|
+
|
4
|
+
shared_examples_for 'Connected Connection' do
|
5
|
+
|
6
|
+
subject { @ib }
|
7
|
+
|
8
|
+
it_behaves_like 'Connected Connection without receiver'
|
9
|
+
|
10
|
+
it 'keeps received messages in Hash by default' do
|
11
|
+
subject.received.should be_a Hash
|
12
|
+
subject.received[:NextValidId].should_not be_empty
|
13
|
+
subject.received[:NextValidId].should have_exactly(1).message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples_for 'Connected Connection without receiver' do
|
18
|
+
|
19
|
+
it { should_not be_nil }
|
20
|
+
it { should be_connected }
|
21
|
+
its(:server) { should be_a Hash }
|
22
|
+
its(:server) { should have_key :reader }
|
23
|
+
its(:subscribers) { should have_at_least(1).item } # :NextValidId and empty Hashes
|
24
|
+
its(:next_order_id) { should be_a Fixnum } # Not before :NextValidId arrives
|
25
|
+
end
|
26
|
+
|
27
|
+
# Need top level method to access instance var (@received) in nested context
|
28
|
+
def create_connection opts={}
|
29
|
+
# Start disconnected (we need to set up catch-all subscriber first)
|
30
|
+
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger).merge(opts)
|
31
|
+
|
32
|
+
# Hash of received messages, keyed by message type
|
33
|
+
@received = Hash.new { |hash, key| hash[key] = Array.new }
|
34
|
+
|
35
|
+
@subscriber = proc { |msg| @received[msg.message_type] << msg }
|
36
|
+
end
|
2
37
|
|
3
38
|
describe IB::Connection do
|
4
39
|
|
5
|
-
context '
|
6
|
-
# THIS depends on TWS|Gateway connectivity
|
40
|
+
context 'instantiated with default options', :connected => true do
|
7
41
|
before(:all) do
|
8
|
-
|
9
|
-
@ib.
|
42
|
+
create_connection
|
43
|
+
@ib.wait_for :NextValidId
|
10
44
|
end
|
11
45
|
|
12
46
|
after(:all) { close_connection }
|
13
47
|
|
14
|
-
|
15
|
-
subject { @ib }
|
16
|
-
|
17
|
-
it { should_not be_nil }
|
18
|
-
it { should be_connected }
|
19
|
-
its(:server) { should be_a Hash }
|
20
|
-
its(:server) { should have_key :reader }
|
21
|
-
its(:subscribers) { should have_at_least(1).item } # :NextValidID and empty Hashes
|
22
|
-
its(:next_order_id) { should be_a Fixnum } # Not before :NextValidID arrives
|
23
|
-
end
|
48
|
+
it_behaves_like 'Connected Connection'
|
24
49
|
|
25
50
|
describe '#send_message', 'sending messages' do
|
26
51
|
it 'allows 3 signatures representing IB::Messages::Outgoing' do
|
27
|
-
expect {
|
28
|
-
@ib.send_message :RequestOpenOrders, :subscribe => true
|
29
|
-
}.to_not raise_error
|
52
|
+
expect { @ib.send_message :RequestOpenOrders }.to_not raise_error
|
30
53
|
|
31
|
-
expect {
|
32
|
-
@ib.send_message IB::Messages::Outgoing::RequestOpenOrders, :subscribe => true
|
54
|
+
expect { @ib.send_message IB::Messages::Outgoing::RequestOpenOrders
|
33
55
|
}.to_not raise_error
|
34
56
|
|
35
|
-
expect {
|
36
|
-
@ib.send_message IB::Messages::Outgoing::RequestOpenOrders.new :subscribe => true
|
57
|
+
expect { @ib.send_message IB::Messages::Outgoing::RequestOpenOrders.new
|
37
58
|
}.to_not raise_error
|
38
59
|
end
|
39
60
|
|
40
61
|
it 'has legacy #dispatch alias' do
|
41
|
-
expect { @ib.dispatch :RequestOpenOrders
|
42
|
-
}.to_not raise_error
|
62
|
+
expect { @ib.dispatch :RequestOpenOrders }.to_not raise_error
|
43
63
|
end
|
44
64
|
end
|
45
65
|
|
46
66
|
context "subscriptions" do
|
67
|
+
before(:all) do
|
68
|
+
@id = {} # Moving id between contexts. Feels dirty.
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#subscribe' do
|
72
|
+
after(:all) { clean_connection }
|
73
|
+
|
74
|
+
it 'adds (multiple) subscribers, returning subscription id' do
|
75
|
+
@id[:first] = @ib.subscribe(IB::Messages::Incoming::OrderStatus) do |msg|
|
76
|
+
log msg.to_human
|
77
|
+
end
|
78
|
+
|
79
|
+
@id[:second] = @ib.subscribe /Value/, :OpenOrder, @subscriber
|
80
|
+
|
81
|
+
@id[:third] = @ib.subscribe /Account/, &@subscriber
|
82
|
+
|
83
|
+
[[@id[:first], IB::Messages::Incoming::OrderStatus],
|
84
|
+
[@id[:second], IB::Messages::Incoming::OpenOrder],
|
85
|
+
[@id[:second], IB::Messages::Incoming::PortfolioValue],
|
86
|
+
[@id[:second], IB::Messages::Incoming::AccountValue], # as /Value/
|
87
|
+
[@id[:third], IB::Messages::Incoming::AccountValue], # as /Account/
|
88
|
+
[@id[:third], IB::Messages::Incoming::AccountDownloadEnd],
|
89
|
+
[@id[:third], IB::Messages::Incoming::AccountUpdateTime],
|
90
|
+
].each do |(subscriber_id, message_class)|
|
91
|
+
@ib.subscribers.should have_key(message_class)
|
92
|
+
@ib.subscribers[message_class].should have_key(subscriber_id)
|
93
|
+
end
|
94
|
+
end
|
47
95
|
|
48
|
-
|
49
|
-
|
96
|
+
it 'returns Integer subscription id' do
|
97
|
+
@id[:first].should be_an Integer
|
98
|
+
@id[:second].should be_an Integer
|
99
|
+
@id[:third].should be_an Integer
|
50
100
|
end
|
51
101
|
|
52
|
-
|
102
|
+
context 'when subscribed' do
|
53
103
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
@ib.
|
60
|
-
|
104
|
+
before(:all) do
|
105
|
+
@ib.send_message :RequestAccountData
|
106
|
+
@ib.wait_for :AccountDownloadEnd
|
107
|
+
end
|
108
|
+
|
109
|
+
after(:all) { @ib.send_message :RequestAccountData, :subscribe => false }
|
110
|
+
|
111
|
+
it 'receives subscribed message types and processes them in subscriber callback' do
|
112
|
+
@received[:AccountValue].should_not be_empty
|
113
|
+
@received[:PortfolioValue].should_not be_empty
|
114
|
+
@received[:AccountDownloadEnd].should_not be_empty
|
115
|
+
@received[:AccountUpdateTime].should_not be_empty
|
116
|
+
end
|
117
|
+
|
118
|
+
it_behaves_like 'Valid account data request'
|
119
|
+
end
|
120
|
+
end # subscribe
|
121
|
+
|
122
|
+
describe '#unsubscribe' do
|
123
|
+
before(:all) { @result = @ib.unsubscribe @id[:first], @id[:second] }
|
124
|
+
|
125
|
+
it 'removes all subscribers at given id or ids' do
|
126
|
+
[IB::Messages::Incoming::OrderStatus,
|
127
|
+
IB::Messages::Incoming::OpenOrder,
|
128
|
+
IB::Messages::Incoming::PortfolioValue,
|
129
|
+
IB::Messages::Incoming::AccountValue,
|
130
|
+
].each do |message_class|
|
131
|
+
@ib.subscribers[message_class].should_not have_key(@id[:first])
|
132
|
+
@ib.subscribers[message_class].should_not have_key(@id[:second])
|
133
|
+
end
|
61
134
|
end
|
62
|
-
end
|
63
135
|
|
64
|
-
|
65
|
-
|
136
|
+
it 'does not remove subscribers at other ids' do
|
137
|
+
@ib.subscribers[IB::Messages::Incoming::AccountValue].should have_key(@id[:third])
|
138
|
+
@ib.subscribers[IB::Messages::Incoming::AccountDownloadEnd].should have_key(@id[:third])
|
139
|
+
@ib.subscribers[IB::Messages::Incoming::AccountUpdateTime].should have_key(@id[:third])
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'returns an Array of removed subscribers' do
|
143
|
+
@result.should be_an Array
|
144
|
+
@result.should have_exactly(4).deleted_subscribers
|
145
|
+
end
|
66
146
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
IB::Messages::Incoming::PortfolioValue
|
71
|
-
].each do |message_class|
|
72
|
-
@ib.subscribers[message_class].should_not have_key(@subscriber_id)
|
147
|
+
it 'raises on nosense id given' do
|
148
|
+
expect { @ib.unsubscribe 'nonsense' }.to raise_error /No subscribers with id/
|
149
|
+
expect { @ib.unsubscribe rand(9999999) }.to raise_error /No subscribers with id/
|
73
150
|
end
|
74
151
|
end
|
75
152
|
|
153
|
+
context 'when unsubscribed' do
|
154
|
+
|
155
|
+
before(:all) do
|
156
|
+
@ib.send_message :RequestAccountData
|
157
|
+
@ib.wait_for { !@received[:AccountDownloadEnd].empty? }
|
158
|
+
end
|
159
|
+
|
160
|
+
after(:all) { @ib.send_message :RequestAccountData, :subscribe => false }
|
161
|
+
|
162
|
+
it 'receives subscribed message types still subscribed' do
|
163
|
+
@received[:AccountValue].should_not be_empty
|
164
|
+
@received[:AccountUpdateTime].should_not be_empty
|
165
|
+
@received[:AccountDownloadEnd].should_not be_empty
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'does not receive unsubscribed message types' do
|
169
|
+
@received[:PortfolioValue].should be_empty
|
170
|
+
end
|
171
|
+
|
172
|
+
it { should_log /No subscribers for message .*:PortfolioValue/ }
|
173
|
+
it { should_not_log /No subscribers for message .*:AccountValue/ }
|
174
|
+
end # when subscribed
|
76
175
|
end # subscriptions
|
176
|
+
|
177
|
+
describe '#connect' do
|
178
|
+
it 'raises on another connection attempt' do
|
179
|
+
expect { @ib.connect }.to raise_error /Already connected/
|
180
|
+
end
|
181
|
+
end
|
77
182
|
end # connected
|
78
183
|
|
79
|
-
context '
|
80
|
-
before(:all) {
|
184
|
+
context 'instantiated passing :connect => false' do
|
185
|
+
before(:all) { create_connection :connect => false,
|
186
|
+
:reader => false }
|
187
|
+
subject { @ib }
|
188
|
+
|
189
|
+
it { should_not be_nil }
|
190
|
+
it { should_not be_connected }
|
191
|
+
its(:server) { should be_a Hash }
|
192
|
+
its(:server) { should_not have_key :reader }
|
193
|
+
its(:received) { should be_empty }
|
194
|
+
its(:subscribers) { should be_empty }
|
195
|
+
its(:next_order_id) { should be_nil }
|
196
|
+
|
197
|
+
describe 'connecting idle conection' do
|
198
|
+
before(:all) do
|
199
|
+
@ib.connect
|
200
|
+
@ib.start_reader
|
201
|
+
@ib.wait_for :NextValidId
|
202
|
+
end
|
203
|
+
after(:all) { close_connection }
|
204
|
+
|
205
|
+
it_behaves_like 'Connected Connection'
|
206
|
+
end
|
81
207
|
|
82
|
-
|
83
|
-
subject { @ib }
|
208
|
+
end # not connected
|
84
209
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
210
|
+
context 'instantiated passing :received => false' do
|
211
|
+
before(:all) { create_connection :connect => false,
|
212
|
+
:reader => false,
|
213
|
+
:received => false }
|
214
|
+
subject { @ib }
|
215
|
+
|
216
|
+
it { should_not be_nil }
|
217
|
+
it { should_not be_connected }
|
218
|
+
its(:server) { should be_a Hash }
|
219
|
+
its(:server) { should_not have_key :reader }
|
220
|
+
its(:received) { should be_empty }
|
221
|
+
its(:subscribers) { should be_empty }
|
222
|
+
its(:next_order_id) { should be_nil }
|
223
|
+
|
224
|
+
describe 'connecting idle conection' do
|
225
|
+
before(:all) do
|
226
|
+
@ib.connect
|
227
|
+
@ib.start_reader
|
228
|
+
@ib.wait_for 1 # ib.received not supposed to work!
|
229
|
+
end
|
230
|
+
after(:all) { close_connection }
|
231
|
+
|
232
|
+
it_behaves_like 'Connected Connection without receiver'
|
91
233
|
end
|
92
234
|
|
93
235
|
end # not connected
|
236
|
+
|
94
237
|
end # describe IB::Connection
|
@@ -7,7 +7,7 @@ shared_examples_for 'Alert message' do
|
|
7
7
|
its(:message_type) { should == :Alert }
|
8
8
|
its(:message_id) { should == 4 }
|
9
9
|
its(:version) { should == 2 }
|
10
|
-
its(:data) { should == {:version=>2, :error_id=>-1, :code=>2104, :message=>"Market data farm connection is OK:cashfarm"}}
|
10
|
+
its(:data) { should == {:version=>2, :error_id=>-1, :code=>2104, :message=>"Market data farm connection is OK:cashfarm"} }
|
11
11
|
its(:error_id) { should == -1 }
|
12
12
|
its(:code) { should == 2104 }
|
13
13
|
its(:message) { should =~ /Market data farm connection is OK/ }
|
@@ -37,13 +37,13 @@ describe IB::Messages::Incoming do
|
|
37
37
|
context 'Message received from IB', :connected => true do
|
38
38
|
|
39
39
|
before(:all) do
|
40
|
-
|
41
|
-
wait_for
|
40
|
+
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
|
41
|
+
@ib.wait_for :Alert
|
42
42
|
end
|
43
43
|
|
44
44
|
after(:all) { close_connection }
|
45
45
|
|
46
|
-
subject { @received[:Alert].first }
|
46
|
+
subject { @ib.received[:Alert].first }
|
47
47
|
|
48
48
|
it_behaves_like 'Alert message'
|
49
49
|
end #
|
@@ -21,7 +21,7 @@ describe IB::Models::Contract do
|
|
21
21
|
it { should_not be_nil }
|
22
22
|
its(:con_id) { should == 0 }
|
23
23
|
its(:strike) { should == 0 }
|
24
|
-
its(:sec_type) { should
|
24
|
+
its(:sec_type) { should be_nil }
|
25
25
|
its(:created_at) { should be_a Time }
|
26
26
|
its(:include_expired) { should == false }
|
27
27
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe IB::Models::Execution do
|
4
|
+
|
5
|
+
let(:properties) do
|
6
|
+
{:account_name => "DU111110",
|
7
|
+
:average_price => 1.31075,
|
8
|
+
:client_id => 1111,
|
9
|
+
:cumulative_quantity => 20000,
|
10
|
+
:exchange => "IDEALPRO",
|
11
|
+
:exec_id => "0001f4e8.4f5d48f1.01.01",
|
12
|
+
:liquidation => 0,
|
13
|
+
:order_id => 373,
|
14
|
+
:perm_id => 1695693613,
|
15
|
+
:price => 1.31075,
|
16
|
+
:shares => 20000,
|
17
|
+
:side => :buy,
|
18
|
+
:time => "20120312 15:41:09"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
context "instantiation" do
|
23
|
+
context 'empty without properties' do
|
24
|
+
subject { IB::Models::Execution.new }
|
25
|
+
|
26
|
+
it { should_not be_nil }
|
27
|
+
its(:order_id) { should == 0 }
|
28
|
+
its(:client_id) { should == 0 }
|
29
|
+
its(:perm_id) { should == 0 }
|
30
|
+
its(:shares) { should == 0 }
|
31
|
+
its(:price) { should == 0 }
|
32
|
+
its(:liquidation) { should == 0 }
|
33
|
+
its(:created_at) { should be_a Time }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with properties' do
|
37
|
+
subject { IB::Models::Execution.new properties }
|
38
|
+
|
39
|
+
it 'sets properties right' do
|
40
|
+
properties.each do |name, value|
|
41
|
+
subject.send(name).should == value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end #instantiation
|
46
|
+
|
47
|
+
context "properties" do
|
48
|
+
|
49
|
+
it 'allows setting properties' do
|
50
|
+
expect {
|
51
|
+
x = IB::Models::Execution.new
|
52
|
+
properties.each do |name, value|
|
53
|
+
subject.send("#{name}=", value)
|
54
|
+
subject.send(name).should == value
|
55
|
+
end
|
56
|
+
}.to_not raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'sets side as directed by its setter' do
|
60
|
+
@x = IB::Models::Execution.new
|
61
|
+
['BOT', 'BUY', 'Buy', 'buy', :BUY, :BOT, :Buy, :buy, 'B', :b].each do |val|
|
62
|
+
expect { @x.side = val }.to_not raise_error
|
63
|
+
@x.side.should == :buy
|
64
|
+
end
|
65
|
+
|
66
|
+
['SELL', 'SLD', 'Sel', 'sell', :SELL, :SLD, :Sell, :sell, 'S', :S].each do |val|
|
67
|
+
expect { @x.side = val }.to_not raise_error
|
68
|
+
@x.side.should == :sell
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end # properties
|
72
|
+
|
73
|
+
end # describe IB::Models::Contract
|
@@ -1,66 +1,10 @@
|
|
1
1
|
require 'integration_helper'
|
2
2
|
|
3
|
-
shared_examples_for 'Valid account data request' do
|
4
|
-
|
5
|
-
after(:all) do
|
6
|
-
@ib.send_message :RequestAccountData, :subscribe => false
|
7
|
-
clean_connection
|
8
|
-
end
|
9
|
-
|
10
|
-
context "received :AccountUpdateTime message" do
|
11
|
-
subject { @received[:AccountUpdateTime].first }
|
12
|
-
|
13
|
-
it { should be_an IB::Messages::Incoming::AccountUpdateTime }
|
14
|
-
its(:data) { should be_a Hash }
|
15
|
-
its(:time_stamp) { should =~ /\d\d:\d\d/ }
|
16
|
-
its(:to_human) { should =~ /AccountUpdateTime/ }
|
17
|
-
end
|
18
|
-
|
19
|
-
context "received :AccountValue message" do
|
20
|
-
subject { @received[:AccountValue].first }
|
21
|
-
|
22
|
-
#ps
|
23
|
-
it { should be_an IB::Messages::Incoming::AccountValue }
|
24
|
-
its(:data) { should be_a Hash }
|
25
|
-
its(:account_name) { should =~ /\w\d/ }
|
26
|
-
its(:key) { should be_a String }
|
27
|
-
its(:value) { should be_a String }
|
28
|
-
its(:currency) { should be_a String }
|
29
|
-
its(:to_human) { should =~ /AccountValue/ }
|
30
|
-
end
|
31
|
-
|
32
|
-
context "received :PortfolioValue message" do
|
33
|
-
subject { @received[:PortfolioValue].first }
|
34
|
-
|
35
|
-
it { should be_an IB::Messages::Incoming::PortfolioValue }
|
36
|
-
its(:contract) { should be_a IB::Models::Contract }
|
37
|
-
its(:data) { should be_a Hash }
|
38
|
-
its(:position) { should be_a Integer }
|
39
|
-
its(:market_price) { should be_a Float }
|
40
|
-
its(:market_value) { should be_a Float }
|
41
|
-
its(:average_cost) { should be_a Float }
|
42
|
-
its(:unrealized_pnl) { should be_a Float }
|
43
|
-
its(:realized_pnl) { should be_a Float }
|
44
|
-
its(:account_name) { should =~ /\w\d/ }
|
45
|
-
its(:to_human) { should =~ /PortfolioValue/ }
|
46
|
-
end
|
47
|
-
|
48
|
-
context "received :AccountDownloadEnd message" do
|
49
|
-
subject { @received[:AccountDownloadEnd].first }
|
50
|
-
|
51
|
-
it { should be_an IB::Messages::Incoming::AccountDownloadEnd }
|
52
|
-
its(:data) { should be_a Hash }
|
53
|
-
its(:account_name) { should =~ /\w\d/ }
|
54
|
-
its(:to_human) { should =~ /AccountDownloadEnd/ }
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
3
|
describe "Request Account Data", :connected => true, :integration => true do
|
59
4
|
|
60
5
|
before(:all) do
|
61
6
|
verify_account
|
62
|
-
|
63
|
-
:PortfolioValue, :AccountUpdateTime)
|
7
|
+
@ib = IB::Connection.new OPTS[:connection].merge(:logger => mock_logger)
|
64
8
|
end
|
65
9
|
|
66
10
|
after(:all) { close_connection }
|
@@ -68,7 +12,11 @@ describe "Request Account Data", :connected => true, :integration => true do
|
|
68
12
|
context "with subscribe option set" do
|
69
13
|
before(:all) do
|
70
14
|
@ib.send_message :RequestAccountData, :subscribe => true
|
71
|
-
wait_for
|
15
|
+
@ib.wait_for 5, :AccountDownloadEnd
|
16
|
+
end
|
17
|
+
after(:all) do
|
18
|
+
@ib.send_message :RequestAccountData, :subscribe => false
|
19
|
+
clean_connection
|
72
20
|
end
|
73
21
|
|
74
22
|
it_behaves_like 'Valid account data request'
|
@@ -77,7 +25,12 @@ describe "Request Account Data", :connected => true, :integration => true do
|
|
77
25
|
context "without subscribe option" do
|
78
26
|
before(:all) do
|
79
27
|
@ib.send_message :RequestAccountData
|
80
|
-
wait_for
|
28
|
+
@ib.wait_for 5, :AccountDownloadEnd
|
29
|
+
end
|
30
|
+
|
31
|
+
after(:all) do
|
32
|
+
@ib.send_message :RequestAccountData, :subscribe => false
|
33
|
+
clean_connection
|
81
34
|
end
|
82
35
|
|
83
36
|
it_behaves_like 'Valid account data request'
|