Skip to content

Generated file. Source: docs/annual_accumulation_logic.md Edit the source document and run npm run docs:sync to refresh this published copy.

Annual Accumulation Strategy Family

This file documents the shared annual-accumulation engine used by:

  • TOS
  • EDO
  • ROS
  • ROD

These bonds:

  • capitalize interest yearly inside the active batch
  • do not pay coupons during the cycle
  • settle tax only on redemption
  • may be rolled into the next cycle after natural maturity
  • may end with final early redemption if the simulation horizon stops mid-cycle

Status

  • Shared engine status: active in production code
  • Code path:
  • annual-accumulation.ts: src/app/domain/bond-engine/strategies/annual-accumulation.ts

Shared Responsibilities

The shared engine is responsible for:

  • initial purchase of full bonds
  • yearly bond-value growth inside the active batch
  • monthly accrual of the residual cash account between yearly checkpoints (interest accrual zero for capitalizing bonds)
  • natural redemption handling
  • exchange-driven rollover after maturity using bond.exchangePrice
  • transfer of leftover redemption cash to the residual cash account
  • final early-redemption settlement
  • yearly snapshots and public yearly results
  • purchase and redemption event ledgers

Specializations define:

  • strategyId
  • cycle length in years
  • annual rate resolution policy
  • whether to accrue interest on the residual cash account (determined by bond.capitalization)

Shared State

The engine tracks:

  • activeBondCount
  • capitalBase
  • nominalValue
  • cycleYear
  • cashAccountBalance
  • purchaseEvents
  • redemptionEvents
  • yearSnapshots
  • yearlyResults

Important modeling rules:

  • this family does not keep a free reinvestment pool during the cycle
  • any residual amount that cannot buy a full bond after rollover is stored on the residual cash account
  • that account accrues monthly interest and monthly tax
  • for capitalizing bonds (where bond.capitalization !== 'none'), the residual cash account does not earn interest; it only accumulates leftover redemption residues after rollovers
  • for non-capitalizing bonds, the residual cash account accrues interest at the configured annual rate

Year Loop

For each year:

  1. Accrue the residual cash account for 12 months.
  2. Resolve the annual bond rate for the active cycle year.
  3. Compute the gross value of the active batch after yearly capitalization.
  4. Compute the liquidation checkpoint for that year.
  5. If the batch reached natural maturity:
  6. calculate redemption tax
  7. redeem the whole batch
  8. if the horizon continues, buy the next batch at exchangePrice
  9. move the leftover redemption residue to the residual cash account
  10. If the horizon ends before maturity:
  11. apply final early redemption
  12. reduce the taxable profit basis by the early-redemption fee before tax
  13. Build the yearly snapshot and yearly public result.

Natural Redemption And Rollover

Natural maturity settlement uses:

grossProfit = grossBondValue - nominalValue
tax = grossProfit * 0.19
liquidationBondValue = grossBondValue - tax

If the simulation continues past maturity:

nextBondCount = floor(liquidationBondValue / exchangePrice)
nextPurchaseValue = nextBondCount * exchangePrice
transferToAccount = liquidationBondValue - nextPurchaseValue

Then:

  • nextBondCount becomes the new active batch
  • the nominal base of the new batch is nextBondCount * 100
  • transferToAccount is added to the residual cash account

This is the mechanism that aligns the family with the workbook after the first maturity.

Final Early Redemption

If the investment horizon ends before the cycle matures:

boundedEarlyCost = min(max(0, grossBondValue - nominalValue), fullEarlyRedemptionCost)
taxableProfit = grossBondValue - nominalValue - boundedEarlyCost
tax = taxableProfit * 0.19
netLiquidation = grossBondValue - boundedEarlyCost - tax

Tax is therefore calculated after reducing the taxable profit basis by the realized early-redemption cost.

Snapshot Semantics

Intermediate years

  • grossValue = bondValue + cashAccountBalance
  • netValue = carried portfolio value, not forced liquidation
  • cash = 0 in this family
  • cashAccountBalance = workbook-style residual account that may already include prior rollover residue and accrued account interest

Final year

  • grossValue = final checkpoint before final-year taxes and early-redemption fees
  • netValue = actual liquidation value for the chosen horizon
  • bondValue = liquidated bond component visible in the final public result

Invariants

The shared engine should always satisfy:

  • grossValue >= liquidationValue
  • cash = 0
  • cashAccountBalance >= 0
  • final public gross/net totals match the last yearly snapshot after display rounding
  • rollover never buys fractional bonds

Mermaid Overview

flowchart TD
    A[Initial capital] --> B[Buy initial full bonds]
    B --> C[Residual amount goes to cash account]
    C --> D[Year loop]
    D --> E[Accrue cash account monthly]
    E --> F[Capitalize active batch yearly]
    F --> G{Natural maturity?}
    G -->|No| H{Final year?}
    G -->|Yes| I[Redeem batch and calculate tax]
    I --> J{More years left?}
    J -->|Yes| K[Buy next batch at exchangePrice]
    K --> L[Move residue to cash account]
    L --> D
    J -->|No| M[Build final result]
    H -->|No| D
    H -->|Yes| N[Final early redemption]
    N --> M

Shared Tests

Current test paths:

  • annual-accumulation.strategy.test.ts: tests/annual-accumulation.strategy.test.ts
  • excel-accumulation-alignment.test.ts: tests/excel-accumulation-alignment.test.ts

Shared-engine tests should validate:

  • invariant grids
  • final total consistency
  • purchase and redemption history retention
  • residual cash-account behavior after rollover
  • Excel alignment for TOS, EDO, ROS, and ROD

Change Rules

If the shared annual-accumulation engine changes, update together:

  • annual-accumulation.ts: src/app/domain/bond-engine/strategies/annual-accumulation.ts
  • all specialization docs and tests that depend on it
  • annual_accumulation_logic.md