Skip to content

High-performance Scala library for performing exchange rate lookups and currency conversions

Notifications You must be signed in to change notification settings

leetoo/scala-forex

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scala Forex

Build Status Release License Join the chat at https://gitter.im/snowplow/scala-forex

1. Introduction

Scala Forex is a high-performance Scala library for performing exchange rate lookups and currency conversions, using Joda-Money and Joda-Time.

It includes configurable LRU (Least Recently Used) caches to minimize calls to the API; this makes the library usable in high-volume environments such as Hadoop and Storm.

Currently Scala Forex uses the Open Exchange Rates API to perform currency lookups.

2. Setup

2.1 OER Sign Up

First sign up to Open Exchange Rates to get your App ID for API access.

There are three types of accounts supported by OER API, Unlimited, Enterprise and Developer levels. See the sign up page for specific account descriptions. For Scala Forex, we recommend an Enterprise or Unlimited account, unless all of your conversions are to or from USD (see section 4.5 OER accounts for an explanation). For 10-minute rate updates, you will need an Unlimited account (other accounts are hourly).

2.2 Installation

The latest version of Scala Forex is 0.5.0, which is cross-built against 2.10.x and 2.11.x.

If you're using SBT, add the following lines to your build file:

libraryDependencies ++= Seq(
  "com.snowplowanalytics" %% "scala-forex" % "0.5.0"
)

Note the double percent (%%) between the group and artifactId. That'll ensure you get the right package for your Scala version.

3. Usage

The Scala Forex supports two types of usage:

  1. Exchange rate lookups
  2. Currency conversions

Both usage types support live, near-live or historical (end-of-day) exchange rates.

3.1 Configuration

Scala Forex is configured via two case classes:

  1. ForexConfig contains general configuration
  2. OerClientConfig contains Open Exchange Rates-specific configuration

3.1.1 ForexConfig

Case class with defaults:

case class ForexConfig(
  nowishCacheSize: Int       = 13530,
  nowishSecs: Int            = 300,  
  eodCacheSize: Int          = 405900,  
  getNearestDay: EodRounding = EodRoundDown,
  baseCurrency: String       = "USD"  
)

To go through each in turn:

  1. nowishCacheSize is the size configuration for near-live(nowish) lookup cache, it can be disabled by setting its value to 0. The key to nowish cache is a currency pair so the size of the cache equals to the number of pairs of currencies available.

  2. nowishSecs is the time configuration for near-live lookup. Nowish call will use the exchange rates stored in nowish cache if its time stamp is less than or equal to nowishSecs old.

  3. eodCacheSize is the size configuration for end-of-day(eod) lookup cache, it can be disabled by setting its value to 0. The key to eod cache is a tuple of currency pair and time stamp, so the size of eod cache equals to the number of currency pairs times the days which the cache will remember the data for.

  4. getNearestDay is the rounding configuration for latest eod(at) lookup. The lookup will be performed on the next day if the rounding mode is set to EodRoundUp, and on the previous day if EodRoundDown.

  5. baseCurrency can be configured to different currencies by the users.

For an explanation for the default values please see section 4.4 Explanation of defaults below.

3.1.2 OerClientConfig

Case class with defaults:

case class OerClientConfig(
  appId: String,            
  accountLevel: AccountType
) extends ForexClientConfig

To go through each in turn:

  1. appId is the unique key for the user's account
  2. accountLevel is the account type provided by the user which should obviously be consistent with the app ID. There are three types of account levels, users should provide the exact account type name to configure the OER Client:
  3. UnlimitedAccount
  4. EnterpriseAccount
  5. DeveloperAccount

3.2 Rate lookup

For the required imports, please see section 4.2 REPL setup below.

3.2.1 Live rate

Lookup a live rate (no cacheing available):

// USD => JPY
val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount))
val usd2jpy = fx.rate.to("JPY").now   // => Right(JPY 105)           

3.2.2 Near-live rate

Lookup a near-live rate (cacheing available):

// JPY => GBP
val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount))
val jpy2gbp = fx.rate("JPY").to("GBP").nowish   // => Right(GBP 0.01)

3.2.3 Near-live rate without cache

Lookup a near-live rate (uses cache selectively):

// JPY => GBP
val fx = Forex(ForexConfig(nowishCacheSize = 0), OerClientConfig(appId, DeveloperAccount))
val jpy2gbp = fx.rate("JPY").to("GBP").nowish   // => Right(GBP 0.01)

3.2.4 Latest-prior EOD rate

