oanda_api_v20 2.0.0 → 2.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +28 -0
- data/lib/oanda_api_v20/api.rb +2 -2
- data/lib/oanda_api_v20/client.rb +12 -5
- data/lib/oanda_api_v20/exceptions.rb +11 -1
- data/lib/oanda_api_v20/pricing.rb +24 -0
- data/lib/oanda_api_v20/version.rb +1 -1
- data/spec/oanda_api_v20/api_spec.rb +31 -2
- data/spec/oanda_api_v20/exceptions_spec.rb +24 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c705d6ed7e93994a827ab0f090c3ffb210af8abf
|
4
|
+
data.tar.gz: 9beca7942edcad3e584e29c2607ace094b38c3dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3206d93bf544c75b273a3685acb11a5485877e99127925c51df9173f50fd968ae7b335f7b40f7e67493fdc61e55ac977cef11a2d900eb6b737ce68119db203f
|
7
|
+
data.tar.gz: da31de41ee3bfd5cb0a245ce1e185e0e4ba1773d0e192efade7cd9536ffe9ce96ca8c2f5e1d507abf89cc629e62ff356d9625626b41d1490174e101146fbbc29
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 2.1.0
|
4
|
+
#### 2019-02-01
|
5
|
+
* New pricing_stream endpoint. Big thanks to @joseluis-fw.
|
6
|
+
* Raises an OandaApiV20::ParseError exception when a malformed response returned from Oanda API.
|
7
|
+
* The OandaApiV20::RequestError exception object now has an original_exception attribute which contains the original exception object raised.
|
8
|
+
|
3
9
|
## 2.0.0
|
4
10
|
#### 2018-04-24
|
5
11
|
* Fixed issues when trying to reuse a Client object to make HTTP requests to Oanda.
|
data/README.md
CHANGED
@@ -33,6 +33,10 @@ If you would like to trade with your test account:
|
|
33
33
|
|
34
34
|
client = OandaApiV20.new(access_token: 'my_access_token', practice: true)
|
35
35
|
|
36
|
+
If you would like to use the streaming endpoints:
|
37
|
+
|
38
|
+
client = OandaApiV20.new(access_token: 'my_access_token', stream: true)
|
39
|
+
|
36
40
|
If you need your requests to go through a proxy:
|
37
41
|
|
38
42
|
client = OandaApiV20.new(access_token: 'my_access_token', proxy_url: 'https://user:pass@proxy.com:80')
|
@@ -305,10 +309,34 @@ options = {
|
|
305
309
|
client.account('account_id').pricing(options).show
|
306
310
|
```
|
307
311
|
|
312
|
+
```ruby
|
313
|
+
client = OandaApiV20.new(access_token: 'my_access_token', stream: true)
|
314
|
+
|
315
|
+
options = {
|
316
|
+
'instruments' => 'EUR_USD,USD_CAD'
|
317
|
+
}
|
318
|
+
|
319
|
+
client.account('account_id').pricing_stream(options).show do |json|
|
320
|
+
puts json if json['type'] == 'PRICE'
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
308
324
|
## Exceptions
|
309
325
|
|
326
|
+
A `OandaApiV20::ParseError` will be raised when a response from the Oanda API is malformed.
|
327
|
+
|
310
328
|
A `OandaApiV20::RequestError` will be raised when a request to the Oanda API failed for any reason.
|
311
329
|
|
330
|
+
You can access the original exception in a `OandaApiV20::RequestError`:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
begin
|
334
|
+
do_something
|
335
|
+
rescue OandaApiV20::RequestError => e
|
336
|
+
e.original_exception
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
312
340
|
## Contributing
|
313
341
|
|
314
342
|
1. Fork it
|
data/lib/oanda_api_v20/api.rb
CHANGED
@@ -33,7 +33,7 @@ module OandaApiV20
|
|
33
33
|
define_method(method_name) do |*args, &block|
|
34
34
|
# Add the block below before each of the api_methods to set the last_action and last_arguments.
|
35
35
|
# Return the OandaApiV20::Api object to allow for method chaining when any of the api_methods have been called.
|
36
|
-
# Only make an HTTP request to Oanda API
|
36
|
+
# Only make an HTTP request to Oanda API when an action method like show, update, cancel, close or create was called.
|
37
37
|
set_last_action_and_arguments(method_name, *args)
|
38
38
|
return self unless http_verb
|
39
39
|
|
@@ -56,7 +56,7 @@ module OandaApiV20
|
|
56
56
|
last_arguments.nil? || last_arguments.empty? ? send(last_action, &block) : send(last_action, *last_arguments, &block)
|
57
57
|
end
|
58
58
|
rescue Http::Exceptions::HttpException => e
|
59
|
-
raise OandaApiV20::RequestError, e.
|
59
|
+
raise OandaApiV20::RequestError.new(e.message, response: e.response, original_exception: e.original_exception)
|
60
60
|
end
|
61
61
|
|
62
62
|
if response.body && !response.body.empty?
|
data/lib/oanda_api_v20/client.rb
CHANGED
@@ -3,9 +3,15 @@ module OandaApiV20
|
|
3
3
|
include HTTParty
|
4
4
|
|
5
5
|
BASE_URI = {
|
6
|
-
live:
|
7
|
-
|
8
|
-
|
6
|
+
live: {
|
7
|
+
api: 'https://api-fxtrade.oanda.com/v3',
|
8
|
+
stream: 'https://stream-fxtrade.oanda.com/v3'
|
9
|
+
},
|
10
|
+
practice: {
|
11
|
+
api: 'https://api-fxpractice.oanda.com/v3',
|
12
|
+
stream: 'https://stream-fxpractice.oanda.com/v3'
|
13
|
+
}
|
14
|
+
}.freeze
|
9
15
|
|
10
16
|
attr_accessor :access_token, :proxy_url, :max_requests_per_second, :connection_pool_size, :debug
|
11
17
|
attr_reader :base_uri, :headers
|
@@ -20,7 +26,8 @@ module OandaApiV20
|
|
20
26
|
@connection_pool_size ||= 2
|
21
27
|
@max_requests_per_second ||= 100
|
22
28
|
@last_api_request_at = Array.new(max_requests_per_second)
|
23
|
-
|
29
|
+
uris = options[:practice] == true ? BASE_URI[:practice] : BASE_URI[:live]
|
30
|
+
@base_uri = options[:stream] == true ? uris[:stream] : uris[:api]
|
24
31
|
|
25
32
|
@headers = {}
|
26
33
|
@headers['Authorization'] = "Bearer #{access_token}"
|
@@ -39,7 +46,7 @@ module OandaApiV20
|
|
39
46
|
pool_size: connection_pool_size
|
40
47
|
}
|
41
48
|
|
42
|
-
persistent_connection_adapter_options.merge!(logger: ::Logger.new(STDOUT)) if debug
|
49
|
+
persistent_connection_adapter_options.merge!(logger: ::Logger.new(STDOUT), debug_output: ::Logger.new(STDOUT)) if debug
|
43
50
|
Client.persistent_connection_adapter(persistent_connection_adapter_options)
|
44
51
|
end
|
45
52
|
|
@@ -1,4 +1,14 @@
|
|
1
1
|
module OandaApiV20
|
2
2
|
class ApiError < RuntimeError; end
|
3
|
-
class
|
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
|
4
14
|
end
|
@@ -5,5 +5,29 @@ 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 = StringIO.new
|
12
|
+
|
13
|
+
Client.send(http_verb, "#{base_uri}/accounts/#{account_id}/pricing/stream", headers: headers, query: options, stream_body: true) do |fragment|
|
14
|
+
begin
|
15
|
+
next if fragment.empty?
|
16
|
+
|
17
|
+
buffer << fragment
|
18
|
+
next unless fragment.match(/\n\Z/)
|
19
|
+
|
20
|
+
buffer.string.split("\n").each do |message|
|
21
|
+
cleaned_message = message.strip
|
22
|
+
next if cleaned_message.empty?
|
23
|
+
yield JSON.parse(cleaned_message)
|
24
|
+
end
|
25
|
+
rescue JSON::ParseError => e
|
26
|
+
raise OandaApiV20::ParseError, "#{e.message} in '#{fragment}'"
|
27
|
+
ensure
|
28
|
+
buffer.flush
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
8
32
|
end
|
9
33
|
end
|
@@ -61,8 +61,10 @@ describe OandaApiV20::Api do
|
|
61
61
|
|
62
62
|
describe '#method_missing' do
|
63
63
|
describe 'constructs the correct API URL under' do
|
64
|
-
let!(:client)
|
65
|
-
let!(:
|
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') }
|
66
68
|
|
67
69
|
before(:each) do
|
68
70
|
stub_request(:get, /https:\/\/api-fxtrade\.oanda\.com\/v3.*/)
|
@@ -331,6 +333,32 @@ describe OandaApiV20::Api do
|
|
331
333
|
api.pricing(options).show
|
332
334
|
expect(a_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100/pricing').with(query: options)).to have_been_made.once
|
333
335
|
end
|
336
|
+
|
337
|
+
it 'retrieving all pricing stream' do
|
338
|
+
options = {
|
339
|
+
'instruments' => 'EUR_USD,USD_CAD'
|
340
|
+
}
|
341
|
+
|
342
|
+
body = <<~EOF
|
343
|
+
{"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
|
344
|
+
EOF
|
345
|
+
|
346
|
+
headers = {
|
347
|
+
'Transfer-Encoding' => 'chunked',
|
348
|
+
'Content-Type' => 'application/octet-stream'
|
349
|
+
}
|
350
|
+
|
351
|
+
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)
|
352
|
+
|
353
|
+
messages = []
|
354
|
+
|
355
|
+
stream_api.pricing_stream(options).show do |message|
|
356
|
+
messages << message
|
357
|
+
end
|
358
|
+
|
359
|
+
expect(a_request(:get, 'https://stream-fxtrade.oanda.com/v3/accounts/100-100-100/pricing/stream').with(query: options)).to have_been_made.once
|
360
|
+
expect(messages.count).to eq(2)
|
361
|
+
end
|
334
362
|
end
|
335
363
|
end
|
336
364
|
|
@@ -426,6 +454,7 @@ describe OandaApiV20::Api do
|
|
426
454
|
:position, :positions, :open_positions,
|
427
455
|
:transaction, :transactions, :transactions_id_range, :transactions_since_id,
|
428
456
|
:pricing,
|
457
|
+
:pricing_stream,
|
429
458
|
:candles
|
430
459
|
] }
|
431
460
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OandaApiV20::RequestError do
|
4
|
+
describe '#initialize' do
|
5
|
+
let(:response) { { code: 200, body: '' } }
|
6
|
+
|
7
|
+
it 'sets the message attribute' do
|
8
|
+
exception = OandaApiV20::RequestError.new('An error as occured while processing response.')
|
9
|
+
expect(exception.message).to eq('An error as occured while processing response.')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'sets the response attribute when supplied' do
|
13
|
+
exception = OandaApiV20::RequestError.new('An error as occured while processing response.', response: response)
|
14
|
+
expect(exception.response).to eq(response)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'sets the original_exception attribue when supplied' do
|
18
|
+
original_exception = OpenSSL::SSL::SSLError.new('SSL_read: sslv3 alert handshake failure')
|
19
|
+
exception = OandaApiV20::RequestError.new('An error as occured while processing response.', original_exception: original_exception)
|
20
|
+
expect(exception.original_exception).to be_an_instance_of(OpenSSL::SSL::SSLError)
|
21
|
+
expect(exception.original_exception.message).to eq('SSL_read: sslv3 alert handshake failure')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oanda_api_v20
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kobus Joubert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- oanda_api_v20.gemspec
|
140
140
|
- spec/oanda_api_v20/api_spec.rb
|
141
141
|
- spec/oanda_api_v20/client_spec.rb
|
142
|
+
- spec/oanda_api_v20/exceptions_spec.rb
|
142
143
|
- spec/oanda_api_v20/oanda_api_v20_spec.rb
|
143
144
|
- spec/spec_helper.rb
|
144
145
|
homepage: http://rubygems.org/gems/oanda_api_v20
|
@@ -168,5 +169,6 @@ summary: Ruby Oanda REST API V20
|
|
168
169
|
test_files:
|
169
170
|
- spec/oanda_api_v20/api_spec.rb
|
170
171
|
- spec/oanda_api_v20/client_spec.rb
|
172
|
+
- spec/oanda_api_v20/exceptions_spec.rb
|
171
173
|
- spec/oanda_api_v20/oanda_api_v20_spec.rb
|
172
174
|
- spec/spec_helper.rb
|