oanda_api 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/{.rspec_non_jruby → .rspec} +0 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +5 -0
- data/README.md +29 -9
- data/lib/oanda_api.rb +3 -0
- data/lib/oanda_api/client/client.rb +3 -3
- data/lib/oanda_api/client/namespace_proxy.rb +14 -9
- data/lib/oanda_api/client/token_client.rb +2 -3
- data/lib/oanda_api/client/username_client.rb +1 -1
- data/lib/oanda_api/configuration.rb +3 -3
- data/lib/oanda_api/errors.rb +4 -1
- data/lib/oanda_api/resource/heartbeat.rb +13 -0
- data/lib/oanda_api/resource_base.rb +17 -3
- data/lib/oanda_api/resource_collection.rb +7 -8
- data/lib/oanda_api/streaming/client.rb +153 -0
- data/lib/oanda_api/streaming/request.rb +135 -0
- data/lib/oanda_api/utils/utils.rb +2 -2
- data/lib/oanda_api/version.rb +1 -1
- data/spec/oanda_api/resource_base_spec.rb +47 -0
- data/spec/oanda_api/resource_collection_spec.rb +2 -2
- data/spec/oanda_api/streaming/client_spec.rb +132 -0
- data/spec/oanda_api/streaming/request_spec.rb +172 -0
- data/spec/spec_helper.rb +4 -0
- metadata +121 -138
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c7acc3d30cf5e19c55528d74251a43ad5e6387c
|
4
|
+
data.tar.gz: 6bb298acd99fb97dd08d82d86455e910694e56cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f44a232f8c05ee18ec10b8a0e98cba28d865e8533ba2b0efdf7eca8e87936a5e9fadbc2ee49c56a5916f81b7d7063d8dbf9b9371c87d920a7d7f36992bac8f8b
|
7
|
+
data.tar.gz: 63c786402973185453b85fee525936234ac690a49b5119922dfbebb6fc4613512f36cdbe5cfc0bc5d883e2fafec20cf29bd8ea3f19d20fffe6b11e0305d25b7b
|
data/{.rspec_non_jruby → .rspec}
RENAMED
File without changes
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 0.90
|
4
|
+
|
5
|
+
* Add support for live [Streaming](http://developer.oanda.com/rest-live/streaming/).
|
6
|
+
* Add #to_json serialization to resource classes.
|
7
|
+
* fixed Yardoc formatting.
|
8
|
+
|
9
|
+
###What's New?
|
10
|
+
OandaAPI now supports Oanda's streaming API for consuming realtime ticks and account transactions. See the example in the README and have a look at the specs for `OandaAPI::Streaming::Client`.
|
11
|
+
|
data/Gemfile
CHANGED
@@ -4,6 +4,9 @@ gemspec
|
|
4
4
|
|
5
5
|
group :development do
|
6
6
|
gem "pry"
|
7
|
+
gem 'pry-stack_explorer'
|
8
|
+
gem 'pry-byebug'
|
9
|
+
|
7
10
|
gem "guard"
|
8
11
|
gem "guard-rspec"
|
9
12
|
gem "fuubar"
|
@@ -11,3 +14,5 @@ group :development do
|
|
11
14
|
gem "github-markup"
|
12
15
|
gem "rubocop", require: false
|
13
16
|
end
|
17
|
+
|
18
|
+
gem 'simplecov', :require => false, :group => :test
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ client = OandaAPI::Client::TokenClient.new(:practice, "practice_account_token")
|
|
12
12
|
account = client.account(12345).get
|
13
13
|
```
|
14
14
|
|
15
|
-
returns an OandaAPI::Resource::Account
|
15
|
+
returns an `OandaAPI::Resource::Account`, with method accessors for all of the [Account](http://developer.oanda.com/rest-live/accounts/) attributes defined by the Oanda API.
|
16
16
|
|
17
17
|
|
18
18
|
## Features
|
@@ -34,7 +34,7 @@ Some Examples
|
|
34
34
|
```ruby
|
35
35
|
require 'oanda_api'
|
36
36
|
|
37
|
-
client = OandaAPI::Client::TokenClient.new(:practice, ENV
|
37
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV.fetch("OANDA_PRACTICE_TOKEN"))
|
38
38
|
|
39
39
|
prices = client.prices(instruments: %w(EUR_USD USD_JPY)).get
|
40
40
|
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
```ruby
|
51
51
|
require 'oanda_api'
|
52
52
|
|
53
|
-
client = OandaAPI::Client::TokenClient.new(:practice, ENV
|
53
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV.fetch("OANDA_PRACTICE_TOKEN"))
|
54
54
|
|
55
55
|
candles = client.candles( instrument: "EUR_USD",
|
56
56
|
granularity: "M1",
|
@@ -78,7 +78,7 @@ end
|
|
78
78
|
```ruby
|
79
79
|
require 'oanda_api'
|
80
80
|
|
81
|
-
client = OandaAPI::Client::TokenClient.new(:practice, ENV
|
81
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV.fetch("OANDA_PRACTICE_TOKEN"))
|
82
82
|
|
83
83
|
order = client.account(12345)
|
84
84
|
.order(instrument: "USD_JPY",
|
@@ -96,7 +96,7 @@ order.trade_opened.id # => 175491416
|
|
96
96
|
```ruby
|
97
97
|
require 'oanda_api'
|
98
98
|
|
99
|
-
client = OandaAPI::Client::TokenClient.new(:practice, ENV
|
99
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV.fetch("OANDA_PRACTICE_TOKEN"))
|
100
100
|
|
101
101
|
account = client.account(12345) # => OandaAPI::NamespaceProxy
|
102
102
|
position = account.position("USD_JPY").get # => OandaAPI::Resource::Position
|
@@ -118,6 +118,24 @@ transaction.time # => 2014-12-19 03:29:48 UTC
|
|
118
118
|
transaction.type # => "MARKET_ORDER_CREATE"
|
119
119
|
```
|
120
120
|
|
121
|
+
##Streaming
|
122
|
+
OandaAPI also supports the [Oanda realtime streaming API](http://developer.oanda.com/rest-live/streaming/).
|
123
|
+
|
124
|
+
For example to stream live prices,
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
client = OandaAPI::Streaming::Client.new(:practice, ENV.fetch("OANDA_PRACTICE_TOKEN"))
|
128
|
+
prices = client.prices(account_id: 1234, instruments: %w[AUD_CAD AUD_CHF])
|
129
|
+
prices.stream do |price|
|
130
|
+
# Note: The code in this block should handle the price
|
131
|
+
# as efficently as possible, otherwise the connection could timeout.
|
132
|
+
# For example, you could publish the tick on a queue to be handled
|
133
|
+
# by some other thread or process.
|
134
|
+
price # => OandaAPI::Resource::Price
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
|
121
139
|
Documentation
|
122
140
|
-------------
|
123
141
|
|
@@ -125,15 +143,15 @@ Please see the [Oanda Developer Wiki](http://developer.oanda.com/rest-live/intro
|
|
125
143
|
for detailed documentation and API usage notes.
|
126
144
|
|
127
145
|
|
128
|
-
| Ruby | Oanda REST API |
|
146
|
+
| Ruby | Oanda REST API |
|
129
147
|
|:---------------------------|:---------------------|
|
130
|
-
| client.accounts.get | GET /v1/accounts |
|
148
|
+
| client.accounts.get | GET /v1/accounts |
|
131
149
|
| client.account(123).get | GET /v1/accounts/123 |
|
132
150
|
| client.account.create | POST /v1/accounts |
|
133
151
|
| client.instruments.get | GET /v1/instruments |
|
134
152
|
| client.prices(instruments: ["EUR_USD","USD_JPY"]).get | GET /v1/prices/?instruments=EUR_USD%2CUSD_JPY |
|
135
|
-
| client.account(123).orders.get | GET /v1/accounts/123/orders
|
136
|
-
| client.account(123).order(123).get | GET /v1/accounts/123/orders/123
|
153
|
+
| client.account(123).orders.get | GET /v1/accounts/123/orders |
|
154
|
+
| client.account(123).order(123).get | GET /v1/accounts/123/orders/123 |
|
137
155
|
| client.account(123).order( *options* ).create | POST /v1/accounts/123/orders |
|
138
156
|
| client.account(123).order(id:123, *options* ).update | PATCH /v1/accounts/123/orders/123 |
|
139
157
|
| client.account(123).order(123).close | DELETE /v1/accounts/123/orders/123 |
|
@@ -216,3 +234,5 @@ License
|
|
216
234
|
|
217
235
|
Copyright (c) 2014 Dean Missikowski. Distributed under the MIT License. See
|
218
236
|
[LICENSE](LICENSE) for further details.
|
237
|
+
|
238
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/oanda_api.rb
CHANGED
@@ -10,12 +10,15 @@ require_relative 'oanda_api/client/namespace_proxy'
|
|
10
10
|
require_relative 'oanda_api/client/resource_descriptor'
|
11
11
|
require_relative 'oanda_api/client/token_client'
|
12
12
|
require_relative 'oanda_api/client/username_client'
|
13
|
+
require_relative 'oanda_api/streaming/client'
|
14
|
+
require_relative 'oanda_api/streaming/request'
|
13
15
|
require_relative 'oanda_api/errors'
|
14
16
|
require_relative 'oanda_api/resource_base'
|
15
17
|
require_relative 'oanda_api/resource_collection'
|
16
18
|
require_relative 'oanda_api/resource/account'
|
17
19
|
require_relative 'oanda_api/resource/instrument'
|
18
20
|
require_relative 'oanda_api/resource/candle'
|
21
|
+
require_relative 'oanda_api/resource/heartbeat'
|
19
22
|
require_relative 'oanda_api/resource/order'
|
20
23
|
require_relative 'oanda_api/resource/position'
|
21
24
|
require_relative 'oanda_api/resource/price'
|
@@ -8,7 +8,7 @@ module OandaAPI
|
|
8
8
|
# Provides everything needed for accessing the API.
|
9
9
|
#
|
10
10
|
# - Uses persistant http connections.
|
11
|
-
# - Uses
|
11
|
+
# - Uses `OpenSSL::SSL::VERIFY_PEER` to always validate SSL certificates.
|
12
12
|
# - Uses compression if enabled (see {Configuration#use_compression}).
|
13
13
|
# - Uses request rate limiting if enabled (see {Configuration#use_request_throttling}).
|
14
14
|
module Client
|
@@ -92,7 +92,7 @@ module OandaAPI
|
|
92
92
|
# Maps An API _action_ to a corresponding http verb.
|
93
93
|
#
|
94
94
|
# @param [Symbol] method an API action. Supported actions are:
|
95
|
-
#
|
95
|
+
# `:create`, `:close`, `:delete`, `:get`, `:update`.
|
96
96
|
#
|
97
97
|
# @return [Symbol] an http verb.
|
98
98
|
def self.map_method_to_http_verb(method)
|
@@ -167,7 +167,7 @@ module OandaAPI
|
|
167
167
|
|
168
168
|
# @private
|
169
169
|
# Enables method-chaining.
|
170
|
-
# @return [
|
170
|
+
# @return [NamespaceProxy]
|
171
171
|
def method_missing(sym, *args)
|
172
172
|
NamespaceProxy.new self, sym, args.first
|
173
173
|
end
|
@@ -24,12 +24,13 @@ module OandaAPI
|
|
24
24
|
# ordered list of segments, joined, creates a path to a resource URI.
|
25
25
|
#
|
26
26
|
# @param [Hash] conditions an optional list of parameters that typically
|
27
|
-
# specifies conditions and filters for a resource request. A
|
27
|
+
# specifies conditions and filters for a resource request. A _"key"_
|
28
28
|
# or _"id"_ is a condition that identifies a particular resource. If a
|
29
29
|
# key condition is included, it is extracted and added as a namespace
|
30
30
|
# segment. See {#extract_key_and_conditions}.
|
31
31
|
def initialize(client, namespace_segment, conditions)
|
32
|
-
fail ArgumentError, "expecting an OandaAPI::Client instance" unless
|
32
|
+
fail ArgumentError, "expecting an OandaAPI::Client instance" unless
|
33
|
+
client && client.is_a?(OandaAPI::Client) || client.is_a?(OandaAPI::Streaming::Client)
|
33
34
|
fail ArgumentError, "expecting a namespace value" if namespace_segment.to_s.empty?
|
34
35
|
|
35
36
|
@client = client
|
@@ -38,7 +39,7 @@ module OandaAPI
|
|
38
39
|
extract_key_and_conditions conditions
|
39
40
|
end
|
40
41
|
|
41
|
-
# Returns a deep clone of
|
42
|
+
# Returns a deep clone of `self`.
|
42
43
|
# @return [NamespaceProxy]
|
43
44
|
def clone
|
44
45
|
ns = self.dup
|
@@ -86,20 +87,24 @@ module OandaAPI
|
|
86
87
|
end
|
87
88
|
|
88
89
|
# Executes an API request and returns a resource object, or returns a
|
89
|
-
# clone of
|
90
|
+
# clone of `self` for method chaining.
|
91
|
+
#
|
92
|
+
# @yield [OandaAPI::ResourceBase] if the method is `:stream`.
|
93
|
+
#
|
94
|
+
# @return [void] if the method is `:stream`.
|
90
95
|
#
|
91
96
|
# @return [OandaAPI::Client::NamespaceProxy] if the method is used
|
92
97
|
# for chaining.
|
93
98
|
#
|
94
99
|
# @return [OandaAPI::ResourceBase] if the method is one of the supported
|
95
|
-
# _terminating_ methods (
|
100
|
+
# _terminating_ methods (`:create`, `:close`, `:delete`, `:get`, `:update`).
|
96
101
|
#
|
97
|
-
# @return [OandaAPI::ResourceCollection] if the method is
|
102
|
+
# @return [OandaAPI::ResourceCollection] if the method is `:get` and the
|
98
103
|
# API returns a collection of resources.
|
99
|
-
def method_missing(sym, *args)
|
104
|
+
def method_missing(sym, *args, &block)
|
100
105
|
# Check for terminating method
|
101
|
-
if [:create, :close, :delete, :get, :update].include?(sym)
|
102
|
-
@client.execute_request sym, namespace, conditions
|
106
|
+
if [:create, :close, :delete, :get, :update, :stream].include?(sym)
|
107
|
+
@client.execute_request sym, namespace, conditions, &block
|
103
108
|
else
|
104
109
|
ns = self.clone
|
105
110
|
ns.namespace_segments << Utils.pluralize(sym)
|
@@ -8,8 +8,7 @@ module OandaAPI
|
|
8
8
|
# {http://developer.oanda.com/rest-live/authentication/ obtaining a personal access token from Oanda}.
|
9
9
|
#
|
10
10
|
# @example Example usage
|
11
|
-
#
|
12
|
-
# client = OandaAPI::Client::TokenClient.new :practice, token
|
11
|
+
# client = OandaAPI::Client::TokenClient.new :practice, ENV.fetch("OANDA_PRACTICE_TOKEN")
|
13
12
|
#
|
14
13
|
# # Get information for an account.
|
15
14
|
# # See http://developer.oanda.com/rest-live/accounts/
|
@@ -25,7 +24,7 @@ module OandaAPI
|
|
25
24
|
# @return [String] Oanda personal access token.
|
26
25
|
#
|
27
26
|
# @!attribute [rw] domain
|
28
|
-
# @return [Symbol] identifies the Oanda subdomain (
|
27
|
+
# @return [Symbol] identifies the Oanda subdomain (`:practice` or `:live`)
|
29
28
|
# accessed by the client.
|
30
29
|
#
|
31
30
|
# @!attribute [rw] default_params
|
@@ -15,7 +15,7 @@ module OandaAPI
|
|
15
15
|
#
|
16
16
|
#
|
17
17
|
# @!attribute [r] domain
|
18
|
-
# @return [Symbol] identifies the Oanda subdomain (
|
18
|
+
# @return [Symbol] identifies the Oanda subdomain (`:sandbox`) which the
|
19
19
|
# client accesses.
|
20
20
|
#
|
21
21
|
# @!attribute [r] username
|
@@ -11,7 +11,7 @@ module OandaAPI
|
|
11
11
|
USE_COMPRESSION = false
|
12
12
|
USE_REQUEST_THROTTLING = false
|
13
13
|
|
14
|
-
# The format in which dates will be returned by the API (
|
14
|
+
# The format in which dates will be returned by the API (`:rfc3339` or `:unix`).
|
15
15
|
# See the Oanda Development Guide for more details about {http://developer.oanda.com/rest-live/development-guide/#date_Time_Format DateTime formats}.
|
16
16
|
# @return [Symbol]
|
17
17
|
def datetime_format
|
@@ -27,7 +27,7 @@ module OandaAPI
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# The maximum number of requests per second allowed to be made through the
|
30
|
-
# API. Only enforced if {#use_request_throttling?} is
|
30
|
+
# API. Only enforced if {#use_request_throttling?} is `true`.
|
31
31
|
#
|
32
32
|
# @return [Numeric]
|
33
33
|
def max_requests_per_second
|
@@ -44,7 +44,7 @@ module OandaAPI
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# The minimum amount of time in seconds that must elapse between consecutive requests to the API.
|
47
|
-
# Determined by {#max_requests_per_second}. Only enforced if {#use_request_throttling?} is
|
47
|
+
# Determined by {#max_requests_per_second}. Only enforced if {#use_request_throttling?} is `true`.
|
48
48
|
# @return [Float]
|
49
49
|
def min_request_interval
|
50
50
|
@min_request_interval ||= (1.0 / max_requests_per_second)
|
data/lib/oanda_api/errors.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Heartbeat returned by the Streaming API.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/streaming/ Heartbeats}.
|
5
|
+
class Heartbeat < ResourceBase
|
6
|
+
attr_accessor :time
|
7
|
+
|
8
|
+
def time=(v)
|
9
|
+
@time = Time.parse v.to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module OandaAPI
|
2
|
+
require 'json'
|
2
3
|
# Base class for all Resources.
|
3
4
|
#
|
4
5
|
# @!attribute [rw] location
|
5
|
-
# @return [String] the
|
6
|
+
# @return [String] the `location` header if one is returned in an API
|
6
7
|
# response.
|
7
|
-
# @example Using the
|
8
|
+
# @example Using the `location` attribute
|
8
9
|
# client = OandaAPI::Client::TokenClient.new :practice, token
|
9
10
|
# all_transactions = client.account(123).alltransactions.get
|
10
11
|
# all_transactions.location # => https://fxtrade.oanda.com/transactionhistory/d3aed6823c.json.zip
|
@@ -15,10 +16,16 @@ module OandaAPI
|
|
15
16
|
# {http://developer.oanda.com/rest-live/development-guide/ Oanda Developer Guide}
|
16
17
|
# for documentation about resource attributes.
|
17
18
|
def initialize(attributes = {})
|
18
|
-
initialize_attributes Utils.rubyize_keys(attributes
|
19
|
+
initialize_attributes Utils.rubyize_keys(attributes)
|
19
20
|
@location = attributes.location if attributes.respond_to? :location
|
20
21
|
end
|
21
22
|
|
23
|
+
# Serializes an instance as JSON
|
24
|
+
# @return [String] a stringified JSON representation of an instance
|
25
|
+
def to_json
|
26
|
+
JSON.generate @_attributes.merge(custom_attributes)
|
27
|
+
end
|
28
|
+
|
22
29
|
private
|
23
30
|
|
24
31
|
# @private
|
@@ -27,9 +34,16 @@ module OandaAPI
|
|
27
34
|
# @param [Hash] attributes collection of resource attributes.
|
28
35
|
# @return [void]
|
29
36
|
def initialize_attributes(attributes)
|
37
|
+
@_attributes = attributes
|
30
38
|
attributes.each do |key, value|
|
31
39
|
send("#{key}=", value) if respond_to? key
|
32
40
|
end
|
33
41
|
end
|
42
|
+
|
43
|
+
# Provides additional attributes used in serialization.
|
44
|
+
# @return [Hash] returns a hash of customized attributes for serialization
|
45
|
+
def custom_attributes
|
46
|
+
{}.tap { |hash| hash[:location] = location if location }
|
47
|
+
end
|
34
48
|
end
|
35
49
|
end
|
@@ -32,7 +32,7 @@ module OandaAPI
|
|
32
32
|
@attributes = Utils.rubyize_keys attributes
|
33
33
|
@collection = @attributes.delete(resource_descriptor.collection_name) || []
|
34
34
|
@collection.map! { |resource| resource_descriptor.resource_klass.new resource }
|
35
|
-
@location
|
35
|
+
@location = attributes.location if attributes.respond_to? :location
|
36
36
|
end
|
37
37
|
|
38
38
|
# @yield [OandaAPI::ResourceBase]
|
@@ -47,21 +47,20 @@ module OandaAPI
|
|
47
47
|
|
48
48
|
# @private
|
49
49
|
# Responds to collection-scoped accessor methods that are specific to the
|
50
|
-
# type of resource collection. For example, a
|
51
|
-
# the collection-scoped methods
|
50
|
+
# type of resource collection. For example, a `Candle` collection includes
|
51
|
+
# the collection-scoped methods `#granularity` and `#instrument`.
|
52
52
|
def method_missing(sym, *args)
|
53
53
|
case
|
54
|
-
when @attributes.keys.include?(sym)
|
54
|
+
when @attributes.keys.include?(sym)
|
55
55
|
@attributes[sym]
|
56
|
-
when
|
57
|
-
@collection.
|
58
|
-
@collection.send sym
|
56
|
+
when @collection.respond_to?(sym)
|
57
|
+
@collection.send sym
|
59
58
|
else
|
60
59
|
super
|
61
60
|
end
|
62
61
|
end
|
63
62
|
|
64
|
-
# Returns
|
63
|
+
# Returns `true` for concrete, delegated and dynamic methods.
|
65
64
|
# @return [Boolean]
|
66
65
|
def respond_to?(sym)
|
67
66
|
case
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Streaming
|
3
|
+
# Resource URI templates
|
4
|
+
BASE_URI = {
|
5
|
+
live: "https://stream-fxtrade.oanda.com/[API_VERSION]",
|
6
|
+
practice: "https://stream-fxpractice.oanda.com/[API_VERSION]"
|
7
|
+
}
|
8
|
+
|
9
|
+
# @example Example Usage
|
10
|
+
# client = OandaAPI::Streaming::Client.new :practice, ENV.fetch("ONADA_PRACTICE_TOKEN")
|
11
|
+
#
|
12
|
+
# # IMPORTANT
|
13
|
+
# # This code will block indefinitely because streaming executes as an infinite loop.
|
14
|
+
# prices = client.prices(account_id: 1234, instruments: %w[AUD_CAD AUD_CHF])
|
15
|
+
# prices.stream do |price|
|
16
|
+
#
|
17
|
+
# # The code in this block should handle the price as efficently
|
18
|
+
# # as possible, otherwise the connection could timeout.
|
19
|
+
# # For example, you could publish the tick on a queue to be handled
|
20
|
+
# # by some other thread or process.
|
21
|
+
# price # => OandaAPI::Resource::Price
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# client.events.stream do |transaction|
|
25
|
+
# transaction # => OandaAPI::Resource::Transaction
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # -- Stopping the stream --
|
29
|
+
# # You may add a second argument to the block to yield the client itself.
|
30
|
+
# # You can use the client's `#stop!` method to terminate streaming.
|
31
|
+
# @prices = []
|
32
|
+
# prices = client.prices(account_id: 1234, instruments: %w[AUD_CAD AUD_CHF])
|
33
|
+
# prices.stream do |price, client|
|
34
|
+
# @prices << price
|
35
|
+
# client.stop! if @prices.size == 10
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @!attribute [r] auth_token
|
39
|
+
# @return [String] Oanda personal access token.
|
40
|
+
#
|
41
|
+
# @!attribute [r] streaming_request
|
42
|
+
# @return [OandaAPI::Streaming::Request]
|
43
|
+
#
|
44
|
+
# @!attribute [rw] domain
|
45
|
+
# @return [Symbol] identifies the Oanda subdomain (`:practice` or `:live`)
|
46
|
+
# accessed by the client.
|
47
|
+
#
|
48
|
+
# @!attribute [rw] headers
|
49
|
+
# @return [Hash] parameters that are included with every API request
|
50
|
+
# as HTTP headers.
|
51
|
+
class Client
|
52
|
+
attr_reader :auth_token, :streaming_request
|
53
|
+
attr_accessor :domain, :headers
|
54
|
+
|
55
|
+
# @param [Symbol] domain see {#domain}
|
56
|
+
# @param [String] auth_token see {#auth_token}
|
57
|
+
def initialize(domain, auth_token)
|
58
|
+
super()
|
59
|
+
@auth_token = auth_token
|
60
|
+
self.domain = domain
|
61
|
+
@headers = auth
|
62
|
+
@streaming_request = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns an absolute URI for a resource request.
|
66
|
+
#
|
67
|
+
# @param [String] path the path portion of the URI.
|
68
|
+
#
|
69
|
+
# @return [String] a URI.
|
70
|
+
def api_uri(path)
|
71
|
+
uri = "#{BASE_URI[domain]}#{path}"
|
72
|
+
uri.sub "[API_VERSION]", OandaAPI.configuration.rest_api_version
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parameters used for authentication.
|
76
|
+
# @return [Hash]
|
77
|
+
def auth
|
78
|
+
{ "Authorization" => "Bearer #{auth_token}" }
|
79
|
+
end
|
80
|
+
|
81
|
+
# @private
|
82
|
+
# Sets the domain the client can access.
|
83
|
+
# @return [void]
|
84
|
+
def domain=(value)
|
85
|
+
fail ArgumentError, "Invalid domain" unless OandaAPI::DOMAINS.include? value
|
86
|
+
@domain = value
|
87
|
+
end
|
88
|
+
|
89
|
+
# Determines whether emitted resources should include heartbeats.
|
90
|
+
# @param [boolean] value
|
91
|
+
def emit_heartbeats=(value)
|
92
|
+
@emit_heartbeats = value
|
93
|
+
streaming_request.emit_heartbeats = value if streaming_request
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns `true` if emitted resources include heartbeats. Defaults to false.
|
97
|
+
# @return [boolean]
|
98
|
+
def emit_heartbeats?
|
99
|
+
!!@emit_heartbeats
|
100
|
+
end
|
101
|
+
|
102
|
+
# Signals the streaming request to disconnect and terminates streaming.
|
103
|
+
# @return [void]
|
104
|
+
def stop!
|
105
|
+
streaming_request.stop! if running?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns `true` if the client is currently streaming.
|
109
|
+
# @return [boolean]
|
110
|
+
def running?
|
111
|
+
!!(streaming_request && streaming_request.running?)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
# Executes a streaming http request.
|
116
|
+
#
|
117
|
+
# @param [Symbol] _method Ignored.
|
118
|
+
#
|
119
|
+
# @param [String] path the path of an Oanda resource request.
|
120
|
+
#
|
121
|
+
# @param [Hash] conditions optional parameters that are converted into query parameters.
|
122
|
+
#
|
123
|
+
# @yield [OandaAPI:ResourceBase, OandaAPI::Streaming::Client] See {OandaAPI::Streaming::Request.stream}
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
#
|
127
|
+
# @raise [OandaAPI::RequestError] if the API return code is not 2xx.
|
128
|
+
# @raise [OandaAPI::StreamingDisconnect] if the API disconnects.
|
129
|
+
def execute_request(_method, path, conditions = {}, &block)
|
130
|
+
Http::Exceptions.wrap_and_check do
|
131
|
+
@streaming_request = OandaAPI::Streaming::Request.new client: self,
|
132
|
+
uri: api_uri(path),
|
133
|
+
query: Utils.stringify_keys(conditions),
|
134
|
+
headers: OandaAPI.configuration.headers.merge(headers)
|
135
|
+
@streaming_request.stream(&block)
|
136
|
+
return nil
|
137
|
+
end
|
138
|
+
|
139
|
+
rescue Http::Exceptions::HttpException => e
|
140
|
+
raise OandaAPI::RequestError, e.message
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# @private
|
146
|
+
# Enables method-chaining.
|
147
|
+
# @return [OandaAPI::Client::NamespaceProxy]
|
148
|
+
def method_missing(sym, *args)
|
149
|
+
OandaAPI::Client::NamespaceProxy.new self, sym, args.first
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|