oanda_api 0.8.1 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +38 -0
- data/.rspec_non_jruby +2 -0
- data/.yardopts +6 -0
- data/Gemfile +13 -0
- data/Guardfile +7 -0
- data/LICENSE +22 -0
- data/README.md +218 -0
- data/Rakefile +23 -0
- data/lib/oanda_api.rb +25 -0
- data/lib/oanda_api/client/client.rb +175 -0
- data/lib/oanda_api/client/namespace_proxy.rb +112 -0
- data/lib/oanda_api/client/resource_descriptor.rb +52 -0
- data/lib/oanda_api/client/token_client.rb +69 -0
- data/lib/oanda_api/client/username_client.rb +53 -0
- data/lib/oanda_api/configuration.rb +167 -0
- data/lib/oanda_api/errors.rb +4 -0
- data/lib/oanda_api/resource/account.rb +37 -0
- data/lib/oanda_api/resource/candle.rb +29 -0
- data/lib/oanda_api/resource/instrument.rb +21 -0
- data/lib/oanda_api/resource/order.rb +74 -0
- data/lib/oanda_api/resource/position.rb +18 -0
- data/lib/oanda_api/resource/price.rb +16 -0
- data/lib/oanda_api/resource/trade.rb +23 -0
- data/lib/oanda_api/resource/transaction.rb +67 -0
- data/lib/oanda_api/resource_base.rb +35 -0
- data/lib/oanda_api/resource_collection.rb +77 -0
- data/lib/oanda_api/utils/utils.rb +101 -0
- data/lib/oanda_api/version.rb +3 -0
- data/oanda_api.gemspec +32 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_id_close.yml +264 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_id_get.yml +114 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_options_create.yml +74 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_options_update.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_orders_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_orders_options_get.yml +123 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_get.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_close.yml +214 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_get.yml +110 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_id_close.yml +252 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_id_get.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_options_modify.yml +110 -0
- data/spec/fixtures/vcr_cassettes/account_id_trades_filter_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_trades_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_transaction_id_get.yml +283 -0
- data/spec/fixtures/vcr_cassettes/account_id_transactions_options_get.yml +205 -0
- data/spec/fixtures/vcr_cassettes/accounts_create.yml +75 -0
- data/spec/fixtures/vcr_cassettes/accounts_get.yml +111 -0
- data/spec/fixtures/vcr_cassettes/accounts_id_get.yml +187 -0
- data/spec/fixtures/vcr_cassettes/candles_options_get.yml +79 -0
- data/spec/fixtures/vcr_cassettes/instruments_get.yml +501 -0
- data/spec/fixtures/vcr_cassettes/instruments_options_get.yml +81 -0
- data/spec/fixtures/vcr_cassettes/prices_options_get.yml +81 -0
- data/spec/fixtures/vcr_cassettes/sandbox_client.yml +116 -0
- data/spec/fixtures/vcr_cassettes/sandbox_client_account.yml +111 -0
- data/spec/fixtures/vcr_cassettes/sandbox_instrument_EUR_USD.yml +77 -0
- data/spec/oanda_api/client/client_spec.rb +107 -0
- data/spec/oanda_api/client/namespace_proxy_spec.rb +16 -0
- data/spec/oanda_api/client/resource_descriptor_spec.rb +39 -0
- data/spec/oanda_api/client/token_client_spec.rb +60 -0
- data/spec/oanda_api/client/username_client_spec.rb +31 -0
- data/spec/oanda_api/configuration_spec.rb +138 -0
- data/spec/oanda_api/examples/accounts_spec.rb +28 -0
- data/spec/oanda_api/examples/orders_spec.rb +68 -0
- data/spec/oanda_api/examples/positions_spec.rb +38 -0
- data/spec/oanda_api/examples/rates_spec.rb +46 -0
- data/spec/oanda_api/examples/trades_spec.rb +58 -0
- data/spec/oanda_api/examples/transactions_spec.rb +24 -0
- data/spec/oanda_api/resource_collection_spec.rb +109 -0
- data/spec/oanda_api/utils/utils_spec.rb +109 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/client_helper.rb +60 -0
- data/spec/support/vcr.rb +7 -0
- metadata +124 -9
data/.gitignore
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
.*.sw[a-z]
|
15
|
+
|
16
|
+
## RubyMine and related
|
17
|
+
.idea
|
18
|
+
|
19
|
+
## PROJECT::GENERAL
|
20
|
+
coverage
|
21
|
+
rdoc
|
22
|
+
doc
|
23
|
+
pkg
|
24
|
+
|
25
|
+
## PROJECT::SPECIFIC
|
26
|
+
*.gem
|
27
|
+
.bundle
|
28
|
+
Gemfile.lock
|
29
|
+
pkg/*
|
30
|
+
tmp/*
|
31
|
+
spec/reports
|
32
|
+
*.rbc
|
33
|
+
*.rbx
|
34
|
+
.ruby-gemset
|
35
|
+
.ruby-version
|
36
|
+
.rubocop*
|
37
|
+
.rvmrc
|
38
|
+
.yardoc
|
data/.rspec_non_jruby
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dean Missikowski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
# OandaAPI
|
2
|
+
Access Oanda FX accounts, get market data, trade, build trading strategies using Ruby.
|
3
|
+
## Synopsis
|
4
|
+
OandaAPI is a simple Ruby wrapper for the [Oanda REST API](http://developer.oanda.com/rest-live/introduction/).
|
5
|
+
|
6
|
+
This style of API wrapper is typically called a [fluent](http://en.wikipedia.org/wiki/Fluent_interface) interface. The wrapper translates native Ruby objects to and from JSON representations that the API understands.
|
7
|
+
|
8
|
+
For example,
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
client = OandaAPI::Client::TokenClient.new(:practice, "practice_account_token")
|
12
|
+
account = client.account(12345).get
|
13
|
+
```
|
14
|
+
|
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
|
+
|
17
|
+
|
18
|
+
## Features
|
19
|
+
|
20
|
+
A simple native Ruby reflection of the underlying REST API using Ruby idioms where appropriate.
|
21
|
+
|
22
|
+
Enable [Oanda's Best Practices](http://developer.oanda.com/rest-live/best-practices/) recommendations for accessing the API including:
|
23
|
+
|
24
|
+
- Secure connections over SSL with always verified certificates
|
25
|
+
- Persistent connections
|
26
|
+
- Response compression
|
27
|
+
- Request rate limiting
|
28
|
+
|
29
|
+
|
30
|
+
Some Examples
|
31
|
+
-------------
|
32
|
+
|
33
|
+
### Getting price quotes
|
34
|
+
```ruby
|
35
|
+
require 'oanda_api'
|
36
|
+
|
37
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV["practice_account_token"])
|
38
|
+
|
39
|
+
prices = client.prices(instruments: %w(EUR_USD USD_JPY)).get
|
40
|
+
|
41
|
+
prices.each do |p|
|
42
|
+
p.instrument # => "EUR_USD"
|
43
|
+
p.ask # => 1.13781
|
44
|
+
p.bid # => 1.13759
|
45
|
+
p.time # => 2015-01-27 21:01:13 UTC
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Getting candle information
|
50
|
+
```ruby
|
51
|
+
require 'oanda_api'
|
52
|
+
|
53
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV["practice_account_token"])
|
54
|
+
|
55
|
+
candles = client.candles( instrument: "EUR_USD",
|
56
|
+
granularity: "M1",
|
57
|
+
candle_format: "midpoint",
|
58
|
+
start: (Time.now - 3600).utc.to_datetime.rfc3339)
|
59
|
+
.get
|
60
|
+
|
61
|
+
candles.size # => 57
|
62
|
+
candles.granularity # => "M1"
|
63
|
+
candles.instrument # => "EUR_USD"
|
64
|
+
|
65
|
+
candles.each do |c|
|
66
|
+
c.complete? # => true
|
67
|
+
c.open_mid # => 1.137155
|
68
|
+
c.close_mid # => 1.137185
|
69
|
+
c.high_mid # => 1.13729
|
70
|
+
c.low_mid # => 1.137155
|
71
|
+
c.time # => 2015-01-27 20:26:00 UTC
|
72
|
+
c.volume # => 25
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
|
77
|
+
### Creating a market order
|
78
|
+
```ruby
|
79
|
+
require 'oanda_api'
|
80
|
+
|
81
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV["practice_account_token"])
|
82
|
+
|
83
|
+
order = client.account(12345)
|
84
|
+
.order(instrument: "USD_JPY",
|
85
|
+
type: "market",
|
86
|
+
side: "buy",
|
87
|
+
units: 10_000)
|
88
|
+
.create
|
89
|
+
|
90
|
+
order.price # => 114.887
|
91
|
+
order.time # => 2014-12-20 21:25:57 UTC
|
92
|
+
order.trade_opened.id # => 175491416
|
93
|
+
```
|
94
|
+
|
95
|
+
### Closing a position
|
96
|
+
```ruby
|
97
|
+
require 'oanda_api'
|
98
|
+
|
99
|
+
client = OandaAPI::Client::TokenClient.new(:practice, ENV["practice_account_token"])
|
100
|
+
|
101
|
+
account = client.account(12345) # => OandaAPI::NamespaceProxy
|
102
|
+
position = account.position("USD_JPY").get # => OandaAPI::Resource::Position
|
103
|
+
|
104
|
+
position.ave_price # => 114.898
|
105
|
+
position.side # => "buy"
|
106
|
+
position.units # => 30_000
|
107
|
+
|
108
|
+
# Close out the 30_000 long position
|
109
|
+
closed_position = account.position("USD_JPY").close # => OandaAPI::Resource::Position
|
110
|
+
|
111
|
+
closed_position.price # => 114.858
|
112
|
+
closed_position.total_units # => 30_000
|
113
|
+
closed_position.ids # => [175490804, 175491421, 175491416]
|
114
|
+
|
115
|
+
transaction = account.transaction(175490804).get # => OandaAPI::Resource::Transaction
|
116
|
+
transaction.instrument # => "USD_JPY"
|
117
|
+
transaction.time # => 2014-12-19 03:29:48 UTC
|
118
|
+
transaction.type # => "MARKET_ORDER_CREATE"
|
119
|
+
```
|
120
|
+
|
121
|
+
Documentation
|
122
|
+
-------------
|
123
|
+
|
124
|
+
Please see the [Oanda Developer Wiki](http://developer.oanda.com/rest-live/introduction/)
|
125
|
+
for detailed documentation and API usage notes.
|
126
|
+
|
127
|
+
|
128
|
+
| Ruby | Oanda REST API |
|
129
|
+
|:---------------------------|:---------------------|
|
130
|
+
| client.accounts.get | GET /v1/accounts |
|
131
|
+
| client.account(123).get | GET /v1/accounts/123 |
|
132
|
+
| client.account.create | POST /v1/accounts |
|
133
|
+
| client.instruments.get | GET /v1/instruments |
|
134
|
+
| 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
|
137
|
+
| client.account(123).order( *options* ).create | POST /v1/accounts/123/orders |
|
138
|
+
| client.account(123).order(id:123, *options* ).update | PATCH /v1/accounts/123/orders/123 |
|
139
|
+
| client.account(123).order(123).close | DELETE /v1/accounts/123/orders/123 |
|
140
|
+
| client.account(123).positions.get | GET /v1/accounts/123/positions |
|
141
|
+
| client.account(123).position("EUR_USD").get | GET /v1/accounts/123/positions/EUR_USD |
|
142
|
+
| client.account(123).position("EUR_USD").close | DELETE /v1/accounts/123/positions/EUR_USD |
|
143
|
+
| client.account(123).trades.get | GET /v1/accounts/123/trades |
|
144
|
+
| client.account(123).trade(123).get | GET /v1/accounts/123/trades/123 |
|
145
|
+
| client.account(123).trade(id:123, *options* ).update | PATCH /v1/accounts/123/trades/123 |
|
146
|
+
| client.account(123).trade(123).close | DELETE /v1/accounts/123/trades/123 |
|
147
|
+
| client.account(123).transactions.get | GET /v1/accounts/123/transactions |
|
148
|
+
| client.account(123).transaction(123).get | GET /v1/accounts/123/transactions/123 |
|
149
|
+
| client.account(123).alltransactions.get | GET /v1/accounts/123/alltransactions |
|
150
|
+
|
151
|
+
|
152
|
+
Installation
|
153
|
+
------------
|
154
|
+
|
155
|
+
Add this line to your application's Gemfile:
|
156
|
+
|
157
|
+
gem 'oanda_api'
|
158
|
+
|
159
|
+
And then execute:
|
160
|
+
|
161
|
+
$ bundle
|
162
|
+
|
163
|
+
Or install it yourself as:
|
164
|
+
|
165
|
+
$ gem install oanda_api
|
166
|
+
|
167
|
+
Inside of your Ruby program, require oanda_api with:
|
168
|
+
|
169
|
+
require 'oanda_api'
|
170
|
+
|
171
|
+
Configuration
|
172
|
+
-------------
|
173
|
+
|
174
|
+
Add a configuration block to your application to specify client settings such
|
175
|
+
as whether or not to use compression, request rate limiting, or other options.
|
176
|
+
See the RubyDoc [documentation](http://www.rubydoc.info/gems/oanda_api) for OandaAPI for more configuration settings.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
OandaAPI.configure do |config|
|
180
|
+
config.use_compression = true
|
181
|
+
config.use_request_rate_throttling = true
|
182
|
+
config.max_requests_per_second = 10
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
Supported Platforms
|
187
|
+
-------------------
|
188
|
+
|
189
|
+
OandaAPI works with Ruby 2.0 and higher.
|
190
|
+
|
191
|
+
Tested on:
|
192
|
+
|
193
|
+
* MRI 2.1, 2.2
|
194
|
+
* JRuby 1.7, 9.0.0.0.pre
|
195
|
+
* Rubinius 2.4, 2.5
|
196
|
+
|
197
|
+
Contributing
|
198
|
+
------------
|
199
|
+
|
200
|
+
If you'd like to contribute code or modify this gem, you can run the test suite with:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
gem install oanda_api --dev
|
204
|
+
bundle exec rspec # or just 'rspec' may work
|
205
|
+
```
|
206
|
+
When you're ready to code:
|
207
|
+
|
208
|
+
1. Fork this repository on github.
|
209
|
+
2. Make your changes.
|
210
|
+
3. Add tests where applicable and run the existing tests with `rspec` to make sure they all pass.
|
211
|
+
4. Add new documentation where appropriate using [YARD](http://yardoc.org/) formatting.
|
212
|
+
5. Create a new pull request and submit it to me.
|
213
|
+
|
214
|
+
License
|
215
|
+
-------
|
216
|
+
|
217
|
+
Copyright (c) 2014 Dean Missikowski. Distributed under the MIT License. See
|
218
|
+
[LICENSE](LICENSE) for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
7
|
+
task.rspec_opts = ["--color", "--format", "doc"]
|
8
|
+
end
|
9
|
+
task :default => :spec
|
10
|
+
task :test => :spec
|
11
|
+
rescue LoadError
|
12
|
+
# no rspec available
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'yard'
|
17
|
+
|
18
|
+
YARD::Rake::YardocTask.new do |task|
|
19
|
+
task.files = ["lib/**/*.rb"]
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
# no yard available
|
23
|
+
end
|
data/lib/oanda_api.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# The module that contains everything OandaAPI-related:
|
2
|
+
require 'httparty'
|
3
|
+
require 'persistent_httparty'
|
4
|
+
require 'http/exceptions'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
require_relative 'oanda_api/configuration'
|
8
|
+
require_relative 'oanda_api/client/client'
|
9
|
+
require_relative 'oanda_api/client/namespace_proxy'
|
10
|
+
require_relative 'oanda_api/client/resource_descriptor'
|
11
|
+
require_relative 'oanda_api/client/token_client'
|
12
|
+
require_relative 'oanda_api/client/username_client'
|
13
|
+
require_relative 'oanda_api/errors'
|
14
|
+
require_relative 'oanda_api/resource_base'
|
15
|
+
require_relative 'oanda_api/resource_collection'
|
16
|
+
require_relative 'oanda_api/resource/account'
|
17
|
+
require_relative 'oanda_api/resource/instrument'
|
18
|
+
require_relative 'oanda_api/resource/candle'
|
19
|
+
require_relative 'oanda_api/resource/order'
|
20
|
+
require_relative 'oanda_api/resource/position'
|
21
|
+
require_relative 'oanda_api/resource/price'
|
22
|
+
require_relative 'oanda_api/resource/trade'
|
23
|
+
require_relative 'oanda_api/resource/transaction'
|
24
|
+
require_relative 'oanda_api/utils/utils'
|
25
|
+
require_relative 'oanda_api/version'
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'http/exceptions'
|
3
|
+
|
4
|
+
module OandaAPI
|
5
|
+
# List of valid subdomains clients can access.
|
6
|
+
DOMAINS = [:live, :practice, :sandbox]
|
7
|
+
|
8
|
+
# Provides everything needed for accessing the API.
|
9
|
+
#
|
10
|
+
# - Uses persistant http connections.
|
11
|
+
# - Uses +OpenSSL::SSL::VERIFY_PEER+ to always validate SSL certificates.
|
12
|
+
# - Uses compression if enabled (see {Configuration#use_compression}).
|
13
|
+
# - Uses request rate limiting if enabled (see {Configuration#use_request_throttling}).
|
14
|
+
module Client
|
15
|
+
include HTTParty
|
16
|
+
persistent_connection_adapter idle_timeout: 10,
|
17
|
+
keep_alive: 30,
|
18
|
+
pool_size: 2
|
19
|
+
|
20
|
+
# Resource URI templates
|
21
|
+
BASE_URI = {
|
22
|
+
live: "https://api-fxtrade.oanda.com/[API_VERSION]",
|
23
|
+
practice: "https://api-fxpractice.oanda.com/[API_VERSION]",
|
24
|
+
sandbox: "http://api-sandbox.oanda.com/[API_VERSION]"
|
25
|
+
}
|
26
|
+
|
27
|
+
# @private
|
28
|
+
# Camelizes keys and transforms array values into comma-delimited strings.
|
29
|
+
#
|
30
|
+
# @return [String] a url encoded query string.
|
31
|
+
query_string_normalizer proc { |hash|
|
32
|
+
Array(hash).sort_by { |key, _value| key.to_s }.map do |key, value|
|
33
|
+
if value.nil?
|
34
|
+
Utils.camelize(key.to_s)
|
35
|
+
elsif value.respond_to?(:to_ary)
|
36
|
+
serialized = URI.encode value.join(","),
|
37
|
+
Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
38
|
+
"#{Utils.camelize(key)}=#{serialized}"
|
39
|
+
else
|
40
|
+
HashConversions.to_params Utils.camelize(key) => value
|
41
|
+
end
|
42
|
+
end.flatten.join("&")
|
43
|
+
}
|
44
|
+
|
45
|
+
# Returns an absolute URI for a resource request.
|
46
|
+
#
|
47
|
+
# @param [String] path the path portion of the URI.
|
48
|
+
#
|
49
|
+
# @return [String] a URI.
|
50
|
+
def api_uri(path)
|
51
|
+
uri = "#{BASE_URI[domain]}#{path}"
|
52
|
+
uri.sub "[API_VERSION]", OandaAPI.configuration.rest_api_version
|
53
|
+
end
|
54
|
+
|
55
|
+
# @private
|
56
|
+
# Executes an http request.
|
57
|
+
#
|
58
|
+
# @param [Symbol] method a request action. See {Client.map_method_to_http_verb}.
|
59
|
+
#
|
60
|
+
# @param [String] path the path of an Oanda resource request.
|
61
|
+
#
|
62
|
+
# @param [Hash] conditions optional parameters that are converted into
|
63
|
+
# either a query string or url form encoded parameters.
|
64
|
+
#
|
65
|
+
# @return [OandaAPI::ResourceBase] if the API request returns a singular
|
66
|
+
# resource. See {OandaAPI::Resource} for a list of resource types that
|
67
|
+
# can be returned.
|
68
|
+
#
|
69
|
+
# @return [OandaAPI::ResourceCollection] if the API request returns a
|
70
|
+
# collection of resources.
|
71
|
+
#
|
72
|
+
# @raise [OandaAPI::RequestError] if the API return code is not 2xx.
|
73
|
+
def execute_request(method, path, conditions = {})
|
74
|
+
response = Http::Exceptions.wrap_and_check do
|
75
|
+
method = Client.map_method_to_http_verb(method)
|
76
|
+
params_key = [:post, :patch, :put].include?(method) ? :body : :query
|
77
|
+
Client.throttle_request_rate
|
78
|
+
Client.send method,
|
79
|
+
api_uri(path),
|
80
|
+
params_key => Utils.stringify_keys(conditions.merge(default_params)),
|
81
|
+
:headers => OandaAPI.configuration.headers.merge(headers),
|
82
|
+
:open_timeout => OandaAPI.configuration.open_timeout,
|
83
|
+
:read_timeout => OandaAPI.configuration.read_timeout
|
84
|
+
end
|
85
|
+
|
86
|
+
handle_response response, ResourceDescriptor.new(path, method)
|
87
|
+
rescue Http::Exceptions::HttpException => e
|
88
|
+
raise OandaAPI::RequestError, e.message
|
89
|
+
end
|
90
|
+
|
91
|
+
# @private
|
92
|
+
# Maps An API _action_ to a corresponding http verb.
|
93
|
+
#
|
94
|
+
# @param [Symbol] method an API action. Supported actions are:
|
95
|
+
# +:create+, +:close+, +:delete+, +:get+, +:update+.
|
96
|
+
#
|
97
|
+
# @return [Symbol] an http verb.
|
98
|
+
def self.map_method_to_http_verb(method)
|
99
|
+
case method
|
100
|
+
when :create
|
101
|
+
:post
|
102
|
+
when :close
|
103
|
+
:delete
|
104
|
+
when :update
|
105
|
+
:patch
|
106
|
+
else
|
107
|
+
method
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# @private
|
112
|
+
# Limits the execution rate of consecutive requests. Specified by
|
113
|
+
# {OandaAPI::Configuration#max_requests_per_second}. Only enforced
|
114
|
+
# if {OandaAPI::Configuration#use_request_throttling?} is enabled.
|
115
|
+
#
|
116
|
+
# @return [void]
|
117
|
+
def self.throttle_request_rate
|
118
|
+
now = Time.now
|
119
|
+
Thread.current[:oanda_api_last_request_at] ||= now
|
120
|
+
delta = now - Thread.current[:oanda_api_last_request_at]
|
121
|
+
_throttle(now) if delta < OandaAPI.configuration.min_request_interval &&
|
122
|
+
OandaAPI.configuration.use_request_throttling?
|
123
|
+
Thread.current[:oanda_api_last_request_at] = Time.now
|
124
|
+
end
|
125
|
+
|
126
|
+
# @private
|
127
|
+
# The local time of the most recently throttled request.
|
128
|
+
#
|
129
|
+
# @return [Time] if any request has been throttled, the most recent time when
|
130
|
+
# one was temporarily suspended.
|
131
|
+
#
|
132
|
+
# @return [nil] if a request has never been throttled.
|
133
|
+
def self.last_throttled_at
|
134
|
+
Thread.current[:oanda_api_throttled_at]
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# @private
|
140
|
+
# Sleeps for the minimal amount of time required to honour the
|
141
|
+
# {OandaAPI::Configuration#max_requests_per_second} limit.
|
142
|
+
#
|
143
|
+
# @param [Time] time The time that the throttle was requested.
|
144
|
+
#
|
145
|
+
# @return [void]
|
146
|
+
def self._throttle(time)
|
147
|
+
Thread.current[:oanda_api_throttled_at] = time
|
148
|
+
sleep OandaAPI.configuration.min_request_interval
|
149
|
+
end
|
150
|
+
|
151
|
+
# @private
|
152
|
+
# Formats the response from the Oanda API into a resource object.
|
153
|
+
#
|
154
|
+
# @param [#each_pair] response a hash-like object returned by
|
155
|
+
# the internal http client.
|
156
|
+
#
|
157
|
+
# @param [OandaAPI::Client::ResourceDescriptor] resource_descriptor metadata
|
158
|
+
# describing the requested resource.
|
159
|
+
# @return [OandaAPI::ResourceBase, OandaAPI::ResourceCollection] see {#execute_request}
|
160
|
+
def handle_response(response, resource_descriptor)
|
161
|
+
if resource_descriptor.is_collection?
|
162
|
+
ResourceCollection.new response, resource_descriptor
|
163
|
+
else
|
164
|
+
resource_descriptor.resource_klass.new response
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# @private
|
169
|
+
# Enables method-chaining.
|
170
|
+
# @return [Namespace]
|
171
|
+
def method_missing(sym, *args)
|
172
|
+
NamespaceProxy.new self, sym, args.first
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|