Metadata-Version: 2.4
Name: pykdi-quant
Version: 0.2.0
Summary: Fit fair constant-maturity indexes from term structures and validate them against ANBIMA IDkA
Requires-Python: >=3.11
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: pyarrow>=14
Provides-Extra: notebooks
Requires-Dist: jupyterlab>=4.4; extra == 'notebooks'
Requires-Dist: matplotlib>=3.9; extra == 'notebooks'
Requires-Dist: nbconvert>=7.16; extra == 'notebooks'
Requires-Dist: seaborn>=0.13; extra == 'notebooks'
Description-Content-Type: text/markdown

# pykdi

**Python Constant Duration Index** is a quantitative toolkit for fitting term structures and
transforming them into fair, daily constant-maturity return indexes at any requested maturity.

`pykdi` is not limited to the conventional vertices published by an index provider. Given a daily
zero-curve parameter history, it can construct on-demand synthetic indexes such as 6 months,
18 months, 30 months, 7 years, or an entire dense maturity grid. These indexes provide consistent
market-value histories for research, relative-value analysis, portfolio risk, historical stress,
factor construction, and liability-aware investment analysis.

The implementation is empirically checked against the official ANBIMA IDkA return methodology.
The close adherence across published PRE and IPCA maturities demonstrates that the same transforms
can be used confidently between the conventional vertices.

## What Is A Fair Constant-Maturity Index?

A zero curve provides the fair annualized rate for a cash flow at each maturity. A
constant-maturity index asks a different question:

> What would the daily marked-to-market return have been if capital were continually invested at
> the same maturity vertex of that fair curve?

For a requested maturity of `n` business days, the position is initiated using yesterday's
`n`-day zero rate. One business day later it has `n-1` days remaining and is marked using today's
`n-1`-day rate. Proceeds are conceptually reinvested at today's `n`-day vertex, keeping the index's
target maturity constant.

```text
                    n / basis
          (1 + y[t-1, n])
G[t] = ---------------------------------
                    (n - 1) / basis
          (1 + y[t, n-1])
```

This is a **fair synthetic zero-coupon return**, not the return of a specific traded security. It
isolates curve carry and repricing without bond selection, coupon schedules, liquidity premiums,
transaction costs, or rebalancing constraints.

## Why Custom Maturities Matter

Published benchmarks expose a small set of standard vertices. Real portfolios and liabilities
rarely align exactly with them. `pykdi` can generate any integer maturity of at least two days,
allowing users to:

- Match a portfolio, hedge, or liability horizon more precisely.
- Build dense curve-return panels for key-rate and principal-component analysis.
- Interpolate risk in return space instead of selecting the nearest benchmark.
- Construct steepener, flattener, butterfly, and roll-down factors from consistent methodology.
- Estimate volatility, correlation, VaR, expected shortfall, and drawdown by maturity.
- Backtest maturity allocation and duration-timing rules without security-selection effects.
- Compare a traded portfolio with a fair curve-implied benchmark at its actual target duration.

These indexes are analytical marks. They are useful risk and research inputs, but they are not
automatically investable or guaranteed to include every source of realized P&L.

## Capabilities

- Fit Nelson-Siegel-Svensson zero curves on arbitrary maturity arrays.
- Convert fitted curves into daily fair constant-maturity gross returns.
- Add a common external carry factor, such as inflation accretion or funding carry.
- Compound returns into indexes with any positive base value.
- Transform parameter histories directly into multi-maturity index panels.
- Build custom PRE and VNA-adjusted IPCA indexes directly from the latest public mirrors.
- Report drift and adherence against all 12 conventional ANBIMA IDkA maturities.

## Install With UV

```powershell
uv sync --all-extras
```

`uv.lock` pins the complete Python environment. Run tools and notebooks through `uv` without
manually activating a virtual environment:

```powershell
uv run pytest
uv run ruff check .
uv build --clear
```

## Quick Start: Custom Indexes

```python
from pykdi import default_kdi, default_vna_proxy

# 6M, 18M, 30M, 7Y, and 12Y on a 252-business-day basis.
maturities = [126, 378, 630, 1_764, 3_024]
pre_indexes = default_kdi(maturities, segment="pre", base_value=100.0)
ipca_indexes = default_kdi(maturities, segment="ipca", base_value=100.0)
inferred_vna = default_vna_proxy()
```

The index outputs are pandas DataFrames indexed by observation date with integer maturity columns.
`default_kdi` downloads the latest public mirror data by default. Pass `irts_revision` and
`idka_revision` explicitly when a historical reproducible snapshot is required. IPCA indexes use
the common daily VNA proxy inferred from official IDkA IPCA 2Y returns. ANBIMA-backed APIs truncate
fitted decimal rates to six places before compounding, matching the four-decimal-percent buy and
sell rates used in published IDkA calculations.

## General Transform API

### Fit NSS Curves

```python
from pykdi import fit_nss_curves

rates = fit_nss_curves(
    parameters,
    maturity_days=[21, 63, 126, 252, 378, 504, 756],
    annualization_days=252,
)
```

Rates are decimal annual effective rates. The input parameters can come from `load_irts()["pre"]`,
`load_irts()["ipca"]`, or any source satisfying the input contract below.

### Calculate Fair Returns

```python
from pykdi import constant_maturity_returns

gross_returns = constant_maturity_returns(
    parameters,
    maturity_days=[378, 630, 1_764],
)
simple_returns = gross_returns - 1.0
```

The source-independent core transform uses full-precision fitted rates by default. Pass
`rate_decimal_places=6` when reproducing the ANBIMA fitted-rate convention directly through the
core API.

### Add External Carry

