Skip to main content

Streaming, PSD-by-construction covariance estimator with Fisher-kernel weighting and adaptive shrinkage

Project description

Squeeze Kernel Covariance Estimator

CI PyPI Python License: MIT

A streaming covariance estimator for panels of daily financial returns. One O(n²) update per day, positive semi-definite by construction at every step, missing values handled natively, and defaults that require no tuning. Only dependency: NumPy.

Reference: "The Squeeze Kernel Covariance Estimator: Dual-Timescale Tracking with Adaptive Shrinkage" (Kende, 2026).

Why

Rolling-window estimators (Ledoit–Wolf, nonlinear shrinkage, RMT denoising) refit over a fixed window each day and cannot adapt within it; multivariate GARCH (DCC) adapts but needs multi-stage estimation and a fragile news coefficient. The Squeeze Kernel is a single streaming recursion that:

  • is PSD at every step, structurally — never needs eigenvalue clipping, nearest-PSD projection, or a solver;
  • adapts fastest exactly when it matters — a Fisher-information kernel up-weights high-dispersion (stress) days, when correlation regimes actually move;
  • regularises itself — an adaptive equicorrelation shrinkage activates automatically as the asset count approaches the effective sample size, with a provable condition-number bound;
  • ingests missing values natively — listings, delistings, and halts enter as NaN; no imputation or complete-case subsetting;
  • is fast — a full 30-year daily pass takes ~0.75 s at n=100 and ~3.4 s at n=300 (single-threaded), 30–40× faster than rolling-window baselines at scale.

On a 30-year S&P 500 panel (n=100, ~7,600 out-of-sample days) it statistically ties DCC on one-step density forecasts and beats EWMA, Ledoit–Wolf, OAS, nonlinear shrinkage, RMT denoising, and the Gerber statistic — and it is the only method in the 90% model confidence set together with DCC. At n=300 it leads every competitor that remains statistically viable.

Installation

pip install squeeze-kernel          # NumPy only
pip install "squeeze-kernel[full]"  # + SciPy (kappa calibration, chi² kernel)

Quickstart

import numpy as np
from squeeze_kernel import SqueezeKernelEstimator

# daily_returns: array of shape (T, n) — may contain NaN for missing assets
est = SqueezeKernelEstimator(n_assets=daily_returns.shape[1])

for r_t in daily_returns:          # stream one day at a time
    est.update(r_t)

cov  = est.get_cov()               # (n, n) covariance, PSD by construction
corr = est.get_corr()              # (n, n) correlation

That is the whole API for most uses. The defaults (lambda_vol=0.98, lambda_corr=0.996, kappa=0.25) are the paper-recommended settings for daily returns, selected by time-series cross-validation and robust across a 50× parameter sweep — deploy them as-is.

Batch mode, if you prefer the full path in one call:

from squeeze_kernel import estimate_squeeze_cov

cov_path, corr_path, weights = estimate_squeeze_cov(daily_returns, with_weights=True)
# cov_path: (T, n, n) — the estimate after each day

A complete runnable walkthrough (streaming, missing data, batch) is in examples/quickstart.py.

Missing values

Pass NaN for any asset not observed on a given day — nothing else to do:

r_t = np.array([0.004, np.nan, -0.011])   # asset 2 not trading today
est.update(r_t)                            # PSD preserved, no imputation

Parameters

Parameter Default Meaning
lambda_vol 0.98 volatility EWMA decay (half-life ≈ 34 days)
lambda_corr 0.996 correlation EWMA decay (half-life ≈ 173 days, T_eff ≈ 250)
kappa 0.25 Fisher kernel saturation; higher = stronger calm-day filtering
shrinkage "auto" adaptive equicorrelation shrinkage ("none" or a float to override)
shrinkage_delta 0.10 concentration threshold at which shrinkage activates

Useful read-only state after each update(): est.weight (last kernel weight), est.effective_sample_size (kernel-weighted T_eff), est.shrinkage_intensity (current α).

To recalibrate kappa for a different asset class (requires the full extra):

