oanda_api_v20 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e44f2d12b3c6e2fafe20137218ecfec9d8f4678
4
- data.tar.gz: cec80bbcce9fe58398319d715a2d7a50cc1f37f4
3
+ metadata.gz: c705d6ed7e93994a827ab0f090c3ffb210af8abf
4
+ data.tar.gz: 9beca7942edcad3e584e29c2607ace094b38c3dd
5
5
  SHA512:
6
- metadata.gz: 3b4439bf716d67a9117036009817ea19caa302b610b234832b7f7294116df3ab6b5ee6763967c6f4932a0630143367bcf498132f0b689e0c7627f9afdeff6a4e
7
- data.tar.gz: 984932b04c7095c02af46a3000a6b2c8935f8062e707d235a45d07afcc43a4f94d719744d83b7277ab64e90525f1e3086982134b6e2ea58c001714bc717d694b
6
+ metadata.gz: b3206d93bf544c75b273a3685acb11a5485877e99127925c51df9173f50fd968ae7b335f7b40f7e67493fdc61e55ac977cef11a2d900eb6b737ce68119db203f
7
+ data.tar.gz: da31de41ee3bfd5cb0a245ce1e185e0e4ba1773d0e192efade7cd9536ffe9ce96ca8c2f5e1d507abf89cc629e62ff356d9625626b41d1490174e101146fbbc29
@@ -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
@@ -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 When an action method like show, update, cancel, close or create was called.
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.message
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?
@@ -3,9 +3,15 @@ module OandaApiV20
3
3
  include HTTParty
4
4
 
5
5
  BASE_URI = {
6
- live: 'https://api-fxtrade.oanda.com/v3',
7
- practice: 'https://api-fxpractice.oanda.com/v3'
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
- @base_uri = options[:practice] == true ? BASE_URI[:practice] : BASE_URI[:live]
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 RequestError < 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
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
@@ -1,3 +1,3 @@
1
1
  module OandaApiV20
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  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) { OandaApiV20::Client.new(access_token: 'my_access_token', base_uri: 'https://api-fxtrade.oanda.com/v3', headers: {}) }
65
- let!(:api) { OandaApiV20::Api.new(client: client, account_id: '100-100-100') }
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.0.0
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: 2018-04-24 00:00:00.000000000 Z
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