# Sprint 14 — Performance methodology v1

## Versioned contracts

- Engine: `portfolio_performance_v2`
- TTWROR: `ttwror_daily_v1`
- Personal return: `xirr_v1`
- CHF attribution: `performance_attribution_chf_v1`
- Position cost basis: `fifo_v1`
- Investment scope: `investment_performance_scope_v1`

Legacy `twr_v1`, `xirr_v1` and the prior response aliases remain readable; no parallel performance endpoint was added. The former Analytics performance field is a deprecated compatibility projection only: it forwards `portfolio_performance_v2`, preserves raw valuation and benchmark points, and never computes ETF proxy returns. Frontend performance and benchmark presentation use the canonical contract.

## TTWROR

Valuations are sorted by their stored instant. For the daily contract, an external cashflow timestamp is matched to the stored valuation of its source calendar day; duplicate valuations for the same calendar day are rejected as ambiguous. Every external cashflow day must have a stored valuation boundary. No Modified-Dietz estimate and no provider fallback is used.

For subperiod `i`:

```text
r_i = (V_close + |withdrawals_at_close|) /
      (V_open + deposits_at_open) - 1
TTWROR = product(1 + r_i) - 1
```

Timing convention:

- external deposit: beginning of the subperiod starting at its boundary;
- external withdrawal: end of the subperiod ending at its boundary;
- internal transfer inside the selected scope: neutral;
- buy, sell, dividend, interest, fee and tax: not external capital flows;
- a cashflow without a same-boundary valuation makes TTWROR unavailable;
- inconsistent signs and endpoint flows without a return subperiod are rejected;
- annualized TTWROR is emitted only for periods longer than 365 days.

## XIRR

`xirr_v1` adds the negative opening value and positive closing value to dated, signed external flows and solves XNPV with deterministic bisection. It is labeled **annualisierte persönliche Rendite**.

Fail-closed cases include missing opening/closing valuation, missing or unclassified external-flow history, no sign change, more than one sign change, and no root in the bounded search interval. Absence of recorded flows is not proof that none occurred: every selected account needs explicit, audited `performance_cashflow_coverage` with status `complete` over the full requested period. Missing, partial or shorter evidence yields `external_cashflow_history_missing` and no TTWROR or XIRR value.

Golden reference: the widely used Excel XIRR example (`-10000`, `2750`, `4250`, `3250`, `2750` on its documented irregular dates) resolves to approximately `0.373362535`.

## CHF attribution

```text
investment_result = closing_value - opening_value - net_external_cashflows
investment_result = market_price + fx + dividends_and_interest
                    - fees - taxes + other_effects
                    + unattributed_residual
```

All API attribution components are signed CHF effects; fees and taxes are negative. The bridge is complete only when the residual is within CHF 0.01.

For an unchanged position set with complete local market values and FX at both endpoints:

```text
market_price = (local_close - local_open) * fx_open
fx           = local_close * (fx_close - fx_open)
```

The price/FX interaction is assigned to FX. If positions changed or position-level history is incomplete, market and FX remain `null`; the unexplained CHF amount stays visible as `unattributed_residual` with reason code `market_fx_attribution_requires_position_level_history`.

## Productive fail-closed behavior

The global scope is PostFinance including settlement cash, canonical TrueWealth, and Crypto. A complete global return is never published from account-only history while Crypto valuation history is absent. Missing data is represented as `null` with availability status and reason codes, never as zero.

The coverage endpoint derives each role-bound scope status from the same canonical valuation, external-cashflow and attribution rules as `portfolio_performance_v2`. It does not contain permanently hard-coded availability statuses; newly classified and sufficiently valued histories can therefore become `complete` without a contract change.