Lookup the latest EOD (end-of-date) rate prior to your event (cacheing available):

import org.joda.time.{DateTime, DateTimeZone}

// USD => JPY at the end of 12/03/2011
val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount)) // round down to previous day by default
val tradeDate = new DateTime(2011, 3, 13, 11, 39, 27, 567, DateTimeZone.forID("America/New_York"))
val usd2yen = fx.rate.to("JPY").at(tradeDate)   // => Right(JPY 82)

3.2.5 Latest-post EOD rate

Lookup the latest EOD (end-of-date) rate post to your event (cacheing available):

import org.joda.time.{DateTime, DateTimeZone}
import com.snowplowanalytics.forex.EodRoundUp

// USD => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(getNearestDay = EodRoundUp), OerClientConfig(appId, DeveloperAccount))
val tradeDate = new DateTime(2011, 3, 13, 11, 39, 27, 567, DateTimeZone.forID("America/New_York"))
val usd2yen = fx.rate.to("JPY").at(tradeDate)   // => Right(JPY 82)

3.2.6 Specific EOD rate

Lookup the EOD rate for a specific date (cacheing available):

import org.joda.time.DateTime

// GBP => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(baseCurrency="GBP"), OerClientConfig(appId, EnterpriseAccount)) // Your app ID should be an Enterprise account
val eodDate = new DateTime(2011, 3, 13, 0, 0)
val gbp2jpy = fx.rate.to("JPY").eod(eodDate)   // => Right(JPY 131)

3.2.7 Specific EOD rate without cache

Lookup the EOD rate for a specific date (no cacheing):

import org.joda.time.DateTime

// GBP => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(eodCacheSize = 0, baseCurrency="GBP"), OerClientConfig(appId, EnterpriseAccount)) // Your app ID should be an Enterprise account
val eodDate = new DateTime(2011, 3, 13, 0, 0)
val gbp2jpy = fx.rate.to("JPY").eod(eodDate)   // => Right(JPY 131)

3.3 Currency conversion

For the required imports, please see section 4.2 REPL setup below.

3.3.1 Live rate

Conversion using the live exchange rate (no cacheing available):

// 9.99 USD => EUR
val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount))
val priceInEuros = fx.convert(9.99).to("EUR").now

3.3.2 Near-live rate

Conversion using a near-live exchange rate with 500 seconds nowishSecs (cacheing available):

// 9.99 GBP => EUR
val fx = Forex(ForexConfig(nowishSecs = 500), OerClientConfig(appId, DeveloperAccount))
val priceInEuros = fx.convert(9.99, "GBP").to("EUR").nowish

3.3.3 Near-live rate without cache

Note that this will be a live rate conversion if cache is not available. Conversion using a live exchange rate with 500 seconds nowishSecs, this conversion will be done via HTTP request:

// 9.99 GBP => EUR
val fx = Forex(ForexConfig(nowishSecs = 500, nowishCacheSize = 0), OerClientConfig(appId, DeveloperAccount))
val priceInEuros = fx.convert(9.99, "GBP").to("EUR").nowish

3.3.4 Latest-prior EOD rate

Conversion using the latest EOD (end-of-date) rate prior to your event (cacheing available):

import org.joda.time.{DateTime, DateTimeZone}

// 10000 GBP => JPY at the end of 12/03/2011
val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount))
val tradeDate = new DateTime(2011, 3, 13, 11, 39, 27, 567, DateTimeZone.forID("America/New_York"))
val tradeInYen = fx.convert(10000, "GBP").to("JPY").at(tradeDate)                   

3.3.5 Latest-post EOD rate

Lookup the latest EOD (end-of-date) rate following your event (cacheing available):

import org.joda.time.{DateTime, DateTimeZone}
import com.snowplowanalytics.forex.EodRoundUp

// 10000 GBP => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(getNearestDay = EodRoundUp), OerClientConfig(appId, DeveloperAccount))
val tradeDate = new DateTime(2011, 3, 13, 11, 39, 27, 567, DateTimeZone.forID("America/New_York"))
val usd2yen = fx.convert(10000, "GBP").to("JPY").at(tradeDate)

3.3.6 Specific EOD rate

Conversion using the EOD rate for a specific date (cacheing available):

import org.joda.time.DateTime

// 10000 GBP => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(baseCurrency="GBP"), OerClientConfig(appId, DeveloperAccount))
val eodDate = new DateTime(2011, 3, 13, 0, 0)
val tradeInYen = fx.convert(10000).to("JPY").eod(eodDate)

