Change proposal · 005-tax-lots
Add HIFO relief policy
A third lot-relief policy — Highest In, First Out — for tax-loss harvesting, promoted from deferred scope.
Add HIFO (Highest In, First Out) as a third relief policy alongside FIFO and LIFO — it relieves the
highest-cost-basis lot first, minimising realised gain for tax-loss harvesting. HIFO was parked at
TBD-hifo-specific-lot; this change promotes it in and narrows the deferral to specific-lot and
average-cost. It touches FR-002 and FR-003 (the policy set) and SC-001 (the worked example), and it carries one real
design consequence: HIFO is not a deque-end pop like FIFO/LIFO, so lot selection generalises.
Add HIFO relief policy
1 · Intent
FIFO and LIFO answer "which shares did I sell" by acquisition order; HIFO answers it by cost — relieve
the most expensive lots first, so a sale realises the smallest gain (or the largest loss). It is the standard tax-loss-harvesting
policy, and desks expect it next to FIFO/LIFO. The spec already anticipated it: it sits in the out-of-scope register as
TBD-hifo-specific-lot. This change pulls HIFO in now while leaving specific-lot and average-cost deferred.
2 · Scope
In scope
- HIFO as a selectable relief policy (per account), fixed at relief time and recorded on each realised-gain record.
- Generalising lot selection so relief picks the policy-preferred lot — oldest (FIFO), newest (LIFO), or highest-basis (HIFO).
- A distinguishing worked example in SC-001 where HIFO differs from both FIFO and LIFO.
Out of scope
- Specific-lot and average-cost relief — they stay deferred under
TBD-hifo-specific-lot. - Any change to the latency, reconciliation, or bounded-lot SLOs (NFR-001…003) — HIFO reuses the same envelope.
3 · Approach
FIFO and LIFO are pop-from-an-end operations on the acquisition-ordered ArrayDeque (design D-001). HIFO is not:
it must find the lot with the greatest basisPerUnit, which can sit anywhere in the deque. So relief generalises
from "peek/poll the head or tail" to "select the policy-preferred lot, then relieve and possibly split it".
FIFO/LIFO stay O(1) end operations; HIFO becomes an O(open-lots) scan — bounded by the same ≤ 1000-open-lots envelope
NFR-001 already assumes, so the relief-latency SLO is unaffected. The deque still holds lots in acquisition order (the
audit record's holding period depends on it); HIFO simply removes an interior element. The design gains a decision (D-004)
recording this generalisation.
Everything downstream is unchanged: partial-lot split, exact integer realised gain (qty·(sellPrice − basis)),
the immutable audit record, and reconcile-to-replay all apply to HIFO verbatim — only the selection of which lot to
relieve is new.
4 · Deltas
Three requirements touched — the policy set (FR-002, FR-003) and the worked example (SC-001). Each
<spec-delta> embeds its full post-state.
What changes: HIFO joins FIFO and LIFO in the relief-policy set, selected by highest cost basis.
On a sell the slice
What changes: policy selection extends to three values.
Relief policy (FIFO, LIFO, or HIFO)
What changes: the worked example gains a three-lot case that distinguishes HIFO from FIFO and LIFO.
Buy 100 @ 10 then 100 @ 12 and sell 150: under FIFO realized gain relieves the first lot fully and 50 of the second (basis 100·10 + 50·12), leaving a 50-share open lot @ 12; under LIFO it relieves the second fully and 50 of the first. And for a non-monotonic book — buy 100 @ 10, 100 @ 14, 100 @ 12, sell 150 — HIFO relieves the two costliest lots first: 100 @ 14 fully and 50 @ 12 (relieved basis 100·14 + 50·12 = 2000), leaving 50 @ 12 and 100 @ 10 open and realizing the smallest gain of the three policies. Each is computed to the cent and matches a full replay.
5 · Risk register
The change touches a must-tier requirement, so an adversarial risk pass ran. Each finding is dispositioned.
HIFO's highest-basis lookup is an O(open-lots) scan, unlike FIFO/LIFO's O(1) deque-end pop — it could threaten the relief-latency SLO (NFR-001, p99 < 40 µs at ≤ 1000 open lots).
"or HIFO (highest cost basis first)"
The live spec's out-of-scope register defers HIFO (TBD-hifo-specific-lot); adding it here contradicts that deferral.
out-of-scope: "Additional relief policies (HIFO, specific-lot, average-cost) beyond FIFO/LIFO."
Scope risk: pulling in HIFO could drag along the rest of the deferred policy family (specific-lot, average-cost).
§2 In scope — "HIFO as a selectable relief policy"
6 · Tasks
Scoped to this change. On apply these fold into tasks.html as a new provenance-linked phase that
/spectastic.implement drains.
- Add a HIFO worked-example test — buy 100@10, 100@14, 100@12; sell 150 → relieved basis 2000, leaving 50@12 and 100@10; assert HIFO differs from FIFO and LIFO, to the cent src/test/java/io/spectastic/portfolio/TaxLotBookTest.java
- Add HIFO to the policy enum src/main/java/io/spectastic/portfolio/tax/ReliefPolicy.java
- Generalise lot selection — relief picks the policy-preferred lot (FIFO first, LIFO last, HIFO highest basis) and relieves/splits it in place src/main/java/io/spectastic/portfolio/tax/TaxLotBook.java
7 · Open questions
None outstanding — the policy semantics (highest basis first), the per-account selection reusing the existing mechanism, and the scan-based selection bounded by the current lot envelope were all settled; specific-lot and average-cost are explicitly deferred, not open.
8 · Change log
- Proposal authored — HIFO relief policy, promoted from
TBD-hifo-specific-lot; three modified requirements (FR-002, FR-003, SC-001); adversarial risk pass dispositioned. - Applied on 22 Jul 2026 — Applied 2026-07-22-hifo-relief: added HIFO (highest-basis-first) relief policy alongside FIFO/LIFO — FR-002/FR-003 extended, SC-001 gains a distinguishing three-lot example; HIFO promoted from TBD-hifo-specific-lot..