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.

Status Applied Change ID 2026-07-22-hifo-relief Spec 005-tax-lots Author Brian Corbin · @briancorbinxyz Created Targets 3 requirements Read time

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

Applied

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 MUST relieve open lots by the configured policy — FIFO (oldest acquired first), LIFO (newest first), or HIFO (highest cost basis first) — MUST split the boundary lot on partial consumption, and MUST compute realized gain as proceeds minus relieved basis.

What changes: policy selection extends to three values.

Relief policy (FIFO, LIFO, or HIFO) MUST be selectable per account (or per instrument), MUST be fixed at relief time, and MUST be recorded on the realized-gain record for auditability.

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)"

Mitigated: the scan is bounded by the same ≤ 1000-open-lots envelope NFR-001 already assumes; ~1000 integer comparisons sit well inside the 40 µs budget. No SLO change; the reconcile and latency tests cover HIFO on the same envelope.

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."

Mitigated: the deferral is deliberately narrowed, not violated — HIFO is promoted in; specific-lot and average-cost remain deferred under the same marker (see §2 non-goals).

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"

Mitigated: scope is held to HIFO only. The other two policies stay explicitly out of scope; HIFO is the one desks ask for by name.

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

  1. Proposal authored — HIFO relief policy, promoted from TBD-hifo-specific-lot; three modified requirements (FR-002, FR-003, SC-001); adversarial risk pass dispositioned.
  2. 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..