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.
Files changed (73) hide show
  1. data/.gitignore +38 -0
  2. data/.rspec_non_jruby +2 -0
  3. data/.yardopts +6 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +22 -0
  7. data/README.md +218 -0
  8. data/Rakefile +23 -0
  9. data/lib/oanda_api.rb +25 -0
  10. data/lib/oanda_api/client/client.rb +175 -0
  11. data/lib/oanda_api/client/namespace_proxy.rb +112 -0
  12. data/lib/oanda_api/client/resource_descriptor.rb +52 -0
  13. data/lib/oanda_api/client/token_client.rb +69 -0
  14. data/lib/oanda_api/client/username_client.rb +53 -0
  15. data/lib/oanda_api/configuration.rb +167 -0
  16. data/lib/oanda_api/errors.rb +4 -0
  17. data/lib/oanda_api/resource/account.rb +37 -0
  18. data/lib/oanda_api/resource/candle.rb +29 -0
  19. data/lib/oanda_api/resource/instrument.rb +21 -0
  20. data/lib/oanda_api/resource/order.rb +74 -0
  21. data/lib/oanda_api/resource/position.rb +18 -0
  22. data/lib/oanda_api/resource/price.rb +16 -0
  23. data/lib/oanda_api/resource/trade.rb +23 -0
  24. data/lib/oanda_api/resource/transaction.rb +67 -0
  25. data/lib/oanda_api/resource_base.rb +35 -0
  26. data/lib/oanda_api/resource_collection.rb +77 -0
  27. data/lib/oanda_api/utils/utils.rb +101 -0
  28. data/lib/oanda_api/version.rb +3 -0
  29. data/oanda_api.gemspec +32 -0
  30. data/spec/fixtures/vcr_cassettes/account_id_order_id_close.yml +264 -0
  31. data/spec/fixtures/vcr_cassettes/account_id_order_id_get.yml +114 -0
  32. data/spec/fixtures/vcr_cassettes/account_id_order_options_create.yml +74 -0
  33. data/spec/fixtures/vcr_cassettes/account_id_order_options_update.yml +112 -0
  34. data/spec/fixtures/vcr_cassettes/account_id_orders_get.yml +118 -0
  35. data/spec/fixtures/vcr_cassettes/account_id_orders_options_get.yml +123 -0
  36. data/spec/fixtures/vcr_cassettes/account_id_positions_get.yml +112 -0
  37. data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_close.yml +214 -0
  38. data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_get.yml +110 -0
  39. data/spec/fixtures/vcr_cassettes/account_id_trade_id_close.yml +252 -0
  40. data/spec/fixtures/vcr_cassettes/account_id_trade_id_get.yml +112 -0
  41. data/spec/fixtures/vcr_cassettes/account_id_trade_options_modify.yml +110 -0
  42. data/spec/fixtures/vcr_cassettes/account_id_trades_filter_get.yml +118 -0
  43. data/spec/fixtures/vcr_cassettes/account_id_trades_get.yml +118 -0
  44. data/spec/fixtures/vcr_cassettes/account_id_transaction_id_get.yml +283 -0
  45. data/spec/fixtures/vcr_cassettes/account_id_transactions_options_get.yml +205 -0
  46. data/spec/fixtures/vcr_cassettes/accounts_create.yml +75 -0
  47. data/spec/fixtures/vcr_cassettes/accounts_get.yml +111 -0
  48. data/spec/fixtures/vcr_cassettes/accounts_id_get.yml +187 -0
  49. data/spec/fixtures/vcr_cassettes/candles_options_get.yml +79 -0
  50. data/spec/fixtures/vcr_cassettes/instruments_get.yml +501 -0
  51. data/spec/fixtures/vcr_cassettes/instruments_options_get.yml +81 -0
  52. data/spec/fixtures/vcr_cassettes/prices_options_get.yml +81 -0
  53. data/spec/fixtures/vcr_cassettes/sandbox_client.yml +116 -0
  54. data/spec/fixtures/vcr_cassettes/sandbox_client_account.yml +111 -0
  55. data/spec/fixtures/vcr_cassettes/sandbox_instrument_EUR_USD.yml +77 -0
  56. data/spec/oanda_api/client/client_spec.rb +107 -0
  57. data/spec/oanda_api/client/namespace_proxy_spec.rb +16 -0
  58. data/spec/oanda_api/client/resource_descriptor_spec.rb +39 -0
  59. data/spec/oanda_api/client/token_client_spec.rb +60 -0
  60. data/spec/oanda_api/client/username_client_spec.rb +31 -0
  61. data/spec/oanda_api/configuration_spec.rb +138 -0
  62. data/spec/oanda_api/examples/accounts_spec.rb +28 -0
  63. data/spec/oanda_api/examples/orders_spec.rb +68 -0
  64. data/spec/oanda_api/examples/positions_spec.rb +38 -0
  65. data/spec/oanda_api/examples/rates_spec.rb +46 -0
  66. data/spec/oanda_api/examples/trades_spec.rb +58 -0
  67. data/spec/oanda_api/examples/transactions_spec.rb +24 -0
  68. data/spec/oanda_api/resource_collection_spec.rb +109 -0
  69. data/spec/oanda_api/utils/utils_spec.rb +109 -0
  70. data/spec/spec_helper.rb +10 -0
  71. data/spec/support/client_helper.rb +60 -0
  72. data/spec/support/vcr.rb +7 -0
  73. 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
@@ -0,0 +1,2 @@
1
+ --format Fuubar
2
+ --color
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --readme README.md
2
+ --title 'OandaAPI Documentation'
3
+ --charset utf-8
4
+ --markup markdown
5
+ 'lib/**/*.rb' - '*.md'
6
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "pry"
7
+ gem "guard"
8
+ gem "guard-rspec"
9
+ gem "fuubar"
10
+ gem "redcarpet"
11
+ gem "github-markup"
12
+ gem "rubocop", require: false
13
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch("spec/spec_helper.rb") { "spec" }
5
+ end
6
+
7
+ clearing :on
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