Money

Currency-safe computations with money amounts.

Money is a special type of quantity. Its unit type is known as currency.

Money differs from physical quantities mainly in two aspects:

  • Money amounts are discrete. For each currency there is a smallest fraction that can not be split further.
  • The relation between different currencies is not fixed, instead, it varies over time.

The sub-package quantity.money provides classes and functions to deal with these specifics. Its main classes Money, Currency, ExchangeRate and the function registerCurrency() can also be imported from quantity.

Usage

Registering a currency

A currency must explicitly be registered as a unit for further use. The easiest way to do this is to call the function registerCurrency():

>>> EUR = registerCurrency('EUR')
... HKD = registerCurrency('HKD')
... TND = registerCurrency('TND')
... USD = registerCurrency('USD')
>>> EUR, HKD, TND, USD
(Currency(u'EUR'), Currency(u'HKD'), Currency(u'TND'), Currency(u'USD'))

The function is backed by a database of currencies defined in ISO 4217. It takes the 3-character ISO 4217 code as parameter.

Currency derives from Unit. Each instance has a symbol (which is the 3-character ISO 4217 code) and a name. In addition, it holds the smallest fraction defined for amounts in this currency:

>>> TND.symbol
u'TND'
>>> TND.name
u'Tunisian Dinar'
>>> TND.smallestFraction
Decimal('0.001')

Instantiating a money amount

As Money derives from Quantity, an instance can simply be created by giving an amount and a unit:

>>> Money(30, EUR)
Money(Decimal(30, 2), Currency(u'EUR'))

All amounts of money are rounded according to the smallest fraction defined for the currency:

>>> Money(3.128, EUR)
Money(Decimal('3.13'), Currency(u'EUR'))
>>> Money(41.1783, TND)
Money(Decimal('41.178'), Currency(u'TND'))

As with other quantities, money amounts can also be derived from a string or build using the operator ^:

>>> Money('3.18 USD')
Money(Decimal('3.18'), Currency(u'USD'))
>>> 3.18 ^ USD
Money(Decimal('3.18'), Currency(u'USD'))

Computing with money amounts

Money derives from Quantity, so all operations on quantities can also be applied to instances of Money. But because there is no fixed relation between currencies, there is no implicit conversion between money amounts of different currencies:

>>> Money(30, EUR) + Money(3.18, EUR)
Money(Decimal('33.18'), Currency(u'EUR'))
>>> Money(30, EUR) + Money(3.18, USD)
IncompatibleUnitsError: Can't convert 'US Dollar' to 'Euro'

Resulting values are always quantized to the smallest fraction defined with the currency:

>>> Money('3.20 USD') / 3
Money(Decimal('1.07'), Currency(u'USD'))
>>> Money('3.20 TND') / 3
Money(Decimal('1.067'), Currency(u'TND'))

Converting between different currencies

Exchange rates

A conversion factor between two currencies can be defined by using the ExchangeRate. It is given a unit currency (aka base currency), a unit multiple, a term currency (aka price currency) and a term amount, i.e. the amount in term currency equivalent to unit multiple in unit currency:

>>> fxEUR2HKD = ExchangeRate(EUR, 1, HKD, Decimal('8.395804'))
>>> fxEUR2HKD
ExchangeRate(Currency(u'EUR'), Decimal(1), Currency(u'HKD'),
    Decimal('8.395804'))

unitMultiple and termAmount will always be adjusted so that the resulting unit multiple is a power to 10 and the resulting term amounts magnitude is >= -1. The latter will always be rounded to 6 decimal digits.

>>> fxTND2EUR = ExchangeRate(TND, 5, EUR, Decimal('0.0082073'))
>>> fxTND2EUR
ExchangeRate(Currency(u'TND'), Decimal(100), Currency(u'EUR'),
    Decimal('0.164146'))

The resulting rate for an amount of 1 unit currency in term currency can be obtained via the property ExchangeRate.rate:

>>> fxTND2EUR.rate
Decimal('0.00164146')

The property ExchangeRate.quotation gives a tuple of unit currency, term currency and rate:

>>> fxTND2EUR.quotation
(Currency(u'TND'), Currency(u'EUR'), Decimal('0.00164146'))

The properties ExchangeRate.inverseRate and ExchangeRate.inverseQuotation give the rate and the quotation in the opposite direction (but do not round the rate!):

>>> fxTND2EUR.inverseRate
Fraction(50000000, 82073)
>>> fxTND2EUR.inverseQuotation
(Currency(u'EUR'), Currency(u'TND'), Fraction(50000000, 82073))

The inverse ExchangeRate can be created by calling the method ExchangeRate.inverted():

>>> fxEUR2TND = fxTND2EUR.inverted()
>>> fxEUR2TND
ExchangeRate(Currency(u'EUR'), Decimal(1), Currency(u'TND'),
    Decimal('609.213749'))

An exchange rate can be derived from two other exchange rates, provided that they have one currency in common (“triangulation”). If the unit currency of one exchange rate is equal to the term currency of the other, the two exchange rates can be multiplied with each other. If either the unit currencies or the term currencies are equal, the two exchange rates can be divided.

