Generated file. Source:
docs/annual_accumulation_logic.mdEdit the source document and runnpm run docs:syncto refresh this published copy.
Annual Accumulation Strategy Family¶
This file documents the shared annual-accumulation engine used by:
TOSEDOROSROD
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:
activeBondCountcapitalBasenominalValuecycleYearcashAccountBalancepurchaseEventsredemptionEventsyearSnapshotsyearlyResults
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:
- Accrue the residual cash account for
12months. - Resolve the annual bond rate for the active cycle year.
- Compute the gross value of the active batch after yearly capitalization.
- Compute the liquidation checkpoint for that year.
- If the batch reached natural maturity:
- calculate redemption tax
- redeem the whole batch
- if the horizon continues, buy the next batch at
exchangePrice - move the leftover redemption residue to the residual cash account
- If the horizon ends before maturity:
- apply final early redemption
- reduce the taxable profit basis by the early-redemption fee before tax
- 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:
nextBondCountbecomes the new active batch- the nominal base of the new batch is
nextBondCount * 100 transferToAccountis 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 + cashAccountBalancenetValue= carried portfolio value, not forced liquidationcash=0in this familycashAccountBalance= 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 feesnetValue= actual liquidation value for the chosen horizonbondValue= liquidated bond component visible in the final public result
Invariants¶
The shared engine should always satisfy:
grossValue >= liquidationValuecash = 0cashAccountBalance >= 0- final public
gross/nettotals 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.tsexcel-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, andROD
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