The upcoming API 0.0.8 release targets Global Units, Custom Units, and a new Price object that bridges them. It is forcing me to consider some difficult questions.
Amatino’s double-entry data model adopts a ‘quantum mechanics’ approach: Transactions are the atomic units of all financial information, but they themselves are composed of the more fundamental Entry object, with which we never directly interact. This approach allows financial data to be stored as arbitrary precision numbers in their native denomination.
It also means that the data model does not inherently satisfy the double-entry accounting equation. If external constraints are not applied, it would be possible to store an unbalanced Transaction: The composing Entries may not add up to zero, and the equation would not hold.
Obviously, Amatino is designed with stringent constraints. Attempt to insert an unbalanced Transaction and the digits ‘400’ will present themselves quick smart. But what if a Transaction balance is changed after it is inserted?
How could this happen? It is a matter of relativity. Say you create a Transaction (TX) worth 1 U.S. Dollar (USD) with Entries party to two Accounts: One denominated in USD, and one denominated in Woozlewozzles (WZL). WZL being some Custom Unit you have fed to Amatino.
Perhaps TX is created on 1 Jan 2018, dated at 1 Jan 2018, and at that time, the last WZL / USD price you provided was for 31 Dec 2017, at 2 WZL = 1 USD. Amatino will eat TX and store one Entry for 1 USD, and one Entry for 2 WZL.
Now suppose that on 2 Jan 2018, you have access to new price data. You supply Amatino with a 1 Jan 2018 WZL / USD price of 1 WZL = 1 USD. Ceteris paribus, TX is now unbalanced: Attempt to retrieve TX and Amatino will compute output using the price with time closest-to-but-less-than TX time: 1 Jan 2018.
That yields a WZL entry with double the value that it had at Transaction entry. The double-entry equation is violated and the universe (presumably) implodes.
What’s the solution? The two I am working with right now are:
When creating a Transaction, a client may specify some (but not all) constituent Entries as being ‘anchors’. When a new Price is inserted, Amatino looks for any Transactions with Entries party to an Account denominated in the Priced unit, with an effective time less than the next most recent Price, and greater-than-or-equal to the new Price.
Amatino then calculates the difference between the old balance and the new balance based on the new Price. That difference is distributed proportionally across any (or the only) Entry that is not marked as being an anchor.
One advantage of Anchors is that they would be compatible with future-dated Transactions with heterogeneous Global Unit denominations.
2. Blind Repair
Similarly to solution (1), Blind Repair would look for any Transactions affected by a new Price. Instead of applying the difference in balance to specially identified Entries, Amatino applies the difference to any Entry in the Priced unit.
One advantage of Blind Repair is that it does not add new complexity to the Transaction model. Anchors would require implicit or explicit decision making on every Transaction entry.
Blind Repair would not be compatible with future-dated Transactions with heterogenous Global Unit denominations. Global Unit prices are updated metronomically at ~1800 UTC each trading day. If a Transaction contained Entries party to say, USD and Euros, how would Blind Repair choose which Entries to adjust? I don’t see a way to make the process deterministic.
What to do?
Right now, I am leaning towards choosing Blind Repair. I would combine it with a new restriction: Transactions cannot be future-dated when they are denominated in heterogenous units where one of those units is a Global Unit.
It will be frustrating to be unable to conveniently future-date known upcoming or recurring Transactions with such denominations. However, I think the increased complexity of Anchors means they are not a viable alternative.
Have thoughts on a third way? Let me know in the comments!