# D14-F02-A03 — Risk Budgeting

> **Domain:** D14 — Portfolio Construction  
> **Family:** D14-F02 — Risk Allocation  
> **Status:** Tested canonical source package. The article and video remain
> drafts; the visitor website has not been synchronized or verified.

## Executive summary

Risk budgeting asks what share of aggregate portfolio volatility each asset
should carry. Given positive budgets b_i, the allocator returns fully invested
long-only weights whose component volatility contributions match those budgets.
Equal-risk contribution is the special case b_i=1/N; it is not the definition of
every risk-budgeting portfolio.

The package uses the positive volatility coordinate update in
[Griveau-Billion, Richard, and Roncalli (2013)](https://arxiv.org/html/1311.4057),
Eq. 7. That source is an algorithm reference, not evidence that any allocation
is best or suitable for a particular investor.

## Problem and financial relevance

An equal capital split can carry unequal risk when assets have different
volatilities or correlations. Risk budgeting solves the inverse problem:

$$
RC_i=w_i(\Sigma w)_i/\sigma_p,\qquad
RC_i/\sigma_p=b_i,\qquad
\sigma_p=\sqrt{w^T\Sigma w}.
$$

The supplied covariance must be coherent, same-horizon, and same-scale. This is
not a market-data ingestion or corporate-action repair routine.

## Learning objectives

- Translate a stated volatility-risk budget into the component-contribution equation.
- Explain why equal-risk contribution is one budget choice, not the definition of all risk budgeting.
- Run both languages, interpret residuals, and route invalid upstream evidence instead of manufacturing zero risk.

## Definitions and terminology

| Term | Definition |
|---|---|
| Risk budget b_i | Desired share of aggregate volatility risk for asset i; positive and summing to one. |
| Component risk contribution | w_i(Sigma w)_i/sigma_p, in the same volatility unit as sigma_p. |
| Risk share | w_i(Sigma w)_i/(w' Sigma w). |
| Risk budgeting | Solving for weights that match supplied positive risk shares. |
| ERC | The special risk-budgeting case where every b_i=1/N. |

## Scope and contract

The canonical variant is long-only positive volatility-risk budgeting with a
strictly positive-definite supplied covariance and positive budgets. Internal
actual-max scaling and the equal-budget A02 special case are explicit. Spinu's
alternate log-barrier/Newton solver, general coherent risk measures, expected
shortfall, downside risk, shorting, leverage, transaction costs, annualization,
and automatic covariance repair are excluded. See
[DEFINITION-CONTRACT.md](./DEFINITION-CONTRACT.md).

| Decision | Canonical choice | Boundary | Proof |
|---|---|---|---|
| Framework | Positive volatility budgets solved by CCD Eq. 7 | No Spinu/Newton or downside solver | [arXiv:1311.4057](https://arxiv.org/html/1311.4057) |
| Risk scale | One supplied horizon/unit; actual-max internal scaling | No mixed estimator or automatic annualization | scale fixture |
| Budgets | Finite positive values, sum within 10^-12 of one | Zero/negative or arbitrary silently-renormalized totals rejected | error fixtures |
| Tolerance | Maximum absolute share residual 10^-10, budget-relative share residual 10^-8, weight sum 10^-12 | No after-the-fact tolerance choice | parity fixtures |

## Input data contract

| Field | Type | Unit | Nullable | Constraint |
|---|---|---|:---:|---|
| assetIds | ordered string[] | identifier | No | Non-empty, unique, non-blank; order preserved. |
| covariance | number[][] | squared return per common horizon | No | Finite N x N, exactly symmetric, positive diagonal, strict PD. |
| riskBudgets | number[] | unitless share | No | Length N, finite positive, sum within 10^-12 of one. |

The core does not estimate covariance or adjudicate event/effective/
observation/availability clocks, adjustment, revision/finality, missing data,
ownership, or licensing provenance. An upstream adapter must record those facts.

## Output contract

| Field | Meaning | Invariant |
|---|---|---|
| weights | fully invested capital weights | finite, strictly positive, sum within 10^-12 of one |
| componentRiskContributions | absolute volatility contributions | sum to portfolio volatility |
| componentRiskShares | contribution shares | match budgets to absolute 10^-10 and budget-relative 10^-8 residuals; sum to one |
| portfolioVariance, portfolioVolatility | supplied covariance risk scale | finite and strictly positive |
| riskShareResidual, riskShareRelativeResidual, iterations, status | solver diagnostics | status ok only after both residual checks pass |

## Mathematical formulation

Let Sigma be covariance, w positive normalized weights, and q=w' Sigma w.
Then sigma_p=sqrt(q), marginal volatility risk is (Sigma w)_i/sigma_p, and
component risk is w_i(Sigma w)_i/sigma_p. Euler homogeneity gives
sum_i RC_i=sigma_p. The target is w_i(Sigma w)_i/q=b_i.

For internal positive vector y, holding other coordinates fixed gives

$$
\Sigma_{ii}y_i^2+c_i y_i-b_i\sqrt{y^T\Sigma y}=0,\qquad
c_i=\sum_{j\ne i}\Sigma_{ij}y_j.
$$

The positive root is applied cyclically. After convergence, y is normalized to w
and supplied-scale risk is recomputed from w; a homogeneous solver state is not
itself a fully invested portfolio.

## Algorithm

1. Validate IDs, budget domain/sum, covariance shape, symmetry, diagonal, and strict PD.
2. Divide covariance by its actual maximum absolute entry and initialize positive y.
3. For each coordinate, compute the positive root of the budgeted quadratic.
4. Stop only when maximum absolute risk-share residual is at most 10^-10 and maximum budget-relative residual is at most 10^-8; otherwise return structured failure.
5. Normalize y, recompute risk and identities, and return diagnostics.

```text
validate(ids, Sigma, budgets)
S <- Sigma / max(abs(Sigma)); y <- ones(N)
repeat up to 10000 sweeps:
  for i in 0..N-1:
    c <- dot(S[i], y) - S[i,i] * y[i]
    s <- sqrt(dot(y, S*y))
    y[i] <- positive_root(S[i,i], c, budgets[i] * s)
  shares <- y * (S*y) / dot(y, S*y)
  if max_abs(shares - budgets) <= 1e-10 and max_i(abs(shares[i]-budgets[i])/budgets[i]) <= 1e-8: break
if not converged: fail convergence-failure
w <- y / compensated_sum(y)
recompute q, sigma_p, RC and shares from w in supplied scale
return weights, budgets, RC, shares, risk and diagnostics
```

## Worked example

See [CANONICAL-EXAMPLE.md](./CANONICAL-EXAMPLE.md). Its diagonal check is
independent: w_i is proportional to sqrt(b_i)/sigma_i. The non-diagonal fixture
checks the CCD result and direct Python-to-TypeScript parity.

## Evidence and historical-example decision

**Deferred.** No historically proven asset-price case is used to claim a
particular risk-budget allocation. Islam's confirmed motivation is input
integrity: repeated split-invalid prices, suspect price/quantity imports,
changing displayed weights, and Egypt/Japan/USA holdings expressed in USD make
upstream clocks, currency, and corporate-action metadata relevant before an
allocator is trusted. The 1010.SR quantity/price anecdote has no date, source,
or basis and is not historical proof. The prior 4070.SR suspension case is an
event boundary, not zero risk.

The prior [Stale-Quote Detector](https://thefintechbuilder.com/market-data-engineering/cleaning-and-validation/stale-quote-detector/)
method motivates a negative test. In a newly constructed four-row synthetic
panel, replacing a stale asset's returns with zeros invalidates the
positive-volatility input contract; it must not manufacture an infinite budget
weight. Q times local mark times FX (base per local) is market value, not cost
basis, and local-return covariance is not automatically base-currency-return
covariance. These are upstream lessons, not hidden core repairs.

In an independently recalculated two-asset synthetic illustration, A's local
returns `[-.02,-.01,.01,.02]`, B's USD returns `[-.01,.01,-.01,.01]`, and A's
USD-per-local FX returns equal B's series. A's USD returns become
`[-.0298,-.0001,-.0001,.0302]`. For the 50/50 *risk-share* special case,
mixed-basis capital weights are `(0.3874258867,0.6125741133)`, but measured on
the aligned USD covariance their shares are `(0.5851620429,0.4148379571)`.
Solving on the aligned USD covariance gives capital weights
`(0.3203717978,0.6796282022)` and equal risk shares. This demonstrates a
currency-basis error, not an observed investment or trade.

## Visuals and lab

The flow is [visuals/mermaid/risk-budgeting-flow.md](./visuals/mermaid/risk-budgeting-flow.md),
the computed comparison is [visuals/static/risk-budgeting-comparison.svg](./visuals/static/risk-budgeting-comparison.svg),
and the editable lab is [visuals/animated/playground.html](./visuals/animated/playground.html).
The lab calls the compiled module used by tests, renders weights, absolute
contributions, shares, residual, and status, uses adaptive scientific
formatting, keyboard controls, reduced-motion one-step behavior, and terminal
disabled states. It is served over localhost HTTP because file:// ESM loading
is subject to browser CORS restrictions.

## Implementation and testing

Reference functions are implementations/python/risk_budgeting.py and
implementations/typescript/risk-budgeting.ts. Each sweep is O(N^2), memory
O(N^2), and the JSON-lines CLI supports direct cross-language checks.

```powershell
python -m unittest discover -s tests -p "test_*.py"
npx tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --esModuleInterop --types node --rootDir . --outDir .tmp-ts implementations/typescript/risk-budgeting.ts tests/typescript/risk-budgeting.test.ts tests/a03-staging.test.ts
$env:A03_FIXTURE_PATH=(Resolve-Path tests/fixtures/a03-risk-budgeting-fixtures.json)
$env:A03_PYTHON_CLI_PATH=(Resolve-Path implementations/python/risk_budgeting_cli.py)
node .tmp-ts/tests/typescript/risk-budgeting.test.js
```

The checked recipe uses the repository's installed TypeScript 7.0.2 toolchain
and @types/node; run it after installing the repository dependencies rather
than assuming a global compiler.
Serve the lab with python -m http.server 8765 --directory visuals/animated and
open http://127.0.0.1:8765/playground.html.

Tests cover canonical and independent diagonal cases, budget and matrix
boundaries, finite-range behavior, permutation, immutability, actual-max
scaling, metadata-only stale/split/FX/quantity routing, and direct Python/TS
parity. The shared fixture is not the only numerical truth.

## Edge cases and limitations

| Case | Expected behavior | Reason |
|---|---|---|
| Zero or negative budget | invalid-input | Strict positive root has no declared zero-budget behavior. |
| Indefinite or singular covariance | invalid-covariance | This contract does not pretend a unique positive solution exists. |
| Stale quote or split mismatch | Upstream evidence route; core untouched | Flat/zero returns do not prove outage or corporate action. |
| Local mark and FX | Validate market-value and return clocks upstream | Base-currency covariance includes FX/product effects; it is not cost basis. |
| Tiny or huge scale | Actual-max normalization; reject representability loss | Avoid hidden overflow/underflow and false zero risk. |

Risk budgeting only matches the covariance and budgets it receives. It does not
validate source data, corporate actions, FX rates, stale sessions, cost basis,
or estimator fitness. Different horizons/currencies are not made comparable.
A risk-share match is not a forecast, guarantee, or ranking claim.

## Real-world use and related topics

Use this core to teach non-equal risk budgets, test allocation adapters after
evidence validation, and compare a supplied covariance with an independent
oracle. Production use still needs provider provenance, revision policy,
currency/horizon choices, and governance outside the function.

- D14-F02-A01 — reciprocal volatility gives a standalone product baseline.
- D14-F02-A02 — equal-risk contribution is b_i=1/N.
- D14-F02-A04 — HRP uses correlation hierarchy and inverse-variance branch allocation.
- D14-F02-A05 — HERC combines hierarchy with equal-risk allocation at explicit clusters.

## References

See [REFERENCES.md](./REFERENCES.md).
