Skip to content

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

EDO Logic

This file documents the current EDO calculation engine implementation.

Status

  • Scope: current EDO engine and the shared annual-accumulation strategy family it uses
  • Implementation status: active in production code
  • Code path:
  • annual-accumulation.ts: src/app/domain/bond-engine/strategies/annual-accumulation.ts
  • edo.ts: src/app/domain/bond-engine/strategies/edo.ts
  • Test path:
  • annual-accumulation.strategy.test.ts: tests/annual-accumulation.strategy.test.ts
  • edo.strategy.test.ts: tests/edo.strategy.test.ts
  • Last reviewed against code: 2026-03-23

Product Rules

  • Bond: EDO
  • Duration: 10 years
  • Year 1 rate: 5.60%
  • Years 2-10 rate: inflation + 2.00%
  • Capitalization: yearly
  • Interest payout: only on redemption
  • Early redemption cost: 3 zł per bond
  • Bond unit price: 100 zł
  • Tax rate: 19%

Strategy Family

EDO does not use the generic yearly calculator anymore.

It is implemented as a specialization of the shared annual-accumulation family for bonds that:

  • capitalize annually
  • do not pay cash during the cycle
  • allow reinvestment only after redemption
  • may end with early redemption of unfinished batches

That same family is already used by:

  • TOS

and is intended to be extended with:

  • ROS
  • ROD

EDO Specialization

EDO configures the shared engine with:

  • strategyId = annual-accumulation-edo
  • cycleLengthYears = 10
  • rate resolver:
  • cycleYear === 1 -> 5.60%
  • cycleYear >= 2 -> inflation + 2.00%

So the EDO module contains only product-specific rate policy, while yearly batch mechanics remain shared.

State Model

The engine tracks:

  • cash
  • positions
  • purchaseEvents
  • redemptionEvents
  • yearSnapshots
  • yearlyResults

Each active position contains:

  • bonds
  • purchaseYear

Simulation Flow

The engine works on yearly steps.

For each year:

  1. Redeem positions that reached natural maturity (10 years).
  2. Add redeemed net cash to cash.
  3. Reinvest available cash into full new bonds if the horizon continues.
  4. If this is the final year, early-redeem all still-active positions.
  5. Build yearly carried-value and liquidation snapshots.
flowchart TD
    A[Initial cash] --> B[Buy initial full bonds]
    B --> C[Year loop]
    C --> D{Any batch reached 10 years?}
    D -->|Yes| E[Natural redemption + tax]
    D -->|No| F[Keep active batches]
    E --> G[Add net cash]
    F --> G
    G --> H{More years left?}
    H -->|Yes| I[Buy new full bonds]
    H -->|No| J[Final settlement]
    I --> C
    J --> K[Early redeem unfinished batches]
    K --> L[Build final result]

Natural Redemption

For a naturally redeemed batch:

  1. Start with nominal principal.
  2. Apply yearly compounding for 10 years:
V1 = P * (1 + 0.056)
V2 = V1 * (1 + inflation + 0.02)
...
V10 = V9 * (1 + inflation + 0.02)
  1. Compute gross profit:
grossProfit = V10 - P
  1. Compute tax:
tax = grossProfit * 0.19
  1. Add net redemption value to cash:
netRedemption = V10 - tax

Final Early Redemption

If the investment horizon ends before a batch reaches 10 years:

  1. Compute accumulated value for the current age in full years.
  2. Compute tax on total accrued gain.
  3. Subtract early redemption cost:
earlyCost = bondCount * 3 zł
  1. Add final net liquidation value to cash.

Reinvestment

EDO does not reinvest during a cycle.

Reinvestment may happen only after natural redemption, using:

reinvestedBondCount = floor(cash / 100)

Any remainder stays as cash.

The engine records:

  • initial allocation purchase event
  • each reinvestment purchase event
  • natural redemption events
  • final early redemption events

Snapshot Semantics

This is critical for UI consistency.

grossValue

For each year:

  • grossValue means year-end portfolio value before all taxes and costs realized so far
  • it includes:
  • carried portfolio value
  • taxes already realized in previous completed cycles
  • early-redemption costs already realized, if any

netValue

  • intermediate years:
  • netValue = carried portfolio value
  • no forced liquidation is applied
  • final year:
  • netValue = liquidationValue
  • all taxes and early redemption costs are fully settled

taxPaid

  • only tax actually realized in that year

earlyRedemptionCost

  • only cost actually realized in that year

Rounding Policy

Current implementation follows project policy:

  • internal calculations: 0.001 zł
  • final public values: 0.01 zł

Public Contract

The EDO public result includes:

  • yearly results
  • final totals
  • simulationDetails.strategy = "annual-accumulation-edo"
  • simulationDetails.purchaseEvents
  • simulationDetails.redemptionEvents

Covered Tests

Current automated EDO tests cover:

  • first-year fixed 5.60% rate
  • switch to inflation + 2.00%
  • natural 10-year redemption
  • early redemption after 1, 2, and 5 years through golden references and dedicated tests
  • no reinvestment before maturity
  • reinvestment after natural redemption
  • public result contract and strategy id
  • long-horizon large-principal stability
  • shared annual-accumulation invariant grid

Traceability

  • product duration, redemption, tax, reinvestment:
  • edo.strategy.test.ts: tests/edo.strategy.test.ts
  • shared carried-value vs liquidation semantics:
  • annual-accumulation.strategy.test.ts: tests/annual-accumulation.strategy.test.ts
  • purchase and redemption history retention:
  • edo.strategy.test.ts: tests/edo.strategy.test.ts
  • annual-accumulation.strategy.test.ts: tests/annual-accumulation.strategy.test.ts

Change Rules

If EDO logic changes, update together:

  • annual-accumulation.ts: src/app/domain/bond-engine/strategies/annual-accumulation.ts
  • annual_accumulation_logic.md
  • edo.ts: src/app/domain/bond-engine/strategies/edo.ts
  • edo.strategy.test.ts: tests/edo.strategy.test.ts
  • EDO_logic.md
  • init_prompt.md: ai/init_prompt.md