oanda_api_v20 1.5.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,14 @@
1
1
  module OandaApiV20
2
- class RequestError < RuntimeError; end
2
+ class ApiError < RuntimeError; end
3
+ class ParseError < RuntimeError; end
4
+
5
+ class RequestError < RuntimeError
6
+ attr_reader :response, :original_exception
7
+
8
+ def initialize(message = nil, options = {})
9
+ @original_exception = options[:original_exception]
10
+ @response = options[:response]
11
+ super(message)
12
+ end
13
+ end
3
14
  end
@@ -1,7 +1,7 @@
1
1
  # @see http://developer.oanda.com/rest-live-v20/instrument-ep/
2
2
  module OandaApiV20
3
3
  module Instruments
4
- attr_accessor :instrument
4
+ attr_reader :instrument
5
5
 
6
6
  # GET /v3/instruments/:instrument/candles
7
7
  def candles(options = {})
@@ -5,5 +5,17 @@ module OandaApiV20
5
5
  def pricing(options)
6
6
  Client.send(http_verb, "#{base_uri}/accounts/#{account_id}/pricing", headers: headers, query: options)
7
7
  end
8
+
9
+ # GET /v3/accounts/:account_id/pricing/stream
10
+ def pricing_stream(options, &block)
11
+ buffer = String.new
12
+
13
+ Client.send(http_verb, "#{base_uri}/accounts/#{account_id}/pricing/stream", headers: headers, query: options, stream_body: true) do |fragment|
14
+ if !fragment.empty?
15
+ buffer << fragment
16
+ parse(buffer, fragment, &block) if fragment.match(/\n\Z/)
17
+ end
18
+ end
19
+ end
8
20
  end
9
21
  end
@@ -20,5 +20,17 @@ module OandaApiV20
20
20
  def transactions_since_id(options)
21
21
  Client.send(http_verb, "#{base_uri}/accounts/#{account_id}/transactions/sinceid", headers: headers, query: options)
22
22
  end
23
+
24
+ # GET /v3/accounts/:account_id/transactions/stream
25
+ def transactions_stream(options = {}, &block)
26
+ buffer = String.new
27
+
28
+ Client.send(http_verb, "#{base_uri}/accounts/#{account_id}/transactions/stream", headers: headers, query: options, stream_body: true) do |fragment|
29
+ if !fragment.empty?
30
+ buffer << fragment
31
+ parse(buffer, fragment, &block) if fragment.match(/\n\Z/)
32
+ end
33
+ end
34
+ end
23
35
  end
24
36
  end
@@ -1,3 +1,3 @@
1
1
  module OandaApiV20
2
- VERSION = '1.5.0'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'rspec', '~> 3.4'
24
24
  s.add_development_dependency 'webmock', '~> 2.1'
25
25
  s.add_development_dependency 'timecop', '~> 0.8'
26
- s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'rake', '~> 13.0'
27
27
 
28
28
  s.files = `git ls-files`.split("\n")
29
29
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
@@ -2,326 +2,557 @@ require 'spec_helper'
2
2
 
3
3
  describe OandaApiV20::Api do
4
4
  describe '#initialize' do
5
- it 'sets the http_verb attribute when supplied' do
6
- api = OandaApiV20::Api.new(http_verb: :post)
7
- expect(api.http_verb).to eq(:post)
5
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token') }
6
+
7
+ it 'sets the client attribute when supplied' do
8
+ api = OandaApiV20::Api.new(client: client)
9
+ expect(api.client).to eq(client)
10
+ end
11
+
12
+ it 'raises an OandaApiV20::ApiError exception when no client object was supplied' do
13
+ expect{ OandaApiV20::Api.new }.to raise_error(OandaApiV20::ApiError)
8
14
  end
9
15
 
10
16
  it 'sets the base_uri attribute when supplied' do
11
17
  url = 'https://api-fxpractice.oanda.com/v3'
12
- api = OandaApiV20::Api.new(base_uri: url)
18
+ api = OandaApiV20::Api.new(client: client, base_uri: url)
13
19
  expect(api.base_uri).to eq(url)
14
20
  end
15
21
 
22
+ it 'sets the default base_uri attribute when not supplied' do
23
+ api = OandaApiV20::Api.new(client: client)
24
+ expect(api.base_uri).to eq('https://api-fxtrade.oanda.com/v3')
25
+ end
26
+
16
27
  it 'sets the headers attribute when supplied' do