```python
from pykdi import apply_carry_factors

# carry_factors is a date-indexed Series of daily gross factors.
total_returns = apply_carry_factors(gross_returns, carry_factors)
```

For ANBIMA's IPCA-linked indexes, the external factor is the daily NTN-B VNA change. The same
transform can represent another inflation index, funding accrual, collateral remuneration, or a
user-defined carry process when the economics support a common multiplicative factor.

### Rebase Or Transform In One Step

```python
from pykdi import compound_index, transform_constant_maturity

levels = compound_index(total_returns, base_value=1_000.0)

levels_direct = transform_constant_maturity(
    parameters,
    maturity_days=[378, 630, 1_764],
    carry_factors=carry_factors,
    base_value=1_000.0,
)
```

## Risk Calculations

The output is a clean maturity-by-date return matrix suitable for standard risk tooling:

```python
import numpy as np

returns = fair_indexes.pct_change().dropna()
annualized_volatility = returns.std() * np.sqrt(252)
return_correlation = returns.corr()
historical_var_99 = -returns.quantile(0.01)
expected_shortfall_99 = -returns[returns.le(-historical_var_99)].mean()
drawdown = fair_indexes / fair_indexes.cummax() - 1.0
```

For portfolio applications, map holdings or liabilities to chosen maturity vertices and aggregate
scenario returns using exposure weights. A dense grid can also feed PCA, covariance shrinkage,
historical simulation, curve-factor regressions, or custom key-rate buckets.

Weights applied to these returns must reflect the user's P&L mapping; index weights are not
automatically equivalent to market-value, DV01, or duration weights.

## ANBIMA Adherence Validation

The validation compares model daily gross returns with the published-maturity IDkA return mirrors.
It reports correlation, mean error, median and 95th-percentile absolute error, RMSE, maximum error,
annualized tracking error, cumulative drift, annualized drift, and percentages within 0.1 bp and
1 bp for every maturity.

```powershell
uv run pykdi-validate
```

The command uses the latest Hugging Face dataset heads by default and writes:

- `reports/validation_report.md`
- `reports/validation_metrics.csv`
- `reports/top_residuals.csv`
- `reports/inferred_vna_factors.csv`
- `reports/calendar_diagnostics.json`

The PRE indexes are direct adherence checks. Because the public data does not include an
independent NTN-B VNA series, IPCA 2Y calibrates a same-date common carry factor and the other IPCA
maturities are labeled cross-maturity checks rather than independent absolute-return validation.

The generated `validation_report.md` contains the current empirical results. Use
`--irts-revision` and `--idka-revision` to validate an explicitly selected historical snapshot.

## Educational Notebooks

```powershell
uv run jupyter lab
```

Open the notebooks in order:

1. `notebooks/01_anbima_adherence.ipynb` loads the latest curve and index datasets, computes every
   conventional IDkA return through `pykdi`, compares computed and dataset returns, plots cumulative
   paths and residual metrics, and explains the IPCA VNA calibration limitation.
2. `notebooks/02_pykdi_usage.ipynb` walks through NSS curve fitting, arbitrary maturity indexes,
   external carry factors, rebasing, volatility, VaR, drawdown, correlations, and curve factors.

The committed notebooks intentionally contain no outputs or execution counts. To execute a clean
copy non-interactively without changing the source notebook:

```powershell
uv run jupyter nbconvert --to notebook --execute notebooks/01_anbima_adherence.ipynb --output 01_anbima_adherence.executed.ipynb
uv run jupyter nbconvert --to notebook --execute notebooks/02_pykdi_usage.ipynb --output 02_pykdi_usage.executed.ipynb
```

The executed copies are working artifacts and should not replace the output-free notebooks in the
repository.

## Input Contract

NSS parameter frames require a unique, strictly increasing `DatetimeIndex` and these decimal
columns:

```text
beta1, beta2, beta3, beta4, lambda1, lambda2
```

All values must be finite and lambdas strictly positive. The public IRTS adapter removes a row when
all six parameters exactly repeat the immediately preceding row for the same curve. Adjacent rows
must otherwise represent consecutive observations on the chosen business calendar. `pykdi` does
not guess holidays or synthesize a missing curve observation.

## Project Layout

```text
src/pykdi/             Core transforms, public data adapters, and validation
tests/                 Formula, schema, calendar, metric, and CLI tests
notebooks/             Output-free adherence and package-usage walkthroughs
reports/               Current ANBIMA adherence artifacts
```

## Data Provenance

The notebook datasets are author-maintained public mirrors, not authenticated ANBIMA API responses.
Defaults follow each mirror's mutable `main` branch so results can change as new observations or
corrections arrive. Explicit revisions provide reproducible historical reads but do not replace
authoritative source lineage. Core `pykdi` transforms are data-source agnostic and can be used with
internally governed or independently sourced parameter histories.

## Interpretation And Limitations

A `pykdi` index captures fair zero-curve repricing and carry under its stated conventions. It does
not automatically include bid-ask spreads, transaction costs, market impact, security coupons,
liquidity premiums, financing constraints, or parameter-estimation uncertainty. IPCA 2Y fits by
construction because it calibrates the inferred VNA factor; the other IPCA maturities are relative
cross-maturity checks rather than independent VNA validation. Custom vertices remain model-implied
where no published benchmark exists.

## References

- [ANBIMA IDkA page](https://www.anbima.com.br/pt_br/informar/ferramenta/precos-e-indices/idka.htm)
- [IDkA Methodology, February 2026, English](https://www.anbima.com.br/data/files/1C/85/2F/A4/C5FBC91067D089C9BA2BA2A8/Metodologia%20IDkA_fev26_ingles.pdf)