3.3.7 Specific EOD rate without cache

Conversion using the EOD rate for a specific date, (no cacheing):

import org.joda.time.DateTime

// 10000 GBP => JPY at the end of 13/03/2011
val fx = Forex(ForexConfig(eodCacheSize = 0, baseCurrency="GBP"), OerClientConfig(appId, DeveloperAccount))
val eodDate = new DateTime(2011, 3, 13, 0, 0)
val tradeInYen = fx.convert(10000).to("JPY").eod(eodDate)

3.4 Usage notes

3.4.1 LRU cache

The lruCache value determines the maximum number of values to keep in the LRU cache, which the Client will check prior to making an API lookup. To disable the LRU cache, set its size to zero, i.e. lruCache = 0.

3.4.2 From currency selection

A default "from currency" can be specified for all operations, using the baseCurrency argument to the ForexConfig object. If not specified, baseCurrency is set to USD by default.

4. Development

4.1 Building

Assuming git, Vagrant and VirtualBox installed:

 host> git clone https://github.com/snowplow/scala-forex
 host> cd scala-forex
 host> vagrant up && vagrant ssh
guest> cd /vagrant
guest> sbt build

4.2 REPL setup

To try out Scala Forex in the Scala REPL:

guest> sbt console
...
scala> val appId = "<<key>>"
scala> import com.snowplowanalytics.forex.{Forex, ForexConfig}
scala> import com.snowplowanalytics.forex.oerclient.{OerClientConfig, DeveloperAccount}  
scala> val fx = Forex(ForexConfig(), OerClientConfig(appId, DeveloperAccount))
scala> val usd2jpy = fx.rate.to("JPY").now

4.3 Running tests

You must export your OER_KEY or else the test suite will fail. To run the test suite locally:

$ export OER_KEY=<<key>>
$ git clone https://github.com/snowplow/scala-forex.git
$ cd scala-forex
$ sbt test

5. Implementation details

5.1 End-of-day definition

The end of today is 00:00 on the next day.

5.2 Exchange rate lookup

When .now is specified, the live exchange rate available from Open Exchange Rates is used.

When .nowish is specified, a cached version of the live exchange rate is used, if the timestamp of that exchange rate is less than or equal to nowishSecs (see above) ago. Otherwise a new lookup is performed.

When .at(...) is specified, the latest end-of-day rate prior to the datetime is used by default. Or users can configure that Scala Forex "round up" to the end of the occurring day.

When .eod(...) is specified, the end-of-day rate for the specified day is used. Any hour/minute/second/etc portion of the datetime is ignored.

5.3 LRU cache

We recommend trying different LRU cache sizes to see what works best for you.

Please note that the LRU cache implementation is not thread-safe (see this note). Switch it off if you are working with threads.

5.4 Explanation of defaults

5.4.1 nowishCache = (165 * 164 / 2) = 13530

There are 165 currencies provided by the OER API, hence 165 * 164 pairs of currency combinations. The key in nowish cache is a tuple of source currency and target currency, and the nowish cache was implemented in a way such that a lookup from CurrencyA to CurrencyB or from CurrencyB to CurrencyA will use the same exchange rate, so we don't need to store both in the caches. Hence there are (165 * 164 / 2) pairs of currencies for nowish cache.

5.4.2 eodCache = (165 * 164 / 2) * 30 = 405900

Assume the eod cache stores the rates to each pair of currencies for 1 month(i.e. 30 days). There are 165 * 164 / 2 pairs of currencies, hence (165 * 164 / 2) * 30 entries.

5.4.3 nowishSecs = 300

Assume nowish cache stores data for 5 mins.

5.4.4 getNearestDay = EodRoundDown

By convention, we are always interested in the exchange rates prior to the query date, hence EodRoundDown.

5.4.5 baseCurrency = USD

We selected USD for the base currency because this is the OER default as well.

5.5 OER accounts

With Open Exchange Rates' Unlimited and Enterprise accounts, Scala Forex can specify the base currency to use when looking up exchange rates; Developer-level accounts will always retrieve rates against USD, so a rate lookup from e.g. GBY to EUR will require two conversions (GBY -> USD -> EUR). For this reason, we recommend Unlimited and Enterprise-level accounts for slightly more accurate non-USD-related lookups.

6. Copyright and license

Scala Forex is copyright 2013-2016 Snowplow Analytics Ltd.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.

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.

About

High-performance Scala library for performing exchange rate lookups and currency conversions

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 96.8%
  • Shell 3.2%