Foreign currency exchange rates using Yahoo!

  • Authors : Paul Fernley
2 votes
Dependency :
compile ":exchange-rates:1.8"

Documentation

Summary

Description

Exchange Rates Plugin

Description

The exchange-rates plugin will either retrieve foreign currency exchange rates dynamically from Yahoo! or can store rates in the database on a day-by-day, currency-by-currency basis retrieving the rate from Yahoo! the first time each day that a conversion is requested for a given currency. A full set of CRUD screens is included together with a cache statistics screen (at a URL such as http://myServer/myApp/exchangeRate/cache) and a test screen for checking that the system is working (at a URL such as http://myServer/myApp/exchangeRate/test). The screens assume you are using a layout called main. Your application must have Internet access for the exchange-rates plugin to function.

Installation

Execute the following from your application directory:

grails install-plugin exchange-rates

The plugin creates two domains called ExchangeCurrency and ExchangeRate. It also copies a properties file called exchange-rates.properties to the i18n directory of your application overwriting any file of the same name. Two image files (false.png and true.png) are copied to the images directory of your application overwriting any files of the same name. After installation, the ExchangeCurrency table in your database should have a unique index on the 'code' column, but since Hibernate may or may not create this index, you are advised to check it exists otherwise performance may suffer.

Before using the plugin you need to determine what your base currency will be. Daily (as opposed to dynamic) rates are computed via a base currency whose exchange rate is always 1.0. The base currency has no other significance than this. Once the plugin is used, you cannot change the base currency without creating inconsistent data in the database. If you do not specify otherwise, the base currency will be USD (US Dollars). If you wished the base currency to be European Euros (ISO 4217 code EUR), for example, then you would make an entry in Config.groovy as follows:

exchangeRates.baseCurrencyCode="EUR"

By default, the exchange-rates plugin allows manual entry of exchange rates for the future to accomodate companies who set fixed 'corporate' exchange rates on a monthly, quarterly or annual basis etc. If you do not wish to allow the manual entry of future rates, make the following entry in your Config.groovy:

exchangeRates.future.rates=false

Daily exchange rates are held in memory in a 'least recently used cache' for fast repeated access. The default maximum cache size is 8kb, but memory is only used as is needed. If you wish to alter the maximum size (amount of memory) used by the cache, you may do so by making an entry similar to the following in your Config.groovy file:

exchangeRates.cache.size.kb=12

The above example Config.groovy entry increases the cache size to 12kb. Setting the cache size to zero disables caching with a consequent increase in databases activity. You can check the cache statistics using a URL such as: http://myServer/myApp/exchangeRate/cache. Note that you may have to refresh your browser window to see the most up to date statistics.

If your server needs a proxy to access the Internet, then according to the Groovy simple file download from URL example, you may globally make the proxy settings as shown in that example.

Usage

The components of the plugin are in a package called org.grails.plugins.exchangerates and any class that wishes to access the components directly must include the following:

import org.grails.plugins.exchangerates.*

Note that to use the programmatic interface of the exchange-rates plugin, you need access to the ExchangeRateService by including the following statement in a controller or service of your application:

def exchangeRateService

Dynamic Exchange Rates

Dynamic rates are only available programatically since the required rate is obtained dynamically from Yahoo! each time you ask for it. There are no screens within the plugin that relate to dynamic rates other than the test screen as described in the Testing section below. To obtain the current Yahoo! exchange rate from, say, Canadian Dollars to Australian Dollars, you would include the following code in your controller or service:

def rate = exchangeRateService.dynamicRate("CAD", "AUD")

The above code would leave the 'rate' variable (a BigDecimal with up to 6 digits after the decimal point) holding either the exchange rate from Canadian Dollars to Australian Dollars or null if no such rate could be obtained from Yahoo! The following example shows a complete conversion of a value from Canadian Dollars to Australian Dollars including rounding the result to the correct number of decimal places for Australian Dollars:

def canadianValue = 1.23
def rate = exchangeRateService.dynamicRate("CAD", "AUD")
def decs = exchangeRateService.decimalPlacesFor("AUD")
def rawValue = canadianValue * rate
def australianValue = rawValue.setScale(decs, java.math.RoundingMode.HALF_UP)

Since the above example is such a typical usage scenario, you could have achieved the same result more simply (and including error checking) as follows:

def australianValue = exchangeRateService.convertDynamic(1.23, "CAD", "AUD")

In the above example, the 'australianValue' variable would either contain the converted value (rounded to the correct number of decimal places for Australian Dollars) or null if the conversion could not be performed. If you were to perform the above example twice with a one second interval between the method calls, you would quite possibly get two different results since this is the essence of dynamic exchange rates. Similarly, dynamic exchange rates do not necessarily offer mathematic symmetry since exchange rates are based on actual human trading. For example, even if you obtained multiple conversion rates at the same instant in time, there is no guarantee that converting a value from, say, Canadian Dollars to Australian Dollars to New Zealand Dollars then back to Canadian Dollars would give you the value you first started with - even allowing for mathematical rounding errors. Daily rates, as described below, offer a more stable situation at the expense of accuracy at a point in time.

Daily Exchange Rates

For currencies flagged as auto-updatable, the first time each day that a currency is involved in a currency conversion (irrespective of the date for which the conversion is being requested) the current day's exchange rate for that currency is retrieved from Yahoo! and stored in the database if no manual rate has been entered for the current date. For currencies not flagged as auto-updatable you must manually enter the exchange rates yourself. The exchange rates stored in the database are relative to the base currency (which always has a rate of 1.0). For example, if the base currency is US Dollars and one US Dollar would buy you 0.75 European Euros, then the rate for European Euros would be 0.75 in the database. Consequently, the formula for converting from one currency to another is: toValue = fromValue * (toRate / fromRate). Subject to rounding limitations, this gives a mathematical symmetry to the operation of the system (i.e. converting one currency to another followed, later in the day, by the conversion back to the original currency should give the original value) and historical consistency - assuming no manual changes are made to the rates of the currencies involved.

Note that in all discussions about daily exchange rates, dates are relative to the server. This means that if you have users in different time zones from the server you may wish to adjust the date from the user's time zone to the server's time zone using Java's getRawOffset() method of the TimeZone class for both the server and user's time zones. Be aware, however, that if you do this you may end up with a date which, from the server's point of view, is 'tomorrow' and for which no rates may be available.

The exchange-rates plugin includes CRUD screens for maintaining currencies and their daily exchange rates. Of the currencies defined automatically for you on first use of the plugin, you will see that some are flagged as auto-updatable and others are not. This is because Yahoo! does not offer exchange rates for all possible currencies. Some companies set fixed exchange rates per month, quarter or year. In such cases you should consider setting all currencies to non-auto-updatable.

To obtain the daily exchange rate from, say, Canadian Dollars to Australian Dollars as on a given date, you would include the following code in your controller or service:

def rate = exchangeRateService.dailyRate("CAD", "AUD", date)

The above code would leave the 'rate' variable (a BigDecimal with up to 6 digits after the decimal point) holding either the exchange rate from Canadian Dollars to Australian Dollars on the given date or null if no such rate could be obtained from the database. If you omit the 'date' parameter, 'today' will be assumed. If you would like the plugin to perform a full conversion for you, including rounding the resulting value to the correct number of decimal places for the target currency, you could use code similar to the following:

def australianValue = exchangeRateService.convertDaily(1.23, "CAD", "AUD", date)

Again, if you omit the date parameter 'today' will be assumed. The 'australianValue' variable will either contain the Australian Dollar (AUD) equivalent of 1.23 Canadian Dollars (CAD) or null if the conversion cannot be performed - as when rates are unavailable from the database...

The opening paragraph of this section describes how daily exchange rates for 'today' are automatically inserted in to the database whenever a currency is involved for the first time that day in a daily currency conversion (irrespective of the date for which the conversion is to be performed). This implies that, if a specific currency is not used in a conversion on any given day, its rate for that day will not be updated in the database. This implication is indeed correct. If, for example, a specific currency has rates in the database for 2008-12-01 and 2008-12-03 but you subsequently ask the plugin for the historic rate on 2008-12-02, you will be given the latest rate on or before 2008-12-02. In this example you would therfore get the rate for 2008-12-01. If this is not what you want, then you can programatically perform a bulk update of the rates (presumably each day) of all auto-updatabale currencies using the following code:

exchangeRateService.bulkUpdateRates()

Finally, there are two additional methods available in the ExchangeRateService that you might wish to use. Firstly, if you wish to know the base currency code of your system, you can ask the plugin as follows:

def base = exchangeRateService.baseCurrencyCode()

By default, the variable 'base' would contain 'USD' for US Dollars unless you have changed the base currency as described in the Installation section above. Secondly, you might wish to know if manual entry of future exchange rates is permitted. You can do this as follows:

def allowed = exchangeRateService.futureRatesAllowed()

The 'allowed' variable in the above example will contain either of the boolean values true or false as applicable. Manual entry of future exchange rates is permitted by default but can be changed as described in the Installation section above.

Testing

At a URL similar to http://myServer/myApp/exchangeRate/test, you will find a screen that allows you to test the functioning of both dynamic and daily exchange rate conversions. The screen allows you to select the currency to convert from, the currency to convert to, the date to use for the daily rate (ignored by dynamic conversion which is always as of 'now') and the monetary amount to be converted. On pressing the Convert button, and assuming you are connected to the Internet, you will be shown the daily exchange rate and converted amount plus the dynamic exchange rate and converted amount. If any of these result fields is blank it means the relevant result could not be obtained (possibly due to lack of rates in the database etc).

Limitations

Yahoo! does not support exchange rates for all possible currencies. Of those currency definitions included automatically with this plugin, those which at the time of writing Yahoo! did not support have been flagged as non-auto-updatable. Your base currency will also be flagged as not being automatically updatable since its exchange rate must always be 1.0. No guarantee is given of the correctness or applicability to your requirements of the values returned (or failure to return a value) from the exchange-rates plugin or its data source, Yahoo!

Compatibility

This plugin was written using Java version 1.6u18 and Grails version 1.3.0.

If you have the help-balloons plugin installed, on-line help will be provided on the exchange-rates plugin screens, otherwise you will need to read the help texts in the exchange-rates.properties file to see what each field does. If you have the settings plugin installed and have created settings with keys of pagination.max and/or pagination.default then the exchange-rates plugin will use those settings for its pagination. If you have the criteria plugin installed then users of the currencies and rates list pages will be able to limit the records displayed using selection criteria. If you have the drilldowns plugin installed, users will be able to drill down from the currencies page to the rates page for a selected currency. The drilldowns plugin makes maintaining the various exchange rates much more convenient and you are therefore strongly advised to use the drilldowns plugin. If you have the menus plugin installed, a 'return to menu' button will be placed after the 'home' button on all exchange-rates plugin pages. If you have the localizations plugin installed, all translation of texts on the screens provided with the exchange-rates plugin will be done via the database rather than property bundles.

History

Version 1.8. (2010-05-12) Update to Grails v1.3

Version 1.7. (2009-12-30) Update to Grails v1.2

Version 1.6. (2009-04-03) Integration fix for drilldowns plugin

Version 1.5. (2009-04-01) Correction for settings package name

Version 1.4. (2009-04-01) Correction of duplicate packages

Version 1.3. (2009-03-31) Change for static allowedMethods

Version 1.2. (2009-03-31) Update to Grails v1.1 plus packages

Version 1.1. (2009-01-21) Double-check logic fault

Version 1.0. (2008-10-08) Initial release