Currency conversion

I wrote two currency conversion explainers in 2021, now both combined on this page, showing different ways of recording conversions. See Cookbook > Multiple currencies for more.


Here are various ways of recording a conversion from one currency or commodity to another.

Implicit conversion rate

Let's say we are exchanging cash with a friend:

2021-07-27 give dollars, get euros
    assets:cash      USD -10.00
    assets:cash      EUR   8.50

hledger understands that 10 dollars were converted to 8.50 euros. A conversion rate is inferred automatically so as to make the transaction balance. (This can be seen with hledger print -x.)

This is easy to write and to understand. However it is not a fully correct double-entry-bookkeeping journal entry, since USD has magically transformed into EUR "in flight". It is also error prone, since a typo in either amount may not be detected. For example, we might forget the decimal point and write USD -1000. Also, it is easy to create such entries accidentally. For example, in one posting within a transaction we might mistype or omit the currency symbol. hledger accepts these implicit conversions by default, for convenience and compatibility. But you can disallow them by using strict mode or by running the check command (eg: hledger check balancednoautoconversion).

Explicit conversion rate

We can record the conversion rate (cost of one currency in the other) explicitly. This adds redundancy, allowing hledger to catch more errors, and also makes the rate more explicit to human readers. We can write the total amount with @@ (convenient when entries are complex):

2021-07-27 give dollars, get euros
    assets:cash      USD -10.00 @@ EUR 8.50
    assets:cash      EUR   8.50

or the per-unit amount with @ (makes the exchange rate, or an investment's cost basis, clearer):

2021-07-27 give dollars, get euros
    assets:cash      USD -10.00 @ EUR 0.85
    assets:cash      EUR   8.50

hledger calls these "costs". This is the most common way to record such transactions.

Conversion using equity

A fully correct double-entry-bookkeeping journal entry avoids the PTA-specific @/@@ notation, and is balanced in each commodity, using equity. Eg:

2021-07-27 give dollars, get euros
    assets:cash        USD -10.00
    equity:conversion  USD  10.00
    assets:cash        EUR   8.50
    equity:conversion  EUR  -8.50

or, letting hledger infer the above:

2021-07-27 give dollars, get euros
    assets:cash        USD -10.00
    assets:cash        EUR   8.50
    equity:conversion

This is discussed more here.

Two entries for one conversion

This comes up eg when converting Paypal CSV, which provides two records for a currency conversion, one for each side. For example, given some paypal CSV rules like:

account1 assets:paypal
...
if %type (T0200|General Currency Conversion)
 description %type for %referencetxnid %itemtitle
 account2 equity:conversion

we might see journal entries like:

2020-05-16 * Liberapay donation to simonmichael (team darcs-hub)
    assets:online:paypal                     C$24.56 =* C$24.56
    revenues:foss donations:darcshub        C$-26.00
    expenses:banking:paypal                   C$1.44

2020-05-16 * General Currency Conversion for 8Y4N723T948333034 Liberapay donation to simonmichael (team darcs-hub)
    assets:online:paypal                    C$-24.56 =* C$0.00
    equity:conversion                        C$24.56

2020-05-16 * General Currency Conversion for 8Y4N723T948333034 Liberapay donation to simonmichael (team darcs-hub)
    assets:online:paypal                      $16.88 =* $17.55
    equity:conversion                        $-16.88

Ie: some canadian dollars received, followed by two transactions converting that balance to US dollars. This is equivalent to the "Fully balanced conversion" above, just with the conversion entry split into two.

Balancing the accounting equation

If you are a fan of the accounting equation and like to check it by seeing a zero total in the balancesheetequity report, you will need to do something with these equity:conversion balances. Such as, converting them to retained revenues/expenses in your local currency. This can perhaps be done at transaction time, or at the end of each accounting period, or with report options. Best practice is not yet clear, suggestions welcome.

Converting with equity or with costs

Here's another look at the equity vs costs style of conversion entries, somewhat repeating the content above.

In a currency conversion or a stock purchase/sale, one commodity is exchanged for another. In plain text accounting, there are two ways to record such conversions:

Equity method

Balance both commodities against an Equity account. Eg:

2021-01-01
  assets:usd                -1.20 USD
  equity:conversion          1.20 USD
  equity:conversion         -1.00 EUR
  assets:eur                 1.00 EUR

or, equivalently:

2021-01-01
  assets:usd                -1.20 USD
  assets:eur                 1.00 EUR
  equity:conversion

Cost method

PTA tools provide the @ (or @@) notation for specifying a conversion cost (essentially; Ledger/Beancount also provide an alternate {} notation):

2021-01-01
  assets:usd                -1.20 USD
  assets:eur                 1.00 EUR @ 1.20 USD

@-priced amounts (the 1.00 EUR above) will be converted to their price's commodity (USD)

  • internally for checking transaction balancedness, always
  • and visibly in reports, when the -B/--cost flag is used.

Note the redundancy in this entry; the two amounts and the @ price must agree. This provides some extra error checking, but you can also write it non-redundantly, by omitting an amount:

2021-01-01
  assets:usd                           ; the -1.20 USD amount is inferred
  assets:eur                 1.00 EUR @ 1.20 USD

or the conversion price:

2021-01-01
  assets:usd                -1.20 USD
  assets:eur                 1.00 EUR  ; the @ 1.20 USD price is inferred

Which is better ?

It depends...

Cost reporting

The @ (conversion price) method allows "cost" reporting. By adding the -B/--cost flag you can easily see what things cost (or were sold for) in the other commodity. Eg:

$ hledger -f 2a.j bal --cost assets:eur
            1.20 USD  assets:eur
--------------------
            1.20 USD  

Capital gain/loss reporting

The equity method keeps a trace of all commodity exchanges in the equity account, in effect properly recording the accumulated gain/loss from all commodity exchanges (it can be seen by valuing the accumulated total of those equity balances in some commodity).

The @ method does not record the gain/loss from commodity exchanges (at least, not so explicitly and not grouped by commodity pair. We can still calculate it using hledger valuation features like -V, --valuechange, --gain.)

The accounting equation

The equity method keeps accounts and the accounting equation (A+L+E=0) balanced. See how it keeps the balance report's total as zero:

$ hledger -f 1a.j bal
            1.00 EUR  assets:eur
           -1.20 USD  assets:usd
           -1.00 EUR
            1.20 USD  equity:conversion
--------------------
                   0  

The @ method causes unbalanced accounts and a non-zero total (because of the "magical" transformation from one commodity to the other):

$ hledger -f 2a.j bal
            1.00 EUR  assets:eur
           -1.20 USD  assets:usd
--------------------
            1.00 EUR
           -1.20 USD  

The zero total can be seen only if all amounts are converted to cost:

$ hledger -f 2a.j bal --cost
            1.20 USD  assets:eur
           -1.20 USD  assets:usd
--------------------
                   0  

Summary

The equity method:

  • doesn't support cost reporting.

The @ method:

  • doesn't support easy gain/loss reporting by commodity pair.
  • doesn't maintain balanced accounts

However, hledger nowadays can convert between these styles on the fly, using --infer-equity or (with some restrictions) --infer-costs. See the hledger manual > Cost reporting for the latest on this.