MassiveGL FX Rates
One of the things that breaks down quickly in real-world accounting systems is currency.
Most ledger designs treat currency as an afterthought — a field on a transaction, a conversion applied at reporting time, a problem deferred to the spreadsheet team. MGL treats it differently. Exchange rates are a first-class part of the data model, imported automatically, stored with full history, and wired directly into the layer architecture so that multi-currency consolidation happens at query time, not at batch time.
What the demo shows
The short demo above walks through the FX module from the perspective of someone setting up and operating a live system:
- log in and navigate to the FX Rates page
- browse all stored rates across every currency pair in the system
- filter by pair — USD/UYU, EUR/USD, EUR/UYU — and see the rate history chart for each
- switch to the posting console to see how layers and FX rates work together
It is a deliberately short tour. The interesting part is not the UI — it is what the UI is sitting on top of.
Rates, stored and queryable
MGL imports rates from two sources out of the box: the Frankfurter API, which sources data from the European Central Bank, and the Banco Central del Uruguay for USD/UYU and EUR/UYU pairs. Both importers run continuously in the background, backfilling historical data and polling for new rates at a configurable interval.
Each rate is stored with its currency pair, date, rate value, and source identifier. The unique constraint on (pair, date) means upserts are atomic and idempotent — you can run importers in parallel without creating duplicates or races.
The most recent stored date is always available to each importer, so if the system goes offline for a few days it will automatically resume backfilling from where it left off, without manual intervention.
Adding a new rate source is also straightforward: implement a QBean that calls FXRateManager.upsert() with your pair, date, rate, and a source identifier. The rest of the system picks it up automatically.
Where the rates actually get used
The real reason to care about FX rates in MGL is virtual layers.
Physical layers in a journal hold actual posted entries — transactions with real debit and credit amounts in a specific currency. Virtual layers hold no entries. Instead, they carry a formula that computes a balance on demand from other layers, and that formula can invoke fx() to convert amounts using a rate from the database.
So if a journal has three physical layers — layer 0 for Uruguayan pesos, layer 840 for US dollars, layer 978 for euros — a single virtual layer can consolidate all three into a reporting currency:
L0 + fx(L840, "USD/UYU") + fx(L978, "EUR/UYU")
When you query that virtual layer's balance for a given date, the engine reads the balances of the three physical layers, looks up the exchange rates for that date, applies the conversions, and returns the sum. The original postings are never touched. The ledger stays clean. You get a consolidated balance that reflects the actual exchange rates in effect on any historical date you care to query.
This matters for two reasons. First, it removes the need for re-valuation entries or synthetic postings whenever rates change. Second, it means you can go back to any point in time and get an accurate consolidated view using the rates that were actually in effect that day, not today's rates applied retroactively.
Inverse pairs come for free
One small but practical detail: if you store a rate for USD/UYU, the engine automatically makes UYU/USD available as 1 / rate. You only need to store rates in one direction. The inverse lookup is handled in the DBFXProvider, which is the component that GLSession uses when evaluating virtual layer formulas.
Reliable, not magical
There is nothing glamorous about exchange rate management. But it is the kind of thing that quietly breaks accounting systems that were not designed for it from the start.
MGL's approach is simple: import rates from authoritative sources, store them with full history, handle gaps by falling back to the most recent available rate, and make them directly addressable from the layer formula language. No spreadsheets, no manual lookups, no batch jobs that need to run before month-end close.
More to come as the virtual layer documentation and formula reference take shape.
