historical-bank 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS +2 -0
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +84 -0
- data/Gemfile +21 -0
- data/LICENSE +7 -0
- data/README.md +296 -0
- data/examples/add_get_rates.rb +110 -0
- data/examples/exchange.rb +75 -0
- data/historical-bank.gemspec +56 -0
- data/lib/money/bank/historical.rb +339 -0
- data/lib/money/rates_provider/open_exchange_rates.rb +123 -0
- data/lib/money/rates_store/historical_redis.rb +133 -0
- data/spec/bank/historical_spec.rb +503 -0
- data/spec/fixtures/time-series-2015-09.json +5199 -0
- data/spec/rates_provider/open_exchange_rates_spec.rb +201 -0
- data/spec/rates_store/historical_redis_spec.rb +176 -0
- data/spec/spec_helper.rb +29 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 598c943f0debd7880311ebe7be452417b8482e7d
|
4
|
+
data.tar.gz: 85c1004279edde93a2a2e56b4c1fef411d98f273
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ce3fb2034576449270f62515454bfe48654c669f0696702358fe4fc5f0bb1c77e960c304c32d03d38267a8d3b8c3ed065691c2c11bb3b93b84ad1e1a265c7cf
|
7
|
+
data.tar.gz: 0b312998d80ceb7e1f9a0e464484aa11b2c96b0977185cbc9993b9d542a47a57390158c139888adc1b8eb38ea02df0183c2ebe4fcdc14bfdaabc4ef64ad70816
|
data/AUTHORS
ADDED
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributing to historical-bank-ruby
|
2
|
+
|
3
|
+
We're glad you want to make a contribution!
|
4
|
+
|
5
|
+
Fork this repository and send in a pull request when you're finished with your changes. Link any relevant issues in too.
|
6
|
+
|
7
|
+
Take note of the build status of your pull request, only builds that pass will be accepted. Please also keep to our conventions and style so we can keep this repository as clean as possible.
|
8
|
+
|
9
|
+
|
10
|
+
## Steps
|
11
|
+
|
12
|
+
1. Fork the repo
|
13
|
+
2. Grab dependencies: `bundle install`
|
14
|
+
3. Make sure specs are green: `./spec/redis-server.sh` to run the Redis server and `bundle exec rspec` to run the specs. Alternatively, you can run your own Redis server in `localhost`, and add the port as an env var before running the specs, `REDIS_PORT=6379 bundle exec rspec`
|
15
|
+
4. Make your changes
|
16
|
+
5. Run `rubocop -a`
|
17
|
+
6. Make sure specs are still green
|
18
|
+
7. Update `CHANGELOG.md` and `AUTHORS`
|
19
|
+
8. Issue a Pull Request and link any relevant issues
|
20
|
+
|
21
|
+
|
22
|
+
## Tips
|
23
|
+
|
24
|
+
Please make sure you read [README.md](README.md) before you start hacking so that you understand how the gem works :)
|
25
|
+
|
26
|
+
### Redis usage
|
27
|
+
|
28
|
+
The Redis cache that we use stores Hashes with keys formatted as `[namespace]:[base_currency]:[quote_currency]`.
|
29
|
+
These hashes contain ISO dates as keys with base currency rates as values.
|
30
|
+
For example, if `currency` is the Redis namespace we used in the config,
|
31
|
+
USD is the base currency, and we're looking for HUF rates, they can be found in the `currency:USD:HUF` key, and their format will be
|
32
|
+
```
|
33
|
+
{
|
34
|
+
"2016-05-01": "0.272511002E3", # Rates are stored as BigDecimal Strings
|
35
|
+
"2016-05-02": "0.270337998E3",
|
36
|
+
"2016-05-03": "0.271477498E3"
|
37
|
+
}
|
38
|
+
```
|
39
|
+
|
40
|
+
The first key of this Hash translates to: "on May 1st 2016, 1 USD was equivalent to 272.511002 HUF".
|
41
|
+
|
42
|
+
|
43
|
+
### Memory caching
|
44
|
+
|
45
|
+
Apart from Redis, rates are also cached in the Bank's memory,
|
46
|
+
in `@rates`. `@rates` is a `Hash[iso_currency][iso_date]`
|
47
|
+
containing `BigDecimal` exchange rates of `iso_currency` on `iso_date`.
|
48
|
+
When they don't exist in memory, they are retrieved from the Store,
|
49
|
+
and when they don't exist in the Store, they are retrieved from the
|
50
|
+
Provider.
|
51
|
+
|
52
|
+
|
53
|
+
## Possible improvements
|
54
|
+
|
55
|
+
- Make Redis optional
|
56
|
+
- Add another provider (e.g. XE.com)
|
57
|
+
- Add another store (e.g. DynamoDB, MongoDB, Cassandra, Postgres, etc)
|
58
|
+
|
59
|
+
|
60
|
+
## License
|
61
|
+
|
62
|
+
By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/Skyscanner/historical-bank-ruby/blob/master/LICENSE
|
63
|
+
|
64
|
+
All files are released with the Apache 2.0 license.
|
65
|
+
|
66
|
+
If you are adding a new file it should have a header like this:
|
67
|
+
|
68
|
+
```
|
69
|
+
#
|
70
|
+
# Copyright 2017 Skyscanner Limited.
|
71
|
+
#
|
72
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
73
|
+
# you may not use this file except in compliance with the License.
|
74
|
+
# You may obtain a copy of the License at
|
75
|
+
#
|
76
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
77
|
+
#
|
78
|
+
# Unless required by applicable law or agreed to in writing, software
|
79
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
80
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
81
|
+
# See the License for the specific language governing permissions and
|
82
|
+
# limitations under the License.
|
83
|
+
#
|
84
|
+
```
|
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2017 Skyscanner Limited.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
# frozen_string_literal: true
|
18
|
+
|
19
|
+
source 'https://rubygems.org'
|
20
|
+
|
21
|
+
gemspec
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2017 Skyscanner Ltd
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
4
|
+
|
5
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
# historical-bank-ruby
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/Skyscanner/historical-bank-ruby.svg?branch=master)](https://travis-ci.org/Skyscanner/historical-bank-ruby)
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
This gem provides a bank that can serve historical rates,
|
8
|
+
contrary to most bank implementations that provide only current market rates.
|
9
|
+
[Open Exchange Rates](https://openexchangerates.org/) (OER) is used as the provider of the rates and an Enterprise or Unlimited plan is required.
|
10
|
+
As the HTTP requests to OER can add latency to your calls, a `RatesStore` (cache) based on Redis was added, making it super-fast.
|
11
|
+
|
12
|
+
You can use it as your default bank and keep calling the standard `money` gem methods (`Money#exchange_to`, `Bank#exchange_with`). On top of that, we've added a few more methods that allow accessing historical rates (`Money#exchange_to_historical`, `Bank#exchange_with_historical`).
|
13
|
+
|
14
|
+
|
15
|
+
### Base currency
|
16
|
+
|
17
|
+
An **exchange rate** has a **base currency** and a **quote (or counter) currency**.
|
18
|
+
More specifically, it is the price of 1 unit of base currency in the quote currency.
|
19
|
+
For example, if base currency is EUR, quote currency is USD, and the rate is 1.25,
|
20
|
+
this means that 1 EUR is equal to 1.25 USD.
|
21
|
+
|
22
|
+
All the rates fetched and cached by this `Bank` are relative to a single base currency which is defined in the configuration block.
|
23
|
+
This helps to optimize fetching and caching rates.
|
24
|
+
|
25
|
+
Default base currency is EUR, but it can be changed in the config.
|
26
|
+
|
27
|
+
|
28
|
+
### Dates, times, timezones
|
29
|
+
|
30
|
+
The timezone used throughout this gem is the UTC timezone.
|
31
|
+
|
32
|
+
All processed rates (fetched from OER, added manually, cached in Redis) are considered to be the [closing (end of day) rates](https://openexchangerates.org/faq/#eod-values) for their associated dates in UTC.
|
33
|
+
For example, when we have a cached rate of EUR->USD on January 10th 2017 with value 1.25,
|
34
|
+
this means that 1 EUR was equivalent to 1.25 USD on January 10th at 23:59:59.
|
35
|
+
This is the historical rate that the bank will use for exchanging EUR with USD on that date.
|
36
|
+
Consequently, a rate for a certain `date` fetched from OER becomes available at 00:00 UTC on `date+1`.
|
37
|
+
|
38
|
+
For convenience, methods that accept `Date`s as arguments can accept `Time`s as well.
|
39
|
+
When a `Time` is used, it is first converted into the UTC-equivalent `Date`,
|
40
|
+
and method is executed as if that `Date` was passed instead.
|
41
|
+
For example, when the `Time` `2017-01-10 02:50:00 +04:00` is passed as argument to `#exchange_with_historical`,
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
from_money = Money.new(100_00, 'EUR')
|
45
|
+
to_currency = 'USD'
|
46
|
+
|
47
|
+
# 2017-01-10 02:50:00 +0400
|
48
|
+
bank.exchange_with_historical(from_money, to_currency, Time.new(2017, 1, 10, 2, 50, 0, '+04:00'))
|
49
|
+
# => #<Money fractional:10585 currency:USD>
|
50
|
+
```
|
51
|
+
|
52
|
+
it is equivalent to passing the `Date` `2017-01-09`
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# 2017-01-09
|
56
|
+
bank.exchange_with_historical(from_money, to_currency, Date.new(2017, 1, 9))
|
57
|
+
# => #<Money fractional:10585 currency:USD>
|
58
|
+
```
|
59
|
+
|
60
|
+
|
61
|
+
### Caching
|
62
|
+
|
63
|
+
We've implemented 2 layers of caching in order to obliterate latency!
|
64
|
+
First layer is memory (instance variable in the bank object), and second is Redis.
|
65
|
+
If desired rate is not found in memory, the bank tries to look it up in Redis.
|
66
|
+
If that fails too, a request to OER is made.
|
67
|
+
|
68
|
+
When we fetch rates from OER, they are cached in Redis and memory too.
|
69
|
+
Similarly, when the rate is found in Redis, it is again cached in memory.
|
70
|
+
|
71
|
+
<p align="center">
|
72
|
+
<img src="images/cache_diagram.png" alt="Caching diagram"/>
|
73
|
+
</p>
|
74
|
+
|
75
|
+
Pretty simple and fast!
|
76
|
+
|
77
|
+
|
78
|
+
### Singleton
|
79
|
+
|
80
|
+
The bank follows the Singleton pattern, as it inherits from `money` gem's `Money::Bank::Base`.
|
81
|
+
This also helps preserve the memory cache across calls.
|
82
|
+
Don't worry, the bank is thread-safe!
|
83
|
+
|
84
|
+
|
85
|
+
## Requirements
|
86
|
+
|
87
|
+
- **OpenExchangeRates Enterprise or Unlimited plan** - It is needed for calling the `/time-series.json` endpoint which serves historical rates.
|
88
|
+
- **Redis >= 2.8** (versions of Redis earlier than 2.8 may also work fine, but this gem has only been tested with 2.8 and above.)
|
89
|
+
- **Ruby >= 2.0**
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
## Installation
|
94
|
+
|
95
|
+
```
|
96
|
+
gem install historical-bank
|
97
|
+
```
|
98
|
+
|
99
|
+
Alternatively, if you're using `bundler`, you can add
|
100
|
+
``` ruby
|
101
|
+
gem 'historical-bank'
|
102
|
+
```
|
103
|
+
to your `Gemfile` and run `bundle install`
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
## Usage
|
108
|
+
|
109
|
+
Example scripts demonstrating all functionality can be found in [`examples/`](examples/).
|
110
|
+
|
111
|
+
### Configuration
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
Money::Bank::Historical.configure do |config|
|
115
|
+
# (required) your OpenExchangeRates App ID
|
116
|
+
config.oer_app_id = 'XXXXXXXXXXXXXXX'
|
117
|
+
|
118
|
+
# (optional) currency relative to which all the rates are stored (default: EUR)
|
119
|
+
config.base_currency = Money::Currency.new('USD')
|
120
|
+
|
121
|
+
# (optional) the URL of the Redis server (default: 'redis://localhost:6379')
|
122
|
+
config.redis_url = 'redis://localhost:6379'
|
123
|
+
|
124
|
+
# (optional) Redis namespace to prefix all keys (default: 'currency')
|
125
|
+
config.redis_namespace = 'currency_historical_gem'
|
126
|
+
|
127
|
+
# (optional) set a timeout for the OER calls (default: 15 seconds)
|
128
|
+
config.timeout = 20
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
#### Rails
|
133
|
+
|
134
|
+
In Rails, config should be set inside an initializer (`config/initializers`).
|
135
|
+
If you have the `money-rails` gem installed, you can add this on top of the `config/initializers/money.rb` file.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# config/initializers/money.rb
|
139
|
+
|
140
|
+
require 'money/bank/historical'
|
141
|
+
|
142
|
+
Money::Bank::Historical.configure do |config|
|
143
|
+
# ....
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
MoneyRails.configure do |config|
|
148
|
+
# ...
|
149
|
+
|
150
|
+
# if you want to set it as the default bank
|
151
|
+
config.default_bank = Money::Bank::Historical.instance
|
152
|
+
|
153
|
+
# ...
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
#### Sinatra
|
158
|
+
|
159
|
+
In a Sinatra app (or any other kind of app), simply set the config before you call `Money::Bank::Historical.instance`.
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# app.rb
|
163
|
+
|
164
|
+
require 'money/bank/historical'
|
165
|
+
|
166
|
+
Money::Bank::Historical.configure do |config|
|
167
|
+
# ....
|
168
|
+
end
|
169
|
+
|
170
|
+
class App < Sinatra::Base
|
171
|
+
|
172
|
+
configure do
|
173
|
+
# if you want to set it as the default bank
|
174
|
+
Money.default_bank = Money::Bank::Historical.instance
|
175
|
+
end
|
176
|
+
|
177
|
+
# ...
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
### Dates
|
182
|
+
|
183
|
+
The minimum date for which we can fetch rates is January 1st 1999.
|
184
|
+
This [limitation](https://docs.openexchangerates.org/docs/api-introduction) is set by the OpenExchangeRates API.
|
185
|
+
The maximum date for which we fetch OER rates is yesterday (in UTC),
|
186
|
+
as [today's rates are not yet final](https://openexchangerates.org/faq/#timezone).
|
187
|
+
|
188
|
+
However, if you want to overcome these limitations manually, you can add past (and even future!) rates using `Historical#add_rate` and `#add_rates`.
|
189
|
+
|
190
|
+
### Exchange
|
191
|
+
|
192
|
+
You can use the bank object for the exchange
|
193
|
+
```ruby
|
194
|
+
from_money = Money.new(100_00, 'EUR')
|
195
|
+
to_currency = 'GBP'
|
196
|
+
|
197
|
+
bank = Money::Bank::Historical.instance
|
198
|
+
|
199
|
+
# exchange money with rates from December 10th 2016
|
200
|
+
bank.exchange_with_historical(from_money, to_currency, Date.new(2016, 12, 10))
|
201
|
+
# => #<Money fractional:8399 currency:GBP>
|
202
|
+
|
203
|
+
# can also pass a Time/DateTime object, it's converted into the respective UTC Date
|
204
|
+
bank.exchange_with_historical(from_money, to_currency, Time.utc(2016, 10, 2, 11, 0, 0))
|
205
|
+
# => #<Money fractional:8691 currency:GBP>
|
206
|
+
```
|
207
|
+
|
208
|
+
Or perform it directly on the `Money` object
|
209
|
+
```ruby
|
210
|
+
from_money.exchange_to_historical(to_currency, Date.new(2016, 12, 10))
|
211
|
+
# => #<Money fractional:8399 currency:GBP>
|
212
|
+
```
|
213
|
+
|
214
|
+
`Bank#exchange_with` and `Money#exchange_to` can still be used. In this case, recent rates are needed, so yesterday's closing rates are used for the calculation
|
215
|
+
```ruby
|
216
|
+
bank.exchange_with(from_money, to_currency)
|
217
|
+
|
218
|
+
# set the default bank and create a new Money object that will use it
|
219
|
+
Money.default_bank = Money::Bank::Historical.instance
|
220
|
+
from_money = Money.new(100_00, 'EUR')
|
221
|
+
from_money.exchange_to(to_currency)
|
222
|
+
```
|
223
|
+
|
224
|
+
### Adding and retrieving rates
|
225
|
+
|
226
|
+
Adding rates will not be needed in most cases as the rates are fetched from OER.
|
227
|
+
However, `#add_rate` and `#get_rate` were implemented in order to conform to the Bank API.
|
228
|
+
An extra `#add_rates` method was implemented for setting rates in bulk.
|
229
|
+
|
230
|
+
`#add_rate` and `add_rates` can prove quite when **testing**,
|
231
|
+
as you can't afford HTTP requests there.
|
232
|
+
Only thing you need to do is initialize the bank, and add some default rates
|
233
|
+
before your tests run.
|
234
|
+
|
235
|
+
|
236
|
+
#### Get single rate
|
237
|
+
|
238
|
+
`#get_rate` accepts both ISO strings and Money::Currency objects
|
239
|
+
```ruby
|
240
|
+
bank.get_rate(Money::Currency.new('GBP'), 'CAD', Date.new(2016, 10, 1))
|
241
|
+
# => #<BigDecimal:7fd39fd2cb78,'0.1703941289 451827243E1',27(45)>
|
242
|
+
```
|
243
|
+
|
244
|
+
Getting without a datetime will return yesterday's closing rate, e.g. `bank.get_rate('CAD', 'CNY')`.
|
245
|
+
|
246
|
+
#### Add single rate
|
247
|
+
|
248
|
+
`#add_rate` adds a single rate to the Redis cache. It accepts both ISO strings and `Money::Currency` objects. Added rates should be relative to the base currency.
|
249
|
+
```ruby
|
250
|
+
date = Date.new(2016, 5, 18)
|
251
|
+
|
252
|
+
bank.add_rate('EUR', 'USD', 1.2, date)
|
253
|
+
bank.add_rate(Money::Currency.new('USD'), Money::Currency.new('GBP'), 0.8, date)
|
254
|
+
|
255
|
+
# 100 EUR = 100 * 1.2 USD = 100 * 1.2 * 0.8 GBP = 96 GBP
|
256
|
+
bank.exchange_with_historical(from_money, to_currency, date)
|
257
|
+
# => #<Money fractional:9600 currency:GBP>
|
258
|
+
```
|
259
|
+
|
260
|
+
Adding without a datetime will set the rate to yesterday's closing rate
|
261
|
+
```ruby
|
262
|
+
bank.add_rate('EUR', 'USD', 1.4)
|
263
|
+
bank.add_rate(Money::Currency.new('USD'), Money::Currency.new('GBP'), 0.6)
|
264
|
+
|
265
|
+
# 100 EUR = 100 * 1.4 USD = 100 * 1.4 * 0.6 GBP = 84 GBP
|
266
|
+
bank.exchange_with(from_money, to_currency)
|
267
|
+
# => #<Money fractional:8400 currency:GBP>
|
268
|
+
```
|
269
|
+
|
270
|
+
Trying to add a rate that is not relative to the base currency will fail.
|
271
|
+
This is because all cached rates are relative to the base currency.
|
272
|
+
```ruby
|
273
|
+
bank.add_rate('EUR', 'GBP', 0.96, date)
|
274
|
+
# ArgumentError: `from_currency` (EUR) or `to_currency` (GBP) should match the base currency USD
|
275
|
+
```
|
276
|
+
|
277
|
+
#### Add rates in bulk
|
278
|
+
|
279
|
+
`#add_rates` can be used to add multiple historical rates (relative to the base currency) to the Redis cache.
|
280
|
+
```ruby
|
281
|
+
rates = {
|
282
|
+
'EUR' => {
|
283
|
+
'2015-09-10' => 0.11, # 1 USD = 0.11 EUR
|
284
|
+
'2015-09-11' => 0.22
|
285
|
+
},
|
286
|
+
'GBP' => {
|
287
|
+
'2015-09-10' => 0.44, # 1 USD = 0.44 GBP
|
288
|
+
'2015-09-11' => 0.55
|
289
|
+
}
|
290
|
+
}
|
291
|
+
bank.add_rates(rates)
|
292
|
+
|
293
|
+
# 100 EUR = 100 / 0.11 USD = 100 / 0.11 * 0.44 GBP = 400 GBP
|
294
|
+
bank.exchange_with_historical(from_money, to_currency, Date.new(2015, 9, 10))
|
295
|
+
# => #<Money fractional:40000 currency:GBP>
|
296
|
+
```
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2017 Skyscanner Limited.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
# frozen_string_literal: true
|
18
|
+
|
19
|
+
require 'money/bank/historical'
|
20
|
+
|
21
|
+
redis_url = 'redis://localhost:6379'
|
22
|
+
namespace = 'currency_example'
|
23
|
+
|
24
|
+
Money::Bank::Historical.configure do |config|
|
25
|
+
# (required) your OpenExchangeRates App ID
|
26
|
+
config.oer_app_id = 'XXXXXXXXXXXXXXXXXXXXX'
|
27
|
+
|
28
|
+
# (optional) currency relative to which all the rates are stored (default: EUR)
|
29
|
+
config.base_currency = Money::Currency.new('USD')
|
30
|
+
|
31
|
+
# (optional) the URL of the Redis server (default: 'redis://localhost:6379')
|
32
|
+
config.redis_url = redis_url
|
33
|
+
|
34
|
+
# (optional) Redis namespace to prefix all keys (default: 'currency')
|
35
|
+
config.redis_namespace = namespace
|
36
|
+
|
37
|
+
# (optional) set a timeout for the OER calls (default: 15 seconds)
|
38
|
+
config.timeout = 20
|
39
|
+
end
|
40
|
+
|
41
|
+
bank = Money::Bank::Historical.instance
|
42
|
+
|
43
|
+
from_money = Money.new(100_00, 'EUR')
|
44
|
+
to_currency = 'GBP'
|
45
|
+
|
46
|
+
############ Get single rate #################
|
47
|
+
|
48
|
+
# It accepts both ISO strings and Money::Currency objects.
|
49
|
+
bank.get_rate(Money::Currency.new('GBP'), 'CAD', Date.new(2016, 10, 1))
|
50
|
+
# => #<BigDecimal:7fd39fd2cb78,'0.1703941289 451827243E1',27(45)>
|
51
|
+
|
52
|
+
# Getting without a datetime will return yesterday's closing rate
|
53
|
+
bank.get_rate('CAD', 'CNY')
|
54
|
+
|
55
|
+
############ Add single rate #################
|
56
|
+
|
57
|
+
# It accepts both ISO strings and Money::Currency objects.
|
58
|
+
# Added rates should be relative to the base currency
|
59
|
+
date = Date.new(2016, 5, 18)
|
60
|
+
|
61
|
+
bank.add_rate('EUR', 'USD', 1.2, date)
|
62
|
+
bank.add_rate(Money::Currency.new('USD'), Money::Currency.new('GBP'), 0.8, date)
|
63
|
+
|
64
|
+
# 100 EUR = 100 * 1.2 USD = 100 * 1.2 * 0.8 GBP = 96 GBP
|
65
|
+
bank.exchange_with_historical(from_money, to_currency, date)
|
66
|
+
# => #<Money fractional:9600 currency:GBP>
|
67
|
+
|
68
|
+
# Adding without a datetime will set the rate to yesterday's closing rate
|
69
|
+
bank.add_rate('EUR', 'USD', 1.4)
|
70
|
+
bank.add_rate(Money::Currency.new('USD'), Money::Currency.new('GBP'), 0.6)
|
71
|
+
|
72
|
+
# 100 EUR = 100 * 1.4 USD = 100 * 1.4 * 0.6 GBP = 84 GBP
|
73
|
+
bank.exchange_with(from_money, to_currency)
|
74
|
+
# => #<Money fractional:8400 currency:GBP>
|
75
|
+
|
76
|
+
# trying to add a rate that is not relative to the base currency will fail
|
77
|
+
bank.add_rate('EUR', 'GBP', 0.96, date)
|
78
|
+
# ArgumentError: `from_currency` (EUR) or `to_currency` (GBP) should match the base currency USD
|
79
|
+
|
80
|
+
############ Add rates in bulk #################
|
81
|
+
|
82
|
+
# add historical exchange rates (relative to the base currency) in bulk
|
83
|
+
rates = {
|
84
|
+
'EUR' => {
|
85
|
+
'2015-09-10' => 0.11, # 1 USD = 0.11 EUR
|
86
|
+
'2015-09-11' => 0.22,
|
87
|
+
'2015-09-12' => 0.33
|
88
|
+
},
|
89
|
+
'GBP' => {
|
90
|
+
'2015-09-10' => 0.44, # 1 USD = 0.44 GBP
|
91
|
+
'2015-09-11' => 0.55,
|
92
|
+
'2015-09-12' => 0.66
|
93
|
+
},
|
94
|
+
'VND' => {
|
95
|
+
'2015-09-10' => 0.77, # 1 USD = 0.77 VND
|
96
|
+
'2015-09-11' => 0.88,
|
97
|
+
'2015-09-12' => 0.99
|
98
|
+
}
|
99
|
+
}
|
100
|
+
bank.add_rates(rates)
|
101
|
+
|
102
|
+
# 100 EUR = 100 / 0.11 USD = 100 / 0.11 * 0.44 GBP = 400 GBP
|
103
|
+
bank.exchange_with_historical(from_money, to_currency, Date.new(2015, 9, 10))
|
104
|
+
# => #<Money fractional:40000 currency:GBP>
|
105
|
+
|
106
|
+
########## Clean up Redis keys used here #########
|
107
|
+
|
108
|
+
redis = Redis.new(url: redis_url)
|
109
|
+
keys = redis.keys("#{namespace}*")
|
110
|
+
redis.del(keys)
|