>>> fxEUR2HKD * fxTND2EUR
ExchangeRate(Currency(u'TND'), Decimal(10), Currency(u'HKD'),
    Decimal('0.137814'))
>>> fxEUR2HKD / fxEUR2TND
ExchangeRate(Currency(u'TND'), Decimal(10), Currency(u'HKD'),
    Decimal('0.137814'))
>>> fxEUR2TND / fxEUR2HKD
ExchangeRate(Currency(u'HKD'), Decimal(1), Currency(u'TND'),
    Decimal('72.561693'))
>>> fxHKD2EUR = fxEUR2HKD.inverted()
>>> fxTND2EUR / fxHKD2EUR
ExchangeRate(Currency(u'TND'), Decimal(10), Currency(u'HKD'),
    Decimal('0.137814'))

Converting money amounts using exchange rates

Multiplying an amount in some currency with an exchange rate with the same currency as unit currency results in the equivalent amount in term currency:

>>> mEUR = 5.27 ^ EUR
>>> mEUR * fxEUR2HKD
Money(Decimal('44.25'), Currency(u'HKD'))
>>> mEUR * fxEUR2TND
Money(Decimal('3210.556'), Currency(u'TND'))

Likewise, dividing an amount in some currency with an exchange rate with the same currency as term currency results in the equivalent amount in unit currency:

>>> fxHKD2EUR = fxEUR2HKD.inverted()
>>> mEUR / fxHKD2EUR
Money(Decimal('44.25'), Currency(u'HKD'))

Combining Money with other quantities

As Money derives from Quantity, it can be combined with other quantities in order to define a new quantity. This is, for example, useful for defining prices per quantum.

>>> class PricePerMass(Quantity):
...     defineAs = Money / Mass

Because Money has no reference unit, there is no reference unit created for the derived quantity.

>>> list(PricePerMass.Unit.registeredUnits())
[]

Units must be explicitly defined.

>>> EURpKG = PricePerMass.Unit(defineAs=EUR/KILOGRAM)
>>> list(PricePerMass.Unit.registeredUnits())
[PricePerMass.Unit(u'EUR/kg')]

As with other derived quantities, the function quantity.generateUnits() can be used to create all units from the cross-product of units of the base quantities.

Instances of the derived quantity can be created and used just like those of other quantities.

>>> p = 17.45 ^ EURpKG
>>> p * Decimal('1.05')
PricePerMass(Decimal('18.354', 4), PricePerMass.Unit(u'EUR/kg'))
>>> m = 530 ^ GRAM
>>> m * p
Money(Decimal('9.26'), Currency(u'EUR'))

Note that instances of the derived class are not automatically quantized to the quantum defined for the currency.

>>> PricePerMass.getQuantum(EURpKG) is None
True

Instances of such a “money per quantum” class can also be converted using exchange rates, as long as the resulting unit is defined.

>>> p * fxEUR2HKD
QuantityError: Resulting unit not defined: HKD/kg.
>>> HKDpKG = PricePerMass.Unit(defineAs=HKD/KILOGRAM)
>>> p * fxEUR2HKD
PricePerMass(Decimal('146.75865392'), PricePerMass.Unit(u'HKD/kg'))

Classes

class quantity.money.Currency

Represents a currency, i.e. a money unit.

Parameters:
  • isoCode (string) – ISO 4217 3-character code
  • name (string) – name of the currency
  • minorUnit (Integral) – amount of minor unit (as exponent to 10), optional, defaults to precision of smallest fraction, if that is given, otherwise to 2
  • smallestFraction (number) – smallest fraction available for the currency, optional, defaults to Decimal(10) ** -minorUnit

smallestFraction can also be given as a string, as long as it is convertable to a Decimal.

Returns:

Currency instance

Raises:
  • TypeError – given isoCode is not a string
  • ValueError – no isoCode was given
  • TypeError – given minorUnit is not an Integral number
  • ValueError – given minorUnit < 0
  • ValueError – given smallestFraction can not be converted to a Decimal
  • ValueError – given smallestFraction not > 0
  • ValueError – 1 is not an integer multiple of given smallestFraction
  • ValueError – given smallestFraction does not fit given minorUnit
isoCode

ISO 4217 3-character code.

name

Name of this currency.

smallestFraction

The smallest fraction available for this currency.

class quantity.money.Money

Represents a money amount, i.e. the combination of a numerical value and a money unit, aka. currency.

Instances of Money can be created in two ways, by providing a numerical amount and a Currency or by providing a string representation of a money amount.

1. Form

Parameters:
  • amount (number) – money amount (gets rounded to a Decimal according to smallest fraction of currency)
  • currency (Currency) – money unit

amount must convertable to a decimalfp.Decimal, it can also be given as a string.

Returns:

Money instance

Raises:
  • TypeErroramount can not be converted to a Decimal number
  • ValueError – no currency given

2. Form

