Use multi-currency Ledgers to easily build products that track currencies, stocks and inventories.
A Ledger can contain Ledger Accounts of different currencies, like USD and GBP bank accounts. Ledger Accounts can also be multi-currency, like one representing a stock portfolio, with a balance for each symbol.
You can post Ledger Entries with multiple currencies. It must follow the Accounting Equation per currency, so you'll need at least four Ledger Lines.
FRAGMENT includes a list of common currencies. You can also add your own custom currencies
Multi-currency ledgers often reflect transitory states: a company accepts payment in one currency intending to convert it to another currency. Between accepting and converting the money, the exchange rate could change. Tracking the potential gain or loss from this change is called exposure.
To track exposure, use a Change Ledger Account that has multiple balances, one for each currency. Here's an example that tracks exposure between USD and EUR:
In this example,
To create a multi-currency Ledger, set defaultCurrencyMode
to multi
and unset defaultCurrency
:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{...},
{...}
]
},
"ledgerEntries": {...}
}
For Ledger Accounts in a single currency, such as bank accounts, set currencyMode
and currency
on the account directly:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{
"key": "bank-account",
"currencyMode": "single",
"currency": {
"code": "USD"
}
},
{...}
]
},
"ledgerEntries": {...}
}
Like other Ledger Account properties, currencyMode
and currency
are inherited by child Ledger Accounts unless they are overridden.
You can define multi-currency Ledger Entries types in your Schema in the same way as single-currency Ledger Entries.
Multi-currency Ledger Accounts accept Ledger Lines in any currency, so Ledger Entries that post to them must specify the currency of each Ledger Line:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{
"type": "asset",
"key": "bank-account"
},
{...}
]
},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account_usd",
"description": "Fund {{funding_amount}} USD",
"lines": [
{
"key": "funds_arrive_in_bank",
"account": {
"path": "bank-account"
},
"amount": "{{funding_amount}}",
"currency": {
"code": "USD"
}
},
{...other line}
]
}
]
}
}
You can parameterize currency
to make your Ledger Entries more reusable:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{
"type": "asset",
"key": "bank-account"
},
{...}
]
},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account",
"description": "Fund {{funding_amount}} {{currency}}",
"lines": [
{
"key": "funds_arrive_in_bank",
"account": {
"path": "bank-account"
},
"amount": "{{funding_amount}}",
"currency": {
"code": "{{currency}}"
}
},
{...other line}
]
}
]
}
}
You can also post multi-currency Ledger Entries to Ledger Account templates which parameterize currency
. This is useful for creating linked Ledger Accounts if you have multiple bank accounts in different currencies in a multi-currency Ledger.
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{
"key": "bank-accounts",
"template": true,
"currencyMode": "single",
"currency": {
"code": "{{currency}}"
}
},
{...}
]
},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account",
"description": "Fund {{funding_amount}} {{currency}}",
"lines": [
{
"key": "funds_arrive_in_bank",
"account": {
"path": "bank-accounts:{{currency}}"
},
"amount": "{{funding_amount}}",
"currency": {
"code": "{{currency}}"
}
},
{...other line}
]
}
]
}
}
Ledger Entry Conditions against multi-currency Ledger Accounts need to specify the currency the condition applies to:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{...},
{...}
]
},
"ledgerEntries": {
"types": [
{
"type": "p2p_transfer",
"description": "Move {{funding_amount}} {{currency}}",
"lines": [
{...},
{...}
],
"conditions": [
{
"account": {
"path": "liabilities/users:{{from_user_id}}"
},
"currency": {
"code": "{{currency}}"
},
"postcondition": {
"ownBalance": {
"gte": "0"
}
}
}
]
}
]
}
}
Balances on multi-currency Ledger Accounts are lists of currency and amount, as opposed to just a single amount. They support all the same balance queries as single-currency accounts. You can either:
balances
instead of balance
) to query all currencies at oncecurrency
argument to query a specific currencyTo read a specific currency's balance, pass in the currency
argument:
query GetUSDBalance(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
ownBalance(currency: { code: USD })
}
}
{
"ledgerAccount": {
"path": "assets/banks/user-cash",
"ledger": {
"ik": "multi-currency-ledger"
}
}
}
To read all the balances for a multi-currency Ledger Account, use ownBalances
:
query GetAllBalances(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
ownBalances {
nodes {
currency {
code
}
amount
}
}
}
}
To read aggregated balances for multi-currency Ledger Accounts, pass in the currency
argument, or query childBalances
and balances
:
query GetBalances(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
# Query specific currency
childBalance(currency: { code: USD })
balance(currency: { code: USD })
# Query all currencies
childBalances {
nodes {
currency {
code
}
amount
}
}
balances {
nodes {
currency {
code
}
amount
}
}
}
}
If a Ledger Account has a descendant that is a multi-currency Ledger Account or if it has descendants of different currencies, it has childBalances
and balances
.
Balance reads are eventually consistent by default. To read consistent balances for multi-currency Ledger Accounts, pass in the consistencyMode
argument:
query GetOwnBalances(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
# Query specific currency
ownBalance(
consistencyMode: strong
currency: { code: USD }
)
# Query all currencies
ownBalances(consistencyMode: strong) {
nodes {
currency {
code
}
amount
}
}
}
}
consistencyMode
can be set to:
strong
to perform a strongly consistent balance readeventual
to perform an eventually consistent balance readuse_account
to use the Schema value of the Ledger Account's consistencyConfig.ownBalanceUpdates
settingOnly ownBalance
and ownBalances
can be queried with consistencyMode: strong
.
To query the balance of a Ledger Account at a particular point in time, use the at
argument:
query GetHistoricalBalances(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
# Query specific currency
end_of_year: balance(
at: "1969"
currency: { code: USD }
)
end_of_month: balance(
at: "1969-07"
currency: { code: USD }
)
# Query all currencies
end_of_year_all: balances(at: "1969") {
nodes {
currency {
code
}
amount
}
}
end_of_month_all: balances(at: "1969-07") {
nodes {
currency {
code
}
amount
}
}
}
}
Multi-currency Ledger Accounts support reading the net change over a period:
ownBalanceChanges
, how much ownBalances
changedchildBalanceChanges
, how much childBalances
changedbalanceChanges
, how much balances
changedquery GetBalanceChanges(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
# Query specific currency
ownBalanceChange(
period: "1969"
currency: { code: USD }
)
# Query all currencies
ownBalanceChanges(period: "1969") {
nodes {
currency {
code
}
amount
}
}
}
}
Balance change queries require you to specify a period
. This can be a year, quarter, month, day or hour.
Use balancesDuring
and balanceChangesDuring
to track balances and balance changes over time. For multi-currency accounts, you can query either a specific currency or all currencies:
query GetBalanceHistory(
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
# Query specific currency
usdBalances: balancesDuring(
startTime: "2021"
duration: 12
granularity: monthly
currency: { code: USD }
) {
nodes {
at
amount {
amount
}
}
}
# Query all currencies
allBalances: balancesDuring(
startTime: "2021"
duration: 12
granularity: monthly
) {
nodes {
at
amount {
currency {
code
}
amount
}
}
}
# Query specific currency changes
usdChanges: balanceChangesDuring(
startTime: "2021"
duration: 100
granularity: daily
currency: { code: USD }
) {
nodes {
period
amount {
amount
}
}
}
# Query all currency changes
allChanges: balanceChangesDuring(
startTime: "2021"
duration: 100
granularity: daily
) {
nodes {
period
amount {
currency {
code
}
amount
}
}
}
}
}
You can define your own currencies to track any type of value, like rewards points, stocks or physical items.
To create a custom currency, call the createCustomCurrency
mutation:
mutation CreateCustomCurrency (
$customCurrency: CreateCustomCurrencyInput!,
) {
createCustomCurrency(
customCurrency: $customCurrency
) {
... on CreateCustomCurrencyResult {
customCurrency {
code
customCurrencyId
precision
name
customCode
}
}
... on Error {
code
message
}
}
}
{
"customCurrency": {
"customCurrencyId": "blue-gems",
"precision": 0,
"name": "Blue Gems",
"customCode": "BLUE"
}
}
To use a custom currency, set the customCurrencyId
on the currency
field of a Ledger Account and Ledger Line:
{
"key": "...",
"chartOfAccounts": {
"defaultCurrencyMode": "multi",
"accounts": [
{
"key": "gems-issued",
"currencyMode": "single",
"currency": {
"code": "CUSTOM",
"customCurrencyId": "blue-gems"
}
},
{...}
]
},
"ledgerEntries": {
"types": [
{
"type": "issue-blue-gems",
"description": "Issue blue gems",
"lines": [
{
"key": "increase-pool",
"account": {
"path": "gems-issued"
},
"amount": "{{amount}}",
"currency": {
"code": "CUSTOM",
"customCurrencyId": "blue-gems"
}
},
{...other line}
]
}
]
}
}