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