class Money::Bank::Historical

Bank that serves historical exchange rates. Inherits from Money::Bank::Base

Public Class Methods

configuration() click to toggle source

Returns the configuration (Money::Bank::Historical::Configuration)

# File lib/money/bank/historical.rb, line 55
def self.configuration
  @configuration ||= Configuration.new
end
configure() { |configuration| ... } click to toggle source

Configures the bank. Parameters that can be configured:

  • oer_app_id - (required) your OpenExchangeRates App ID

  • oer_account_type - (optional) your OpenExchangeRates account type. Choose one of the values in the Money::RatesProvider::OpenExchangeRates::AccountType module (default: Money::RatesProvider::OpenExchangeRates::AccountType::ENTERPRISE)

  • base_currency - (optional) Money::Currency relative to which all the rates are stored (default: EUR)

  • redis_url - (optional) the URL of the Redis server (default: redis://localhost:6379)

  • redis_namespace - (optional) Redis namespace to prefix all keys (default: currency)

  • timeout - (optional) set a timeout for the OER calls (default: 15 seconds)

Examples

Money::Bank::Historical.configure do |config|
  config.oer_app_id = 'XXXXXXXXXXXXXXXXXXXXX'
  config.oer_account_type = Money::RatesProvider::OpenExchangeRates::AccountType::FREE
  config.base_currency = Money::Currency.new('USD')
  config.redis_url = 'redis://localhost:6379'
  config.redis_namespace = 'currency'
  config.timeout = 20
end
# File lib/money/bank/historical.rb, line 77
def self.configure
  yield(configuration)
  instance.setup
end

Public Instance Methods

add_rate(from_currency, to_currency, rate, datetime = yesterday_utc) click to toggle source

Adds a single rate for a specific date to the Redis cache. If no datetime is passed, it defaults to yesterday (UTC). One of the passed currencies should match the base currency.

Parameters

  • from_currency - Fixed currency of the rate (en.wikipedia.org/wiki/Exchange_rate#Quotations). Accepts ISO String and Money::Currency objects.

  • to_currency - Variable currency of the rate (en.wikipedia.org/wiki/Exchange_rate#Quotations). Accepts ISO String and Money::Currency objects.

  • rate - The price of 1 unit of from_currency in to_currency.

  • datetime - The Date this rate was observed. If Time is passed instead, it's converted to the UTC Date. If no datetime is passed, it defaults to yesterday (UTC).

Errors

  • Raises ArgumentError when neither from_currency, nor to_currency match the base_currency given in the configuration.

Examples

Assuming USD is the base currency

from_money = Money.new(100_00, 'EUR')
to_currency = 'GBP'

date = Date.new(2016, 5, 18)

# 1 EUR = 1.2 USD on May 18th 2016
bank.add_rate('EUR', 'USD', 1.2, date)
# 1 USD = 0.8 GBP on May 18th 2016
bank.add_rate(Money::Currency.new('USD'), Money::Currency.new('GBP'), 0.8, date)

# 100 EUR = 100 * 1.2 USD = 100 * 1.2 * 0.8 GBP = 96 GBP
bank.exchange_with_historical(from_money, to_currency, date)
# => #<Money fractional:9600 currency:GBP>
# File lib/money/bank/historical.rb, line 158
def add_rate(from_currency, to_currency, rate, datetime = yesterday_utc)
  from_currency = Currency.wrap(from_currency)
  to_currency = Currency.wrap(to_currency)

  if from_currency != @base_currency && to_currency != @base_currency
    raise ArgumentError, "`from_currency` (#{from_currency.iso_code}) or "\
                         "`to_currency` (#{to_currency.iso_code}) should "\
                         "match the base currency #{@base_currency.iso_code}"
  end

  date = datetime_to_date(datetime)

  currency_date_rate_hash = if from_currency == @base_currency
                              {
                                to_currency.iso_code => {
                                  date.iso8601 => rate
                                }
                              }
                            else
                              {
                                from_currency.iso_code => {
                                  date.iso8601 => 1 / rate
                                }
                              }
                            end

  add_rates(currency_date_rate_hash)
end
add_rates(currency_date_rate_hash) click to toggle source

Adds historical rates in bulk to the Redis cache.

Parameters

currency_date_rate_hash - A Hash of exchange rates, broken down by currency and date. See the example for details.

Examples

Assuming USD is the base currency

rates = {
  'EUR' => {
    '2015-09-10' => 0.11, # 1 USD = 0.11 EUR
    '2015-09-11' => 0.22
  },
  'GBP' => {
    '2015-09-10' => 0.44, # 1 USD = 0.44 GBP
    '2015-09-11' => 0.55
  }
}
bank.add_rates(rates)
# File lib/money/bank/historical.rb, line 121
def add_rates(currency_date_rate_hash)
  @store.add_rates(currency_date_rate_hash)
end
exchange_with(from_money, to_currency) click to toggle source

Exchanges from_money to to_currency using yesterday's closing rates and returns a new Money object.

Parameters

  • from_money - The Money object to exchange

  • to_currency - The currency to exchange from_money to. Accepts ISO String and Money::Currency objects.

# File lib/money/bank/historical.rb, line 221
def exchange_with(from_money, to_currency)
  exchange_with_historical(from_money, to_currency, yesterday_utc)
end
exchange_with_historical(from_money, to_currency, datetime) click to toggle source

Exchanges from_money to to_currency using datetime's closing rates and returns a new Money object.

Parameters

  • from_money - The Money object to exchange

  • to_currency - The currency to exchange from_money to. Accepts ISO String and Money::Currency objects.

  • datetime - The Date to get the exchange rate from. If Time is passed instead, it's converted to the UTC Date.

# File lib/money/bank/historical.rb, line 233
def exchange_with_historical(from_money, to_currency, datetime)
  date = datetime_to_date(datetime)

  from_currency = from_money.currency
  to_currency = Currency.wrap(to_currency)

  rate = rate_on_date(from_currency, to_currency, date)
  to_amount = from_money.amount * rate

  Money.from_amount(to_amount, to_currency)
end
get_rate(from_currency, to_currency, datetime = yesterday_utc) click to toggle source

Returns the BigDecimal rate for converting from_currency to to_currency on a specific date. This is the price of 1 unit of from_currency in to_currency on that date. If rate is not found in the Redis cache, it is fetched from OpenExchangeRates. If no datetime is passed, it defaults to yesterday (UTC).

Parameters

  • from_currency - Fixed currency of the returned rate (en.wikipedia.org/wiki/Exchange_rate#Quotations). Accepts ISO String and Money::Currency objects.

  • to_currency - Variable currency of the returned rate (en.wikipedia.org/wiki/Exchange_rate#Quotations). Accepts ISO String and Money::Currency objects.

  • datetime - The Date the returned rate was observed. If Time is passed instead, it's converted to the UTC Date. If no datetime is passed, it defaults to yesterday (UTC).

Examples

bank.get_rate(Money::Currency.new('GBP'), 'CAD', Date.new(2016, 10, 1))
# => #<BigDecimal:7fd39fd2cb78,'0.1703941289 451827243E1',27(45)>
# File lib/money/bank/historical.rb, line 205
def get_rate(from_currency, to_currency, datetime = yesterday_utc)
  from_currency = Currency.wrap(from_currency)
  to_currency = Currency.wrap(to_currency)

  date = datetime_to_date(datetime)

  rate_on_date(from_currency, to_currency, date)
end
setup() click to toggle source

Called at the end of the superclass' initialize and also when configuration changes. It initializes/resets all the instance variables.

# File lib/money/bank/historical.rb, line 84
def setup
  @base_currency = Historical.configuration.base_currency
  # Hash[iso_currency][iso_date]
  @rates = {}
  @store = RatesStore::HistoricalRedis.new(@base_currency,
                                           Historical.configuration.redis_url,
                                           Historical.configuration.redis_namespace)
  @provider = RatesProvider::OpenExchangeRates.new(Historical.configuration.oer_app_id,
                                                   @base_currency,
                                                   Historical.configuration.timeout,
                                                   Historical.configuration.oer_account_type)
  # for controlling access to @rates
  @mutex = Mutex.new
end

Private Instance Methods

base_rate_on_date(currency, date) click to toggle source

rate for converting 1 unit of base currency to currency

# File lib/money/bank/historical.rb, line 267
def base_rate_on_date(currency, date)
  return 1 if @base_currency == currency

  rate = get_base_rate(currency, date) ||
         fetch_stored_base_rate(currency, date) ||
         fetch_provider_base_rate(currency, date)

  if rate.nil?
    raise UnknownRate, "Rate from #{currency} to #{@base_currency} "\
                       "on #{date} not found"
  end

  rate
end
datetime_to_date(datetime) click to toggle source
# File lib/money/bank/historical.rb, line 247
def datetime_to_date(datetime)
  datetime.is_a?(Date) ? datetime : datetime.utc.to_date
end
fetch_provider_base_rate(currency, date) click to toggle source
# File lib/money/bank/historical.rb, line 293
def fetch_provider_base_rate(currency, date)
  currency_date_rate_hash = @provider.fetch_rates(date)

  date_rate_hash = currency_date_rate_hash[currency.iso_code]
  rate = date_rate_hash && date_rate_hash[date.iso8601]

  if currency_date_rate_hash && !currency_date_rate_hash.empty?
    @store.add_rates(currency_date_rate_hash)
  end

  if date_rate_hash && !date_rate_hash.empty?
    set_base_rates(currency, date_rate_hash)
  end

  rate
end
fetch_stored_base_rate(currency, date) click to toggle source
# File lib/money/bank/historical.rb, line 282
def fetch_stored_base_rate(currency, date)
  date_rate_hash = @store.get_rates(currency)

  if date_rate_hash && !date_rate_hash.empty?
    rate = date_rate_hash[date.iso8601]
    set_base_rates(currency, date_rate_hash)

    rate
  end
end
get_base_rate(currency, date) click to toggle source
# File lib/money/bank/historical.rb, line 318
def get_base_rate(currency, date)
  @mutex.synchronize do
    rates = @rates[currency.iso_code]
    rates[date] if rates
  end
end
rate_on_date(from_currency, to_currency, date) click to toggle source

rate for converting 1 unit of from_currency (e.g. USD) to to_currency (e.g. GBP). Comments below assume EUR is the base currency, 1 EUR = 1.21 USD, and 1 EUR = 0.83 GBP on given date

# File lib/money/bank/historical.rb, line 254
def rate_on_date(from_currency, to_currency, date)
  return 1 if from_currency == to_currency

  # 1 EUR = 1.21 USD => 1 USD = 1/1.21 EUR
  from_base_to_from_rate = base_rate_on_date(from_currency, date)
  # 1 EUR = 0.83 GBP
  from_base_to_to_rate   = base_rate_on_date(to_currency,   date)

  # 1 USD = 1/1.21 EUR = (1/1.21) * 0.83 GBP = 0.83/1.21 GBP
  from_base_to_to_rate / from_base_to_from_rate
end
set_base_rates(currency, date_rate_hash) click to toggle source
# File lib/money/bank/historical.rb, line 310
def set_base_rates(currency, date_rate_hash)
  iso_currency = currency.iso_code
  @mutex.synchronize do
    @rates[iso_currency] = {} if @rates[iso_currency].nil?
    @rates[iso_currency].merge!(date_rate_hash)
  end
end
yesterday_utc() click to toggle source

yesterday in UTC timezone

# File lib/money/bank/historical.rb, line 326
def yesterday_utc
  Time.now.utc.to_date - 1
end