QuEP 6: Currency, Money, and Exchange Rates

Joel Nelson

Abstract

This is a proposal to enhance QuantLib with a new module for handling currencies, money, and foreign exchange. The implementation follows closely the OMG Currency specifications (ftp://ftp.omg.org/pub/docs/formal/00-06-29.pdf), but not to the end of being 100% OMG compliant. In addition to new classes, some of the current functionality relating to "date-time" and "amount of time" concepts are extended to meet the needs of the currency module.

Current implementation and Disadvantages

This is an add-on enhancement for missing functionality more than it is an extension of any current implementation. Currency only exists as an enumeration of some ISO currency codes. Apart from this, there is are no abstractions of "currency", "money", or "exchange rates" available from within the library.

A Date abstraction exists in the QuantLib::Date class and contains a pretty complete "date" implementation. However, it has no "time" component for representing "date-time". Another potential limitation is that "date" is a single, concrete class. By adding an abstract class containing the "date-time" interface, the implementation would be freed up and others could be added (Strategy pattern). The current implementation is based around a date serial number (as in Excel) that must be manipulated to produce the date in any other form. While functionally correct, it is can be a drag on performance. Providing a Date-Time abstract class instead of just wrapping QuantLib::Date allows us to add new Date and DateTime implementations as needed.

"Amount of time" exists as QuantLib::Period, but the implementation is limited to a specific use. It can hold a "unit of time" as an integer, but can not convert between units. Furthermore, once a Period object is constructed, it can not change precision. For example, if the Period object is created to hold weeks it can not deal with an amount of time such as "2 weeks, 2 days, 12 hours". It can handle some of the functionality needed to hold an "amount of time", but it isn't as flexible as the OMG DAmountOfTime object. QuantLib::Period is fine for what it represents, but the library needs the functionality in AmountOfTime as a complimentary class.

Proposed implementation

Following the OMG spec, the currency module can be broken down into four main components and the supporting enhancements.
The new components are:

The enhancements are:

For a more detailed business level explanation of the module, see the related OMG document (ftp://ftp.omg.org/pub/docs/formal/00-06-29.pdf).

Quantlib::Currency Namespace Note

All related currency, money, and exchange rate classes in a new namespace named "Currency". The supporting enhancements are all contained the "QuantLib" namespace.

Currency Component Description

Currency encapsulates basic descriptive information of a currency, the methods used to create a currency and. This includes:

  1. currency name. e.g. "U.S. Dollar"
  2. mnemonic e.g. "USD"
  3. numeric code e.g. "840" (used for ISO currencies)
  4. symbol e.g. "$"
  5. fraction symbol e.g. "¢"
  6. scale factor e.g. 10
  7. ratio of fraction to whole e.g. 100 for dollar:cents
  8. description e.g. "United States Dollar"
  9. introduction date e.g. (QuantLib default date)
  10. expiration date e.g. (QuantLib default date)
  11. if it represents and ISO currency description
  12. if ISO, which version it represents
  13. if the external/internal output should display fractional part
  14. operations to allow setting external/internal output display
  15. a set of locales for which the currency is used (for ISO currencies)
  16. operations to add/remove locales
  17. operations to return exchange triangulation next/last currencies
  18. operations to return preferred rounding method
  19. an equality operator for comparing two currencies

Currency Module Design and Static Model

Currency static diagram

The Currency class hierarchy is using the pimpl idiom for lightweight copying and to make polymorphic currency behavior available to scripting languages without the Handle template problems. The base class Currency contains a Handle to the CurrencyImpl abstract class which is comprised of the data fields and getter definitions for Currency. (For more information about the pimpl idiom, see 1: Implementing polymorphic behavior by means of the pimpl idiom.

Usually when applying the pimpl idiom, all operations called on Currency would then be proxied to peer operations on the CurrencyImpl class through the Handle reference. For the Currency class this is only true for the addLocale and removeLocale operations. The rest directly access the public member variables of the CurrencyImpl. it would create more work to duplicate all the getXXX operations in the Currency and CurrencyImpl classes without providing any clear benefits.

The only responsibility of the CurrencyImpl subclasses are to define implementation for the addLocale/removeLocale and setExtern/InternOutputShowingFractions operations and data initialization on construction.

The DefaultCurrency class is used by the client to create currencies that do not already exist as classes in the library. Two constructors are provided: a default constructor that creates a Currency with only the default data values and a constructor that takes in the parameters to create a Currency object with user specified values.

There are also several Currency subclasses that implement the most common ISO currencies. They are all designed as singletons so their constructors are declared protected. Instances should be created/retrieved by calling the getInstance operation on each. The ISOCurrencyImpl adapter class defines the addLocale/removeLocale so that the ISOCurrencyImpl subclasses need only handle data initialization for the relevant ISO currency definition. Planned ISO currency classes are USDCurrency, JPYCurrency, EURCurrency, and GBPCurrency. This list will grow as needed.

Currency Implementation Notes

The following are descriptions of the data fields on the CurrencyImpl class. Collectively they hold the state information for every currency object.

string name
string mnemonic
string numeric
string symbol
string fractionSymbol
string description
They are meant to contain ISO 4217:1995 data, but that is left up to the client constructing the Currency instance.

long Currency::scaleFactor
This is the number of significant digits preferred to represent an amount of this currency. For now, setting the scaleFactor will have no effect. It is intended to be used by Money via the Decimal object reference (Money.value). For the time being, Decimal is just a typedef to a double, therefore scaleFactor has no relevance yet.

int ratioFract2Whole
The ratio of the fraction part to whole part of a currency is the ratio between the smallest unit of the fractional part of a currency and one unit of the whole part of a currency. The ratio for United States Dollars is one hundred pennies to one dollar, so ratioFract2Whole = 100. In cases where there is no minor part of a currency (i.e., Yen), it will be set to 1.

DateTime introductionDate
DateTime expirationDate
Introduction/Expiration date will contain a default date if none exists. The default date used is defined by the QuantLib::DateTime class implementation if none is provided by the client instantiating the currency.

boolean isISO
string isoVersion
For more on the ISO currency names and codes see the ISO 4217:1995 currency specification.

vector<string> locale
A user can add and remove locales from any currency. A CurrencyException of type "UNKNOWN_LOCALE" will be thrown if the locale passed in is not found on the list of ISO defined locales (ISO 4217:1995).

Rounding prefRounding
Some currencies have preferred rounding requirements. The Money class uses this to determine how to round for arithmetic operations if no other Rounding class is specified.

Currency nextCurrency/lastCurrency
These operations will be used by the ExchangeRateManager in order to follow triangulated exchange requirements of some currencies. All of the Euro member currencies will return the instance of EURCurrency. There is more on this in the ExchangeRateManager notes.

Money

Money can be thought of as a currency instance. It holds a decimal value (some amount of money) and provides for arithmetic and comparison operations on that amount of money.

Note that the Money class makes use of a new type: Decimal. For now, that is just a typedef to a double. Going forward it should a typedef to a class which contains arbitrary precision decimal representation standard across all platforms and not subject to the rounding problems with floats. For convenience, this should probably expose math operations like rounding (through compositor Rounding class), abs, etc. But this should be the subject of another QuEP.

Money Static Model

Money static diagram

Money is a concrete class providing all the operations necessary to create a new instance, compare two Money objects, and to do basic math. The client can instantiate a Money object with a different rounding to that which the Currency prefers and/or to provide a reference to an ExchangeRateManager object (for currency conversion).

At the minimum, the client must pass a Currency and a Decimal to create a Money instance. Money will attempt to use the rounding associated with its Currency if no other Rounding object is specified. If the Currency has no specific rounding associated to it, no rounding will be performed until the client provides a Rounding object with the setRounding operation.

The CurrencyParams class contains two static variables used by Money when attempting math on two Money objects of different Currencies. The ConversionType contains the values that indicate whether this type of operation is legal and if so what kind of conversion to use. The Base_Currency refers to the Currency to use for conversion if ConversionType = "BASE_CURRENCY_CONVERSION".

Money Implementation Notes

Math Operations
When automatic conversion is allowed and two Money's with different Currencies are added, subtracted, multiplied, or divided, the result will be a Money instance with the Currency on the right side of the operator.

Money[sourceCcy] + Money[targetCcy] = Money[targetCcy]

If base currency conversion is allowed, then the result is in terms of the CurrencyParams.BaseCurrency

Money[oneCcy] + Money[anotherCcy] = Money[baseCcy]

This only applies to the operators. If the operation add(Money m) is passed a Money with a different Currency, the result will be reflected in the Money.value on that object. The Currency will be the same as before add was called.

Please note, whenever Currency exchange is required to do math or logical tests, the values are converted first, then the math is performed on the converted values. Rounding is applied to each individually converted amount by calling Money.round() on each. For example, using the examples above, the illustration below outlines the steps taken for conversion and rounding:
Automatic Conversion Case
Money1[sourceCcy] + Money2[targetCcy] = Money3[targetCcy]

  1. convert Money1[sourceCcy] -> Money1[targetCcy]
  2. Money1[targetCcy].round() - using the same Rounding object as Money2[targetCcy]
  3. add Money1 + Money2
Base Currency Conversion Case
Money1[oneCcy] + Money2[anotherCcy] = Money[BaseCurrency]
  1. convert Money1[oneCcy] -> Money1[baseCcy]
  2. Money1[baseCcy].round() - using the default Rounding object for BaseCurrency
  3. Money2[baseCcy].round() - using the default Rounding object for BaseCurrency
  4. add Money1 + Money2

Whether or not an attempt to add/subtract two Moneys with different Currencies will succeed is determined by the following:

  1. Money must contain a reference to an ExchangeRateManager object that is able to furnish all the exchange rates necessary to perform a conversion.
  2. The CurrencyParams.ConversionType must be "BASE_CURRENCY_CONVERSION" or "AUTOMATED_CONVERSION"

Comparison Operators
In addition to the math operators, the following logical operators are overloaded as well:

Money::round()/rounding()
If the Currency specifies a rounding preference (for example EURCurrency), rounding will be turned on (isRounding will return true). This indicates that all arithmetic operations will be finish by rounding/truncating the final value. The resulting precision is of course determined by the Rounding implementation used. If the Currency does not specify a rounding type, rounding will remain off, calling rounding() will return 0, and calling round() will have no effect on value.

Exchange Rate and Exchange Rate Managers

The exchange rate abstraction contains references to source currency, target currency, the conversion factor, and a rate type identifier. It provides operations for exchanging Money between the source and target currencies. Using a MarketElement member to represent the conversion factor makes the exchange rate observable thought a common interface. A specialized type of Exchange Rate provides date bounded exchange rates.

The Exchange Rate manager abstraction contains a collection of exchange rates and operations for adding, removing, and querying. The look ups are base on source currency, target currency, and rate type. If set to do so, the lookup can create and return derived rates when no explicit direct rate exists. It also takes into account triangulated exchange requirements if present in the source and/or target currencies (Euroland currencies, for example). The related abstraction, DExchangeRateManager, performs serves the same purpose, but for date bounded exchange rates.

Exchange Rate and Exchange Rate Managers Static Model

Exchange Rates static diagram

ExchangeRate is a concrete class that may be constructed with a Currency source, a Currency target, a MarketElement conversion factor, and optionally a string Type. If no string type is provided, it will be defaulted to "direct". The conversion factor is a reference to a QuantLib::MarketElement providing the benefits of the observer/observable pattern and for cohesiveness with the rest of the library. The ExchangeRateList rateChain is a Vector for use when a "direct" exchange is not possible. There are no public setters. All changes to the conversion factor should be performed through the MarketElement. The main function, exchange, takes in Money of either source or target Currency and returns a new Money object reference of the target or source Currency.

DExchangeRate extends ExchangeRate to add startDate/endDate operations. It also provides constructors for including DateTime startDate/endDate.

RateManager is a template providing functions for storage and lookup of ExchangeRates. A subclass RateManager adds the lookup operation that also takes in a DateTime as a search parameter. The type ExchangeRateManager and DExchangeRateManager are a typedef's to RateManager<ExchangeRate> and to DRateManager<DExchangeRate>, respectively.

The ExchangeRates are held in a RateMap for efficient lookup. For Now, RateMap is a typedef to a Map, but if the need arises could be a typedef to an appropriate HashMap.

ExchangeRateManager and DExchangeRateManager are concrete class that maintain and perform lookups on ExchangeRateList and DExchangeRateList members, respectively. They were designed as separate classes because the operations should be named the same and in some cases take in the same parameters (the lookupRate function), but return different types. This is not valid polymorphic behavior so can not be expressed in a class hierarchy. The functionality they do share (although not publicly) is inherited from the superclass ExchangeRateManager base.

Exchange Rate and Exchange Rate Manager Implementation Notes

typedef Vector<ExchangeRate> ExchangeRateList
typedef Vector<DExchangeRate> DExchangeRateList

typedef RateManager<DExchangeRate> RateMap typedef RateManager<DExchangeRate> DRateMap

ExchangeRateManager::lookupRate() At a minimum, this function needs a target and a source currency. The ExchangeRateManager will attempt to find an ExchangeRate with the same source/target currencies first, followed by an ExchangeRate with the source/target currencies in the inverse order. If no type is specified, the type of "any" will be assumed. The following type prioritization is used:

If a specific type is passed in, only an exchange rate of that type will be returned. Because ExchangeRate.type is defined as a string it could be client defined however they choose. However the ExchangeRateManager recognize the following types as special:

lookupRate handles the cases where specific rules have been given for exchanging through an intermediary currency. For example, all currency exchanges between Euro currencies and any other currency must first be exchanged through the Euro. This is also true to Euro currency to Euro currency exchanges. ( europa.eu.it )
ExchangeRateManager uses the Currency.nextCurrency/lastCurrency information to construct a "derived" type ExchangeRate that follows the exchange triangulation conventions.

ExchangeRateManager::isAutoAddDerived/setAutoAddDerived
Options are available to automatically add derived exchange rates to the internal map so that subsequent lookups are more efficient.

ExchangeRate::Exchange()
The conversion implementation allows for bi-directional exchange based on the Money.currency passed in. For example, if the ExhangeRate was constructed with USD as the source currency and EUR as the target currency (ExchangeRate[USD2EUR]), then passing in a Money[EUR] would cause the Money[EUR].value() to be divided by the conversionFactor's value. A new instance of Money[USD] with the result would be returned. Conversely, passing in a Money[USD] would cause Money[USD].value() to be multiplied by the conversionFactor's value. A new instance of Money[EUR] with the result would be returned.
Note that exchange does not round before returning the Money instance. If the client wants a rounded value, call the Money.round operation. If conversion is happening due to arithmetic on differently denominated Money's, the any rounding is left up to the Money object. It does not happen here.
In the case that the exchange rate is type "derived", this means that the rateChain contains an vector of ExchangeRate objects. In this case, rather than use the conversionFactor, exchange iterates through the list from top to bottom calling exchange on each. For example, if the ExchangeRate is "derived" for converting FRF into JPY, and the rateChain contains the following:

  1. ExchangeRate[FRF to EUR]
  2. ExchangeRate[JPY to EUR]
Calling exchange(Money[FRF]) would do the following::
  1. Money[EUR] = ExchangeRate[FRF2EUR].exchange( Money[FRF] )
  2. Money[JPY] = ExchangeRate[EUR2JPY].exchange( Money[EUR] )
  3. return Money[JPY]

ExchangeRate::type()
Types used by the ExchangeRateManager's are "Direct" and "Derived" although clients creating new ExchangeRate instances can input any string. When adding ExchangeRates to an ExchangeRateManager, the only condition is that the combination of type, source, and target currencies must be unique. "Direct" is the default for client instantiated instances because the conversion factor represents an explicit mapping from the source to target currency. "Derived" is used by the ExchangeRateManager to indicate that it constructed the ExchangeRate from a chain of "Direct" and/or "Derived" rates.

Note that the == (equals) operator is also overloaded.

Rounding/Truncation

A simple set of classes have is created for the purposes of rounding money after it has been exchanged and after money arithmetic. It is outside of the Currency namespace so can be used wherever rounding/truncation is needed.

Rounding/Truncation Static Model

Rounding static diagram

Rounding is the abstract base class defining the basic operation for rounding. The subclasses provide the implementation for their specific types of rounding (strategy pattern).

Rounding/Truncation Implementation Notes

The Ceiling/FloorTruncation classes are concrete subclasses implementing the two basic types of truncation at with arbitrary precision. The precision is specified by the client by passing an int representing the decimal precision of the truncated number.

ComputersRuleRounding rounds just as VB/Excel does for currencies: when the 1st digit dropped is 5 with no following digits or 5 with following digits, then add 1 to the rounding place if current digit is odd, leave it as is if even. As with the truncation classes, the client only specifies the internal precision.

EuroRounding rounds as specified in "Euro Paper 22" ( find it at europa.eu.it ): Round to 2 digits. If the 3rd significant digit is less than 5, then round down, else round up. Because the precision is already set by the specification, only the default constructor is available.

CustomRounding is implemented to follow the OMG guidelines for rounding (at least in the Currency specification). The client passes in both the precision and the rounding digit upon construction. The round implementation checks the first digit after the specified precision to see if it is equal to or greater than the rounding digit. If so, it rounds the preceding digit up, else it leaves it unchanged. For example, if precision=2 and rounding digit=4, then the number 458.564 would round up to 458.57. The number 458.563 would round to 458.56.
Note that this could be also used to construct a "LooseEuroRounding" object by using 5 as the rounding digit and selecting a precision greater than 2.

Date-Time and Amount of Time

DateTime extends the concept of Date in QuantLib to include a time component as well as enhancing the interface to be more OMG DTime like. It has been designed to be backwards compatible with the current QuantLib usage of Date. This does not guarantee compatibility with all client usage of QuantLib::Date, however.

AmountOfTime is used with the DateTime class for flexible date-time addition and to represent a difference between two DateTime's.

DateTime Static Model

DateTime static diagram

DateTime is an abstract class that declares the operations for basic date time functionality. It is more or less a union between the operations on QuantLib::Date and the OMG DTime value interface definition. It also contains static members defining the minimum, maximum, and default date-time's.

The first concrete implementation of DateTime is ExcelDateTime. Because the Excel serial number can hold precision down to the second (in the fractional part), the ExcelDateTime class leverages the existing Quantlib::Date class to represent DateTime. In the near future, other more efficient concrete DateTime classes will be created. This will be the subject of a QuEP yet to come.

You will note that the pimpl idiom was not used for the DateTime class hierarchy. For the pimpl idiom to be most beneficial, the concrete implementing classes should not define new operations outside of object construction. They should each contain a specific implementation for the operations defined in the abstract implementing superclass. In the case of DateTime, we already know that at least one subclass will be adding operations that are unique to that subclass. For example, ExcelDateTime has getters and setters for the Excel style serial number it contains. Probably no other DateTime concrete class will need these operations. As other DateTime implementations are created other similarly unique operations will be added (e.g. formatDate/formatTime). Because these new operations can not be defined in terms of those existing in the superclass, it is not appropriate to use the pimpl idiom here.

The QuantLib::Date class is still intended to be used via its static operations. There is no corresponding isLeapYear operation on the new DateTime interface.

AmountOfTime (not in the model) is not an extension of the Quantlib::Period class. Instead it is a new class implemented more or less exactly along the lines of the OMG spec DAmountOfTime interface. If needed, the Period class could be enhanced to return an AmountOfTime representation of itself, but that is not the focus of this enhancement.

DateTime Implementation Notes

In addition to the operations shown in the static model, there are overloaded operators to provide DateTime comparisons (<, <=, >, >=, ==), to subtract one date from another, and to add or subtract an AmountOfTime from a DateTime. Here are examples of the arithmetic operators:

DateTime + AmountOfTime = DateTime

DateTime - AmountOfTime = DateTime

DateTime - DateTime = AmountOfTime

Hours, Minutes, Seconds types shown in the model are typedef'd to int's. This is inline with QuantLib's current Day type.

Todo

Should I include more about the Exceptions or just leave it for the technical documentation (i.e. doxygen)?
Formatting: I will be taking a look at this in the mean time.

Conclusion

Try currency out. You will see it can be fun.

Feedback

Feedback on the above proposal should be posted to the QuantLib-dev mailing list.