oanda_api 0.8.1 → 0.8.3
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.
- 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
|