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