Hordak features multi currency support. Each account in Hordak can support one or more currencies. Hordak does provide currency conversion functionality, but should be as part of the display logic only. It is also a good idea to make it clear to users that you are showing converted values.
The preference for Hordak internals is to always store & process values in the intended currency. This is because currency conversion is an inherently lossy process. Exchange rates vary over time, and rounding errors mean that currency conversions are not reversible without data loss (e.g. ¥176.51 -> $1.54 -> ¥176.20).
Moneyclass is provided by moneyd and combines both an amount and a currency into a single value. Hordak uses these these as the core unit of monetary value.
Balance instances (see below for more details):
An account can hold multiple currencies, and a Balance instance is how we represent this.
A Balance may contain one or more
Moneyobjects. There will be precisely one
Moneyobject for each currency which the account holds.
Balance objects may be added, subtracted etc. This will produce a new Balance object containing a union of all the currencies involved in the calculation, even where the result was zero.
is_bank_account=Truemay only support a single currency.
Currency conversion makes use of Django’s cache. It is therefore recommended that you setup your Django cache to something other than the default in-memory store.
currency_exchange() helper function is provided to assist in creating
currency conversion Transactions.
currency_exchange(source, source_amount, destination, destination_amount, trading_account, fee_destination=None, fee_amount=None, date=None, description=None)¶
Exchange funds from one currency to another
Use this method to represent a real world currency transfer. Note this process doesn’t care about exchange rates, only about the value of currency going in and out of the transaction.
You can also record any exchange fees by syphoning off funds to
fee_amount. Note that the free currency must be the same as the source currency.
For example, imagine our Canadian bank has obligingly transferred 120 CAD into our US bank account. We sent CAD 120, and received USD 100. We were also changed 1.50 CAD in fees.
We can represent this exchange in Hordak as follows:
from hordak.utilities.currency import currency_exchange currency_exchange( # Source account and amount source=cad_cash, source_amount=Money(120, 'CAD'), # Destination account and amount destination=usd_cash, destination_amount=Money(100, 'USD'), # Trading account the exchange will be done through trading_account=trading, # We also incur some fees fee_destination=banking_fees, fee_amount=Money(1.50, 'CAD') )
We should now find that:
cad_cash.balance()has decreased by
usd_cash.balance()has increased by
USD 100, CAD -120
You can perform
trading_account.normalise()to discover your unrealised gains/losses on currency traded through that account.
- source (Account) – The account the funds will be taken from
- source_amount (Money) – A
Moneyinstance containing the inbound amount and currency.
- destination (Account) – The account the funds will be placed into
- destination_amount (Money) – A
Moneyinstance containing the outbound amount and currency
- trading_account (Account) – The trading account to be used. The normalised balance of this account will indicate gains/losses you have made as part of your activity via this account. Note that the normalised balance fluctuates with the current exchange rate.
- fee_destination (Account) – Your exchange may incur fees. Specifying this will move incurred fees into this account (optional).
- fee_amount (Money) – The amount and currency of any incurred fees (optional).
- description (str) – Description for the transaction. Will default to describing funds in/out & fees (optional).
- date (datetime.date) – The date on which the transaction took place. Defaults to today (optional).
The transaction created
You can see the above example in practice in
An account balance
Accounts may have multiple currencies. This class represents these multi-currency balances and provides math functionality. Balances can be added, subtracted, multiplied, divided, absolute’ed, and have their sign changed.
Balance([Money(100, 'USD'), Money(200, 'EUR')]) # Or in short form Balance(100, 'USD', 200, 'EUR')
Balances can also be compared, but note that this requires a currency conversion step. Therefore it is possible that balances will compare differently as exchange rates change over time.
Get a list of the underlying
Returns: A list of zero or money money instances. Currencies will be unique. Return type: ([Money])
Get all currencies with non-zero values
Top-level exchange rate backend
This should be extended to hook into your preferred exchange rate service. The primary method which needs defining is
cache_rate(currency, date, rate)¶
Cache a rate for future use
Get the exchange rate for
If implementing your own backend, you should probably override
_get_rate()rather than this.
Get the exchange rate for
You should implement this in any custom backend. For each rate you should call
Normally you will only need to call
cache_rate()once. However, some services provide multiple exchange rates in a single response, in which it will likely be expedient to cache them all.
cache_rate()will result in your backend service being called for every currency conversion. This could be very slow and may result in your software being rate limited (or, if you pay for your exchange rates, you may get a big bill).
Use fixer.io for currency conversions