17
28
  headers = { 'Content-Type' => 'application/json', 'Connection' => 'keep-alive', 'Keep-Alive' => '30' }
18
- api = OandaApiV20::Api.new(headers: headers)
29
+ api = OandaApiV20::Api.new(client: client, headers: headers)
19
30
  expect(api.headers).to eq(headers)
20
31
  end
21
32
 
33
+ it 'sets the default headers attribute when not supplied' do
34
+ api = OandaApiV20::Api.new(client: client)
35
+ expect(api.headers).to eq({ 'Authorization' => 'Bearer my_access_token', 'X-Accept-Datetime-Format' => 'RFC3339', 'Content-Type' => 'application/json' })
36
+ end
37
+
22
38
  it 'sets the account_id attribute when supplied' do
23
39
  account_id = '100-100-100'
24
- api = OandaApiV20::Api.new(account_id: account_id)
40
+ api = OandaApiV20::Api.new(client: client, account_id: account_id)
25
41
  expect(api.account_id).to eq(account_id)
26
42
  end
27
43
 
28
- it 'sets the last_transaction_id attribute when supplied' do
29
- last_transaction_id = '1'
30
- api = OandaApiV20::Api.new(last_transaction_id: last_transaction_id)
31
- expect(api.last_transaction_id).to eq(last_transaction_id)
44
+ it 'sets the instrument variable when supplied' do
45
+ instrument = 'EUR_USD'
46
+ api = OandaApiV20::Api.new(client: client, instrument: instrument)
47
+ expect(api.instance_variable_get(:@instrument)).to eq(instrument)
32
48
  end
33
- end
34
49
 
35
- describe '#public_methods' do
36
- let!(:api) { OandaApiV20::Api.new }
37
- let(:public_methods) { [
38
- :account, :accounts, :summary, :instruments, :changes, :configuration,
39
- :order, :orders, :pending_orders,
40
- :trade, :trades, :open_trades,
41
- :position, :positions, :open_positions,
42
- :transaction, :transactions, :transactions_id_range, :transactions_since_id,
43
- :pricing
44
- ] }
50
+ it 'sets the last_action when supplied' do
51
+ api = OandaApiV20::Api.new(client: client, last_action: 'accounts')
52
+ expect(api.last_action).to eq('accounts')
53
+ end
45
54
 
46
- it 'responds to all public methods' do
47
- public_methods.each do |public_method|
48
- expect(api.respond_to?(public_method)).to be_truthy
49
- end
55
+ it 'sets the last_arguments when supplied' do
56
+ api = OandaApiV20::Api.new(client: client, last_action: 'account', last_arguments: ['100-100-100'])
57
+ expect(api.last_action).to eq('account')
58
+ expect(api.last_arguments).to eq(['100-100-100'])
50
59
  end
51
60
  end
52
61
 