kappa = SqueezeKernelEstimator.calibrate_kappa(burn_in_returns, target_weight=0.6)

Advanced options

Score-exact weighting (weight_statistic="mahalanobis", use with kappa=1.0): drives the kernel with the Mahalanobis surprise z'C⁻¹z/N against the estimator's own correlation instead of the marginal dispersion. Improves accuracy in the moderate-concentration regime — use only when n / T_eff ≲ 0.5 (e.g. n ≤ 100 at the default lambda_corr); at higher concentration the estimated inverse degrades it and the default is strictly better.

est = SqueezeKernelEstimator(n_assets=100, kappa=1.0, weight_statistic="mahalanobis")

Score-driven memory (lambda_corr_fast=0.99): lets stress days also shorten the correlation memory (decay slides from lambda_corr toward lambda_corr_fast as the kernel weight rises). Do not combine with the Mahalanobis option — they act on the same channel and the combination degrades accuracy.

Alternative kernels: pass kernel_fn=kernel_exponential (with kernel_kwargs={"gamma": ...}) or kernel_chi2_cdf, or any callable (d2, *, n_observed, **kw) -> float mapping to [0, 1). The PSD guarantee holds for any such kernel.

How it works

Three mechanisms in one recursion:

  1. Dual-timescale EWMA — fast per-asset volatility (lambda_vol) is separated from slow correlation dynamics (lambda_corr), so variance shocks don't contaminate the correlation estimate.
  2. Fisher kernel weighting — each day's standardized outer product enters with weight w = d²/(d² + kappa), where is the mean squared standardized return: calm days contribute little, dispersion shocks contribute fully.
  3. Adaptive equicorrelation shrinkagealpha = min(1, max(0, n/(2·S) − delta)) blends toward an equicorrelation target using the estimator's own kernel-weighted sample size S; it is a no-op at low dimension and provides provably bounded conditioning at high dimension.

The complete update is a natural-gradient step on the Gaussian log-likelihood, with the kernel weight acting as an adaptive Riemannian learning rate (paper, Appendix B).

Development

uv sync --extra full --extra dev
uv run python -m pytest        # test suite
uv run python -m ruff check .  # lint
uv build                       # build sdist + wheel

Releases: publishing a GitHub release from a v* tag triggers the publish workflow, which builds and uploads to PyPI via trusted publishing.

Citation

@article{kende2026squeeze,
  title  = {The Squeeze Kernel Covariance Estimator: Dual-Timescale Tracking with Adaptive Shrinkage},
  author = {Kende, Robert},
  year   = {2026}
}

See also CITATION.cff.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

squeeze_kernel-0.2.0.tar.gz (11.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

squeeze_kernel-0.2.0-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file squeeze_kernel-0.2.0.tar.gz.

File metadata

  • Download URL: squeeze_kernel-0.2.0.tar.gz
  • Upload date:
  • Size: 11.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for squeeze_kernel-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ee600fdaa334610c76c87005465bebc94bbd303a58cf3ffcb21bd01b5381c2a1
MD5 82fb6dd66436ca664ecc35e0a6f88a74
BLAKE2b-256 50bc979b87300eefccc4df929424a2a0d3cfda34661089b474db975214cedbd4

See more details on using hashes here.

Provenance

The following attestation bundles were made for squeeze_kernel-0.2.0.tar.gz:

Publisher: publish.yml on r0k3/squeeze-kernel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file squeeze_kernel-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: squeeze_kernel-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for squeeze_kernel-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 778665a2e1fd538dca7ab5e29ca7f98fec1ddd7f1eeebac61b668fb8573b35fd
MD5 10ac87d46cc5e2f36cdf7889b41e0ea6
BLAKE2b-256 233965df2e3761b562505f3b7ee5343dcf06e5b4adb32e1a6c60489f60c31333

See more details on using hashes here.

Provenance

The following attestation bundles were made for squeeze_kernel-0.2.0-py3-none-any.whl:

Publisher: publish.yml on r0k3/squeeze-kernel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page