oanda_api_v20 1.5.0 → 2.2.0

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