53
- describe 'constructs the correct API URL under' do
54
- let!(:api) { OandaApiV20::Api.new(base_uri: 'https://api-fxtrade.oanda.com/v3', account_id: '100-100-100', headers: {}) }
62
+ describe '#method_missing' do
63
+ describe 'constructs the correct API URL under' do
64
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
65
+ let!(:stream_client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}, stream: true) }
66
+ let!(:api) { OandaApiV20::Api.new(client: client, account_id: '100-100-100') }
67
+ let!(:stream_api) { OandaApiV20::Api.new(client: stream_client, account_id: '100-100-100') }
68
+
69
+ before(:each) do
70
+ stub_request(:get, /https:\/\/api-fxtrade\.oanda\.com\/v3.*/)
71
+ end
72
+
73
+ context 'accounts for' do
74
+ it 'retrieving an account' do
75
+ api.account('100-100-100').show
76
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100')).to have_been_made.once
77
+ end
78
+
79
+ it 'retrieving all accounts' do
80
+ api.accounts.show
81
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts')).to have_been_made.once
82
+ end
83
+
84
+ it 'retrieving a summary' do
85
+ api.summary.show
86
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/summary')).to have_been_made.once
87
+ end
88
+
89
+ it 'retrieving all instruments' do
90
+ api.instruments.show
91
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/instruments')).to have_been_made.once
92
+ end
93
+
94
+ it 'retrieving an instrument' do
95
+ api.instruments('EUR_USD').show
96
+ options = { 'instruments' => 'EUR_USD' }
97
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/instruments').with(query: options)).to have_been_made.once
98
+ end
99
+
100
+ it 'retrieving no changes' do
101
+ options = {}
102
+ api.changes(options).show
103
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/changes').with(query: { 'sinceTransactionID' => '' })).to have_been_made.once
104
+ end
105
+
106
+ it 'retrieving all changes since a transaction ID' do
107
+ options = { 'sinceTransactionID' => '1' }
108
+ api.changes(options).show
109
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/changes').with(query: options)).to have_been_made.once
110
+ end
111
+
112
+ it 'updating a configuration' do
113
+ options = { 'alias' => 'Timmy!' }
114
+ request = stub_request(:patch, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/configuration').with(body: options)
115
+ api.configuration(options).update
116
+ expect(request).to have_been_requested.once
117
+ end
118
+ end
119
+
120
+ context 'instruments for' do
121
+ let!(:api) { OandaApiV20::Api.new(client: client, account_id: '100-100-100', instrument: 'EUR_USD') }
122
+
123
+ it 'retrieving candlestick data' do
124
+ options = { 'count' => '10' }
125
+ api.candles(options).show
126
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/instruments/EUR_USD/candles').with(query: options)).to have_been_made.once
127
+ end
128
+ end
129
+
130
+ context 'orders for' do
131
+ it 'retrieving an order' do
132
+ api.order('1').show
133
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1')).to have_been_made.once
134
+ end
135
+
136
+ it 'creating an order' do
137
+ options = {
138
+ 'order' => {
139
+ 'units' => '100',
140
+ 'instrument' => 'EUR_CAD',
141
+ 'timeInForce' => 'FOK',
142
+ 'type' => 'MARKET',
143
+ 'positionFill' => 'DEFAULT'
144
+ }
145
+ }
146
+ request = stub_request(:post, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders').with(body: options)
147
+ api.order(options).create
148
+ expect(request).to have_been_requested.once
149
+ end
150
+
151
+ it 'updating an order' do
152
+ options = {
153
+ 'order' => {
154
+ 'timeInForce' => 'GTC',
155
+ 'price' => '1.7000',
156
+ 'type' => 'TAKE_PROFIT',
157
+ 'tradeID' => '1'
158
+ }
159
+ }
160
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1').with(body: options)
161
+ api.order('1', options).update
162
+ expect(request).to have_been_requested.once
163
+ end
164
+
165
+ it 'updating an order client extensions' do
166
+ options = {
167
+ 'clientExtensions' => {
168
+ 'comment' => 'New comment for my limit order'
169
+ }
170
+ }
171
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1/clientExtensions').with(body: options)
172
+ api.order('1', options).update
173
+ expect(request).to have_been_requested.once
174
+ end
175
+
176
+ it 'cancelling an order' do
177
+ request = stub_request(:put,'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1/cancel')
178
+ api.order('1').cancel
179
+ expect(request).to have_been_requested.once
180
+ end
181
+
182
+ it 'retrieving all orders' do
183
+ api.orders.show
184
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders')).to have_been_made.once
185
+ end
186
+
187
+ it 'retrieving all orders' do
188
+ options = { 'instrument' => 'USD_CAD' }
189
+ api.orders(options).show
190
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders').with(query: options)).to have_been_made.once
191
+ end
192
+
193
+ it 'retrieving all pending orders' do
194
+ api.pending_orders.show
195
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/pendingOrders')).to have_been_made.once
196
+ end
197
+ end
198
+
199
+ context 'trades for' do
200
+ it 'retrieving a trade' do
201
+ api.trade('1').show
202
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1')).to have_been_made.once
203
+ end
204
+
205
+ it 'updating a trade' do
206
+ options = {
207
+ 'takeProfit' => {
208
+ 'timeInForce' => 'GTC',
209
+ 'price' => '0.5'
210
+ },
211
+ 'stopLoss' => {
212
+ 'timeInForce' => 'GTC',
213
+ 'price' => '2.5'
214
+ }
215
+ }
216
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/orders').with(body: options)
217
+ api.trade('1', options).update
218
+ expect(request).to have_been_requested.once
219
+ end
220
+
221
+ it 'updating a trade client extensions' do
222
+ options = {
223
+ 'clientExtensions' => {
224
+ 'comment' => 'This is a USD/CAD trade',
225
+ 'tag' => 'trade tag',
226
+ 'id' => 'my_usd_cad_trade'
227
+ }
228
+ }
229
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/clientExtensions').with(body: options)
230
+ api.trade('1', options).update
231
+ expect(request).to have_been_requested.once
232
+ end
233
+
234
+ it 'closing a trade' do
235
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/close')
236
+ api.trade('1').close
237
+ expect(request).to have_been_requested.once
238
+ end
239
+
240
+ it 'closing a trade partially' do
241
+ options = { 'units' => '10' }
242
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/close')
243
+ api.trade('1', options).close
244
+ expect(request).to have_been_requested.once
245
+ end
246
+
247
+ it 'retrieving all trades' do
248
+ options = { 'instrument' => 'USD_CAD' }
249
+ api.trades(options).show
250
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades').with(query: options)).to have_been_made.once
251
+ end
252
+
253
+ it 'retrieving all open trades' do
254
+ api.open_trades.show
255
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/openTrades')).to have_been_made.once
256
+ end
257
+ end
258
+
259
+ context 'positions for' do
260
+ it 'retrieving a position' do
261
+ api.position('EUR_USD').show
262
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_USD')).to have_been_made.once
263
+ end
264
+
265
+ it 'retrieving all positions' do
266
+ api.positions.show
267
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions')).to have_been_made.once
268
+ end
269
+
270
+ it 'retrieving all open positions' do
271
+ api.open_positions.show
272
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/openPositions')).to have_been_made.once
273
+ end
274
+
275
+ it 'closing a position' do
276
+ options = { 'longUnits' => 'ALL' }
277
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_CAD/close').with(body: options)
278
+ api.position('EUR_CAD', options).close
279
+ expect(request).to have_been_requested.once
280
+ end
281
+
282
+ it 'closing a position partially' do
283
+ options = { 'longUnits' => '99' }
284
+ request = stub_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_CAD/close').with(body: options)
285
+ api.position('EUR_CAD', options).close
286
+ expect(request).to have_been_requested.once
287
+ end
288
+ end
289
+
290
+ context 'transactions for' do
291
+ it 'retrieving a transaction' do
292
+ api.transaction('1').show
293
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/1')).to have_been_made.once
294
+ end
295
+
296
+ it 'retrieving all transactions' do
297
+ api.transactions.show
298
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions')).to have_been_made.once
299
+ end
300
+
301
+ it 'retrieving all transactions in date range' do
302
+ options = {
303
+ 'from' => '2016-08-01T02:00:00Z',
304
+ 'to' => '2016-08-15T02:00:00Z'
305
+ }
306
+ api.transactions(options).show
307
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions').with(query: options)).to have_been_made.once
308
+ end
309
+
310
+ it 'retrieving all transactions in an ID range' do
311
+ options = {
312
+ 'from' => '6409',
313
+ 'to' => '6412'
314
+ }
315
+ api.transactions_id_range(options).show
316
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/idrange').with(query: options)).to have_been_made.once
317
+ end
55
318
 
56
- before(:each) do
57
- stub_request(:any, /https:\/\/api-fxtrade\.oanda\.com\/v3.*/)
58
- api.http_verb = :get
59
- end
319
+ it 'retrieving all transactions since an ID' do
320
+ options = {
321
+ 'id' => '6411'
322
+ }
323
+ api.transactions_since_id(options).show
324
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/sinceid').with(query: options)).to have_been_made.once
325
+ end
326
+
327
+ it 'retrieving all transactions stream' do
328
+ body = <<~EOF
329
+ {"id":"6","accountID":"100-100-100","userID":12345678,"batchID":"4","requestID":"11111111111111111","time":"2021-08-20T11:56:39.037505525Z","type":"TAKE_PROFIT_ORDER","tradeID":"5","timeInForce":"GTC","triggerCondition":"DEFAULT","price":"0.71388","reason":"ON_FILL"}\n{"id":"7","accountID":"100-100-100","userID":12345678,"batchID":"4","requestID":"11111111111111112","time":"2021-08-20T11:56:39.037505525Z","type":"STOP_LOSS_ORDER","tradeID":"5","timeInForce":"GTC","triggerCondition":"DEFAULT","triggerMode":"TOP_OF_BOOK","price":"0.71258","distance":"0.00030","reason":"ON_FILL"}\n\r\n
330
+ EOF
331
+
332
+ headers = {
333
+ 'Transfer-Encoding' => 'chunked',
334
+ 'Content-Type' => 'application/octet-stream'
335
+ }
60
336
 
61
- context 'accounts for' do
62
- it 'retrieving an account' do
63
- api.account('100-100-100')
64
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100')).to have_been_made.once
65
- end
337
+ stub_request(:get, 'https://stream-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/stream').to_return(status: 200, body: body, headers: headers)
66
338
 
67
- it 'retrieving all accounts' do
68
- api.accounts
69
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts')).to have_been_made.once
70
- end
339
+ messages = []
71
340
 
72
- it 'retrieving a summary' do
73
- api.summary
74
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/summary')).to have_been_made.once
75
- end
341
+ stream_api.transactions_stream.show do |message|
342
+ messages << message
343
+ end
76
344
 
77
- it 'retrieving all instruments' do
78
- api.instruments
79
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/instruments')).to have_been_made.once
345
+ expect(a_request(:get, 'https://stream-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/stream')).to have_been_made.once
346
+ expect(messages.count).to eq(2)
347
+ end
80
348
  end
81
349
 
82
- it 'retrieving an instrument' do
83
- api.instruments('EUR_USD')
84
- options = { 'instruments' => 'EUR_USD' }
85
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/instruments').with(query: options)).to have_been_made.once
86
- end
350
+ context 'pricing for' do
351
+ it 'retrieving all pricing' do
352
+ options = {
353
+ 'instruments' => 'EUR_USD,USD_CAD'
354
+ }
355
+ api.pricing(options).show
356
+ expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/pricing').with(query: options)).to have_been_made.once
357
+ end
87
358
 
88
- it 'retrieving all changes' do
89
- api.last_transaction_id = '1'
90
- api.changes
91
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/changes').with(query: { 'sinceTransactionID' => '1' })).to have_been_made.once
92
- end
359
+ it 'retrieving all pricing stream' do
360
+ options = {
361
+ 'instruments' => 'EUR_USD,USD_CAD'
362
+ }
93
363
 
94
- it 'retrieving all changes since a transaction id' do
95
- options = {
96
- 'sinceTransactionID' => '1'
97
- }
98
- api.changes(options)
99
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/changes').with(query: options)).to have_been_made.once
100
- end
364
+ body = <<~EOF
365
+ {"type":"PRICE","time":"2019-01-31T18:16:38.818627106Z","bids":[{"price":"0.72711","liquidity":10000000}],"asks":[{"price":"0.72725","liquidity":10000000}],"closeoutBid":"0.72696","closeoutAsk":"0.72740","status":"tradeable","tradeable":true,"instrument":"USD_CAD"}\n{"type":"PRICE","time":"2019-01-31T18:16:48.270050596Z","bids":[{"price":"0.95533","liquidity":10000000}],"asks":[{"price":"0.95554","liquidity":10000000}],"closeoutBid":"0.95533","closeoutAsk":"0.95554","status":"tradeable","tradeable":true,"instrument":"EUR_USD"}\n\r\n
366
+ EOF
101
367
 
102
- it 'updating a configuration' do
103
- api.http_verb = :patch
104
- options = { 'alias' => 'Timmy!' }
105
- api.configuration(options)
106
- expect(a_request(:patch, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/configuration').with(body: options.to_json))
107
- end
108
- end
368
+ headers = {
369
+ 'Transfer-Encoding' => 'chunked',
370
+ 'Content-Type' => 'application/octet-stream'
371
+ }
109
372
 
110
- context 'instruments for' do
111
- let!(:api) { OandaApiV20::Api.new(base_uri: 'https://api-fxtrade.oanda.com/v3', account_id: '100-100-100', instrument: 'EUR_USD', headers: {}) }
373
+ stub_request(:get, 'https://stream-fxtrade.oanda.com/v3/accounts/100-100-100/pricing/stream?instruments=EUR_USD,USD_CAD').to_return(status: 200, body: body, headers: headers)
112
374
 
113
- it 'retrieving candlestick data' do
114
- options = { 'count' => '10' }
115
- api.candles(options)
116
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/instruments/EUR_USD/candles').with(query: options)).to have_been_made.once
117
- end
118
- end
375
+ messages = []
119
376
 
120
- context 'orders for' do
121
- it 'retrieving an order' do
122
- api.order('1')
123
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1')).to have_been_made.once
124
- end
377
+ stream_api.pricing_stream(options).show do |message|
378
+ messages << message
379
+ end
125
380
 
126
- it 'creating an order' do
127
- api.http_verb = :post
128
- options = {
129
- 'order' => {
130
- 'units' => '100',
131
- 'instrument' => 'EUR_CAD',
132
- 'timeInForce' => 'FOK',
133
- 'type' => 'MARKET',
134
- 'positionFill' => 'DEFAULT'
135
- }
136
- }
137
- api.order(options)
138
- expect(a_request(:post, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders').with(body: options.to_json)).to have_been_made.once
139
- end
140
-
141
- it 'updating an order' do
142
- api.http_verb = :put
143
- options = {
144
- 'order' => {
145
- 'timeInForce' => 'GTC',
146
- 'price' => '1.7000',
147
- 'type' => 'TAKE_PROFIT',
148
- 'tradeID' => '1'
149
- }
150
- }
151
- api.order('1', options)
152
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1').with(body: options.to_json)).to have_been_made.once
381
+ expect(a_request(:get, 'https://stream-fxtrade.oanda.com/v3/accounts/100-100-100/pricing/stream').with(query: options)).to have_been_made.once
382
+ expect(messages.count).to eq(2)
383
+ end
153
384
  end
385
+ end
154
386
 
155
- it 'updating an order client extensions' do
156
- api.http_verb = :put
157
- options = {
158
- 'clientExtensions' => {
159
- 'comment' => 'New comment for my limit order'
160
- }
161
- }
162
- api.order('1', options)
163
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1/clientExtensions').with(body: options.to_json)).to have_been_made.once
164
- end
387
+ describe 'network' do
388
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
389
+ let!(:api) { OandaApiV20::Api.new(client: client) }
165
390
 
166
- it 'cancelling an order' do
167
- api.http_verb = :put
168
- api.order('1')
169
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders/1/cancel')).to have_been_made.once
170
- end
391
+ let(:response_account) { '{"account":{"id":"100-100-100","NAV":"100000.0000","balance":"100000.0000","lastTransactionID":"99","orders":[],"positions":[],"trades":[],"pendingOrderCount":0},"lastTransactionID":"99"}' }
392
+ let!(:request_account) { stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100').to_return(status: 200, body: response_account, headers: {}) }
171
393
 
172
- it 'retrieving all orders' do
173
- api.orders
174
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders')).to have_been_made.once
175
- end
394
+ let(:response_accounts) { '{"accounts":[{"id":"100-100-100","tags":[]}]}' }
395
+ let!(:request_accounts) { stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts').to_return(status: 200, body: response_accounts, headers: {}) }
176
396
 
177
- it 'retrieving all orders' do
178
- options = { 'instrument' => 'USD_CAD' }
179
- api.orders(options)
180
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders').with(query: options)).to have_been_made.once
397
+ it 'makes a request to Oanda API' do
398
+ api.accounts.show
399
+ expect(request_accounts).to have_been_requested
400
+ expect(request_accounts).to have_been_requested.at_most_once
181
401
  end
182
402
 
183
- it 'retrieving all pending orders' do
184
- api.pending_orders
185
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/pendingOrders')).to have_been_made.once
403
+ it 'returns the response from Oanda API' do
404
+ expect(api.accounts.show).to eq(JSON.parse(response_accounts))
405
+ expect(api.account('100-100-100').show).to eq(JSON.parse(response_account))
186
406
  end
187
407
  end
188
408
 
189
- context 'trades for' do
190
- it 'retrieving a trade' do
191
- api.trade('1')
192
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1')).to have_been_made.once
193
- end
194
-
195
- it 'updating a trade' do
196
- api.http_verb = :put
197
- options = {
198
- 'takeProfit' => {
199
- 'timeInForce' => 'GTC',
200
- 'price' => '0.5'
201
- },
202
- 'stopLoss' => {
203
- 'timeInForce' => 'GTC',
204
- 'price' => '2.5'
409
+ describe 'sets the correct HTTP verb' do
410
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
411
+ let!(:api) { OandaApiV20::Api.new(client: client, account_id: '100-100-100') }
412
+
413
+ context 'for GET' do
414
+ let!(:request) { stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts') }
415
+
416
+ it 'uses the correct HTTP verb' do
417
+ api.accounts.show
418
+ expect(request).to have_been_requested.once
419
+ end
420
+
421
+ it 'clears the HTTP verb attribute after use' do
422
+ api.accounts.show
423
+ expect(api.send(:http_verb)).to be_nil
424
+ end
425
+ end
426
+
427
+ context 'for POST' do
428
+ let!(:options) {
429
+ {
430
+ 'order' => {
431
+ 'units' => '100',
432
+ 'instrument' => 'EUR_CAD',
433
+ 'timeInForce' => 'FOK',
434
+ 'type' => 'MARKET',
435
+ 'positionFill' => 'DEFAULT'
436
+ }
205
437
  }
206
438
  }
207
- api.trade('1', options)
208
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/orders').with(body: options.to_json)).to have_been_made.once
209
- end
439
+ let!(:request) { stub_request(:post, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/orders').with(body: options) }
210
440
 
211
- it 'updating a trade client extensions' do
212
- api.http_verb = :put
213
- options = {
214
- 'clientExtensions' => {
215
- 'comment' => 'This is a USD/CAD trade',
216
- 'tag' => 'trade tag',
217
- 'id' => 'my_usd_cad_trade'
218
- }
219
- }
220
- api.trade('1', options)
221
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/clientExtensions').with(body: options.to_json)).to have_been_made.once
222
- end
441
+ it 'uses the correct HTTP verb' do
442
+ api.order(options).create
443
+ expect(request).to have_been_requested.once
444
+ end
223
445
 
224
- it 'closing a trade' do
225
- api.http_verb = :put
226
- api.trade('1')
227
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/close')).to have_been_made.once
446
+ it 'clears the HTTP verb attribute after use' do
447
+ api.order(options).create
448
+ expect(api.send(:http_verb)).to be_nil
449
+ end
228
450
  end
229
451
 
230
- it 'closing a trade partially' do
231
- api.http_verb = :put
232
- options = { 'units' => '10' }
233
- api.trade('1', options)
234
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades/1/close')).to have_been_made.once
235
- end
452
+ context 'for PATCH' do
453
+ let!(:options) { { 'alias' => 'Timmy!' } }
454
+ let!(:request) { stub_request(:patch, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/configuration').with(body: options) }
236
455
 
237
- it 'retrieving all trades' do
238
- options = { 'instrument' => 'USD_CAD' }
239
- api.trades(options)
240
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/trades').with(query: options)).to have_been_made.once
241
- end
456
+ it 'uses the correct HTTP verb' do
457
+ api.configuration(options).update
458
+ expect(request).to have_been_requested.once
459
+ end
242
460
 
243
- it 'retrieving all open trades' do
244
- api.open_trades
245
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/openTrades')).to have_been_made.once
461
+ it 'clears the HTTP verb attribute after use' do
462
+ api.configuration(options).update
463
+ expect(api.send(:http_verb)).to be_nil
464
+ end
246
465
  end
247
466
  end
467
+ end
248
468
 
249
- context 'positions for' do
250
- it 'retrieving a position' do
251
- api.position('EUR_USD')
252
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_USD')).to have_been_made.once
253
- end
254
-
255
- it 'retrieving all positions' do
256
- api.positions
257
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions')).to have_been_made.once
258
- end
259
-
260
- it 'retrieving all open positions' do
261
- api.open_positions
262
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/openPositions')).to have_been_made.once
263
- end
264
-
265
- it 'closing a position' do
266
- api.http_verb = :put
267
- options = { 'longUnits' => 'ALL' }
268
- api.position('EUR_CAD', options)
269
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_CAD/close').with(body: options.to_json)).to have_been_made.once
270
- end
469
+ describe 'public methods' do
470
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token') }
471
+ let!(:api) { OandaApiV20::Api.new(client: client) }
472
+ let(:public_methods) { [
473
+ :account, :accounts, :summary, :instruments, :changes, :configuration,
474
+ :order, :orders, :pending_orders,
475
+ :trade, :trades, :open_trades,
476
+ :position, :positions, :open_positions,
477
+ :transaction, :transactions, :transactions_id_range, :transactions_since_id,
478
+ :transactions_stream,
479
+ :pricing,
480
+ :pricing_stream,
481
+ :candles
482
+ ] }
271
483
 
272
- it 'closing a position partially' do
273
- api.http_verb = :put
274
- options = { 'longUnits' => '99' }
275
- api.position('EUR_CAD', options)
276
- expect(a_request(:put, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/positions/EUR_CAD/close').with(body: options.to_json)).to have_been_made.once
484
+ it 'responds to all public methods' do
485
+ public_methods.each do |public_method|
486
+ expect(api.respond_to?(public_method)).to be_truthy
277
487
  end
278
488
  end
279
489
 
280
- context 'transactions for' do
281
- it 'retrieving a transaction' do
282
- api.transaction('1')
283
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/1')).to have_been_made.once
284
- end
490
+ it 'returns an OandaApiV20::Api instance when calling any of the public methods' do
491
+ expect(api.accounts).to be_an_instance_of(OandaApiV20::Api)
492
+ end
285
493
 
286
- it 'retrieving all transactions' do
287
- api.transactions
288
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions')).to have_been_made.once
289
- end
494
+ it 'makes a request to Oanda API when calling any of the action methods' do
495
+ request = stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts')
496
+ api.accounts.show
497
+ expect(request).to have_been_requested.once
498
+ end
499
+ end
290
500
 
291
- it 'retrieving all transactions in date range' do
292
- options = {
293
- 'from' => '2016-08-01T02:00:00Z',
294
- 'to' => '2016-08-15T02:00:00Z'
295
- }
296
- api.transactions(options)
297
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions').with(query: options)).to have_been_made.once
501
+ describe 'private methods' do
502
+ describe '#set_http_verb' do
503
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
504
+ let!(:api) { OandaApiV20::Api.new(client: client) }
505
+
506
+ describe 'sets the correct HTTP verb' do
507
+ it 'for GET' do
508
+ api.send(:set_http_verb, :show, nil)
509
+ expect(api.send(:http_verb)).to eq(:get)
510
+ end
511
+
512
+ it 'for POST' do
513
+ api.send(:set_http_verb, :create, nil)
514
+ expect(api.send(:http_verb)).to eq(:post)
515
+ end
516
+
517
+ it 'for PUT' do
518
+ api.send(:set_http_verb, :update, nil)
519
+ expect(api.send(:http_verb)).to eq(:put)
520
+ end
521
+
522
+ it 'for PUT' do
523
+ api.send(:set_http_verb, :cancel, nil)
524
+ expect(api.send(:http_verb)).to eq(:put)
525
+ end
526
+
527
+ it 'for PUT' do
528
+ api.send(:set_http_verb, :close, nil)
529
+ expect(api.send(:http_verb)).to eq(:put)
530
+ end
531
+
532
+ it 'for PATCH' do
533
+ api.send(:set_http_verb, :update, :configuration)
534
+ expect(api.send(:http_verb)).to eq(:patch)
535
+ end
536
+
537
+ it 'for POST' do
538
+ api.send(:set_http_verb, :create, nil)
539
+ expect(api.send(:http_verb)).to eq(:post)
540
+ end
298
541
  end
542
+ end
299
543
 
300
- it 'retrieving all transactions in an id range' do
301
- options = {
302
- 'from' => '6409',
303
- 'to' => '6412'
304
- }
305
- api.transactions_id_range(options)
306
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/idrange').with(query: options)).to have_been_made.once
307
- end
544
+ describe '#set_last_action_and_arguments' do
545
+ let!(:client) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
546
+ let!(:api) { OandaApiV20::Api.new(client: client) }
308
547
 
309
- it 'retrieving all transactions since an id' do
310
- options = {
311
- 'id' => '6411'
312
- }
313
- api.transactions_since_id(options)
314
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/transactions/sinceid').with(query: options)).to have_been_made.once
548
+ it 'sets the last_action attribute' do
549
+ api.send(:set_last_action_and_arguments, :accounts)
550
+ expect(api.send(:last_action)).to eq(:accounts)
315
551
  end
316
- end
317
552
 
318
- context 'pricing for' do
319
- it 'retrieving all pricing' do
320
- options = {
321
- 'instruments' => 'EUR_USD,USD_CAD'
322
- }
323
- api.pricing(options)
324
- expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/pricing').with(query: options)).to have_been_made.once
553
+ it 'sets the last_arguments attribute' do
554
+ api.send(:set_last_action_and_arguments, :account, '100-100-100')
555
+ expect(api.send(:last_arguments)).to eq(['100-100-100'])
325
556
  end
326
557
  end
327
558
  end