Currencies and exchange rates
Currencies, market and own rates, and the daily update.
Stored money never changes with rates. Each entry stays in its account’s currency, and a cross-currency transfer stores both sides as they happened. Rates are only used to show totals in the book’s currency, so changing a rate re-prices reports, never history.
currencies: ISO code and decimals (USD 2, JPY 0, KWD 3), needed because amounts are stored in minor units. All 155 currencies in use from the ISO 4217 list (20261020000100_iso_4217_currencies.sql); fund codes, precious metals, SDR and test codes are left out (default: nobody keeps an account in them). Rates for codes not in the table (crypto, for example) are dropped by the daily job.fx_rates: market rates shared by everyone on the server, one row per currency per day, quoted against USD. Any pair is derived through USD. Daily history means a past month’s report uses that month’s rate.book_fx_rates: a user’s own rate for one pair in one book, from a date onward. It always wins over the market rate in that book, and works in both directions.books.auto_fx(on by default): use market rates where there’s no own rate. When off, only own rates apply. It’s a per-book choice, not a paid feature.book_rate()picks the rate: own rate, else market rate ifauto_fx, else unknown. Totals report how many amounts had no rate rather than showing a wrong number.- Changing a book’s currency changes the default for new accounts and the currency totals are shown in, and converts its budgets at today’s rate (refused if no rate is known).
Daily update: backend/app/jobs/fx_update.py, one job per server, logged in as fx_updater, run by the scheduler (see Background jobs). One request a day covers every user. Re-running it is harmless.
Providers. By default open.er-api.com (ExchangeRate-API’s open endpoint): no key, daily, about 160 currencies including VND, quoted against USD as fx_rates is. Its terms allow personal and commercial use and keeping the data, forbid passing it on, and ask for the credit “Rates By Exchange Rate API”: the dashboard shows it, linked, when a book has accounts in another currency and the newest rates came from there (rates_source in the dashboard’s answer). Each server fetches rates for its own users; the project ships none. If it fails, or announces its end (time_eol_unix), the job uses fawazahmed0’s currency-api instead: public domain (CC0), no key, about 300 currencies, from jsDelivr with Cloudflare Pages as its own fallback. fx_rates.source records which one each rate came from. An operator who sets OXR_APP_ID gets Open Exchange Rates on their own key instead. Adding a provider means changing only fetch_rates().