Fintech
A year of trading into a tax position you can defend
Broker statements disagree with each other and with the return. Producing the number is the easy half; being able to show how it was derived is the product.
Filing a return against a year of trading looks like arithmetic. Add the gains, subtract the losses, carry the remainder forward.
It is not arithmetic, because the inputs do not agree. Broker statements disagree with each other on format, on which charges are included in a price, and on how they describe the same event. None of them are written to answer the question the return asks. Reconstructing a defensible position from them is the entire product, and the number at the end is the least of it.
The inputs are not a ledger
A contract note is a record of an execution. A tax computation needs a record of a disposal, matched against an acquisition, with a cost that includes the right charges and excludes the wrong ones.
Turning the first into the second is where the work is:
- Charges are not uniform. Some are part of the cost of acquisition, some are deductible against consideration, and some are neither and simply reduce the account balance. Treating the total debited as the cost is the most common error and it is always in the taxpayer's disfavour.
- Instruments are classified differently. The same underlying traded as delivery, intraday or as a derivative is treated as a different kind of income, with different rules about what it can be set against. A ledger that stores only symbol, quantity and price cannot make that distinction later.
- Statements are reissued. A corrected statement arriving after filing is not an edge case, and the system needs to be able to show what changed rather than quietly holding a different number than the one filed.
Corporate actions rewrite the past
A split, bonus or merger changes the quantity held and the cost per unit of a position acquired years earlier. The acquisition did not change; its representation did.
This is the point where a naive design fails permanently. If cost basis is stored as a single figure per holding and mutated when an action occurs, the original is gone, and there is no way to demonstrate afterwards how the current figure was reached. The action has to be an event applied over an immutable acquisition record, so the derivation can be replayed.
That matters because the question at assessment is never what is the number. It is how did you get it, asked about a specific line, possibly two years later.
The matching rule is prescribed, not chosen
Which acquisition a disposal is matched against is determined by rule, not by preference, and the rule that applies is the one in force for that year.
So the rule cannot be a constant in the code. It is a versioned thing the computation selects by period, because a system that lives for several years will eventually compute two years under two different regimes — and will be asked to recompute an old year on the old basis after the new one has shipped.
The same is true of loss treatment: which category a loss falls into, what it may be set against, and how long it survives are all prescribed, all periodic, and all subject to change. Encoding this year's version as the only version is the bug that surfaces exactly once and expensively.
Carry-forward is a state machine
A loss is not a number carried in a field. It has an origin year, a category, a remaining balance and a life. It is consumed partially, in a prescribed order, against particular kinds of gain.
Which means the system has to track not just the surviving balance but the history of what was consumed against what, in which year. Without that, the first year in which a prior loss is partially used produces a figure nobody can explain, and the taxpayer is the one asked to explain it.
Reconciliation is the correctness check
There is one property worth building the whole computation around: at the end of the period, the reconstructed holdings should equal the actual holdings.
If the ledger says a position of 400 and the broker says 250, something is missing — an unmatched disposal, a corporate action not applied, a statement not ingested. That single check catches most classes of error before they reach a return, and it is cheap.
Systems that skip it can produce a confident number from incomplete data, which is the worst available outcome, because nothing about the output signals that it is wrong.
Defensibility is the deliverable
The liability sits with the taxpayer, not the software. A return filed on a wrong gain is their exposure, and it surfaces at assessment when the deadline has long passed.
So the output that matters is not the figure. It is the trail: this disposal, matched to this acquisition, under this year's rule, with these charges included and these excluded, adjusted by this corporate action. A computation that cannot show its work is not finished, however correct it happens to be.
Recognise the problem?
If this describes where you are, the first conversation is usually short and tells you whether we are useful.