Parameters:
  • mStr (string) – unicode string representation of a money amount (incl. currency symbol)
  • currency – the money’s unit (optional)

mStr must contain a numerical value and a currency symbol, separated atleast by one blank. Any surrounding white space is ignored. If currency is given in addition, the resulting money’s currency is set to this currency and its amount is converted accordingly, if possible.

Returns:

Money instance

Raises:
  • TypeError – amount given in mStr can not be converted to a Decimal number
  • ValueError – no currency given
  • TypeError – a byte string is given that can not be decoded using the standard encoding
  • ValueError – given string does not represent a Money amount
  • IncompatibleUnitsError – the currency derived from the symbol given in mStr can not be converted to given currency
classmethod getQuantum(unit)

Return the smallest amount an instance of Money can take for unit.

currency

The money’s currency, i.e. its unit.

class quantity.money.ExchangeRate(unitCurrency, unitMultiple, termCurrency, termAmount)

Basic representation of a conversion factor between two currencies.

Parameters:
  • unitCurrency (Currency) – currency to be converted from, aka base currency
  • unitMultiple (Integral) – amount of base currency
  • termCurrency (Currency) – currency to be converted to, aka price currency
  • termAmount (number) – equivalent amount of term currency

unitCurrency and termCurrency can also be given as 3-character ISO 4217 codes of already registered currencies.

unitMultiple must be > 1. It can also be given as a string, as long as it is convertable to an Integral.

termAmount can also be given as a string, as long as it is convertable to a number.

Example:

1 USD = 0.9683 EUR => ExchangeRate(‘USD’, 1, ‘EUR’, ‘0.9683’)

Returns:ExchangeRate instance

unitMultiple and termAmount will always be adjusted so that the resulting unit multiple is a power to 10 and the resulting term amounts magnitude is >= -1. The latter will always be rounded to 6 decimal digits.

Raises:
  • ValueError – unknown ISO 4217 code given for a currency
  • TypeError – value of type other than Currency or string given for a currency
  • ValueError – currencies given are identical
  • ValueError – unit multiple is not an Integral or is not >= 1
  • ValueError – term amount is not >= 0.000001
  • ValueError – unit multiple or term amount can not be converted to a Decimal
unitCurrency

Currency to be converted from, aka base currency.

termCurrency

Currency to be converted to, aka price currency.

rate

Relative value of termCurrency to unitCurrency.

inverseRate

Inverted rate, i.e. relative value of unitCurrency to termCurrency.

quotation

Tuple of unitCurrency, termCurrency and rate.

inverseQuotation

Tuple of termCurrency, unitCurrency and inverseRate.

inverted()

Return inverted exchange rate.

__hash__()

hash(self)

__eq__(other)

self == other

Parameters:other (object) – object to compare with
Returns:True if other is an instance of ExchangeRate and self.quotation == other.quotation, False otherwise
__mul__(other)

self * other

1. Form

Parameters:other (Money) – money amount to multiply with
Returns:Money instance: equivalent of other in term currency
Raises:ValueError – currency of other is not equal to unit currency

2. Form

Parameters:other (ExchangeRate) – exchange rate to multiply with
Returns:ExchangeRate instance: “triangulated” exchange rate
Raises:ValueError – unit currency of one multiplicant does not equal the term currency of the other multiplicant

3. Form

Parameters:other (Quantity sub-class) – quantity to multiply with

The type of other must be a sub-class of Quantity derived from Money divided by some other sub-class of Quantity.

Returns:Quantity sub-class instance: equivalent of other in term currency
Raises:ValueError – resulting unit is not defined
__div__(other)

self / other

Parameters:other (ExchangeRate) – exchange rate to divide with
Returns:ExchangeRate instance: “triangulated” exchange rate
Raises:ValueError – unit currencies of operands not equal and term currencies of operands not equal
__rdiv__(other)

other / self

1. Form

Parameters:other (Money) – money amount to divide
Returns:Money instance: equivalent of other in unit currency
Raises:ValueError – currency of other is not equal to term currency

2. Form

Parameters:other (Quantity sub-class) – quantity to divide

The type of other must be a sub-class of Quantity derived from Money divided by some other sub-class of Quantity.

Returns:Quantity sub-class instance: equivalent of other in unit currency
Raises:ValueError – resulting unit is not defined

Functions

quantity.money.getCurrencyInfo(isoCode)

Return infos from ISO 4217 currency database.

Parameters:isoCode (string) – ISO 4217 3-character code for the currency to be looked-up
Returns:3-character code, numerical code, name, minorUnit and list of countries which use the currency as functional currency
Return type:tuple
Raises:ValueError – currency with code isoCode not in database

Note

The database available here does only include entries from ISO 4217 which are used as functional currency, not those used for bond markets, noble metals and testing purposes.

quantity.money.registerCurrency(isoCode)

Register the currency with code isoCode from ISO 4217 database.

Parameters:isoCode (string) – ISO 4217 3-character code for the currency to be registered
Returns:Currency: registered currency
Raises:ValueError – currency with code isoCode not in database