Skip to main content

walkforward

Volatility forecasts, position sizing and purged walk-forward splits that never see the future. A C11 library with a numpy front door.

pip install walkforward

Most backtests that look good are quietly peeking one bar ahead. The estimator that sizes a position knows the return it is about to earn, or a training label was computed from prices inside the test window. This library is built so those two things cannot happen by accident, and it is checked on every push against pandas, numpy, scikit-learn, the arch package and exact rational arithmetic.

  • One timing convention. Every forecast at index t is built from data before t. ewma_vol and the GARCH filters are predictive; the rolling statistics and range estimators are contemporaneous and say so in their own docstrings, with lag to fix them.
  • Purging that asks the right question. PurgedWalkForward takes a label horizon, not a purge count, because purge = h - 1 is the part people get wrong. Drops straight into cross_val_score.
  • GARCH(1,1) by maximum likelihood, checked against arch on identical samples and identical likelihoods, and invariant to the units of the returns.
  • Sizing that fails closed. A bad price gives a zero position, not a NaN one. A leverage cap that could overflow is refused rather than silently ignored.
  • Ridge by Householder QR, so a feature at a price level near 1e9 keeps its slope precision.

Research tooling, not investment advice. It is a set of building blocks, not a backtester: it knows nothing about costs, borrow, calendars or corporate actions.

The loop

import numpy as np
import walkforward as wf

# The fit assumes mean-zero returns. Demean with the TRAINING mean; the
# full-sample mean would put the future into the fit.
mu = returns[:1000].mean()
model = wf.garch_fit(returns[:1000] - mu)

# sigma[t] forecasts period t from returns before t. filter_from continues
# from the variance state the fit ended on, which is what makes the
# out-of-sample path the same as filtering everything together.
sigma = model.filter_from(returns[1000:] - mu)

# A position held over period t is entered at the close of t-1, so it is
# sized against the previous close, and earns position * price * return.
entry = close[999:-1]
position = wf.vol_target_position(
    sigma, target_vol=0.01, equity=100_000.0, price=entry, max_leverage=2.0
)
pnl = position * entry * returns[1000:]

model.persistence, model.half_life and model.unconditional_vol are there so you do not have to recompute them.

Cross-validation

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import cross_val_score
import walkforward as wf

# Target is a 21-period forward return, so labels span 21 periods and the
# last 20 training rows before each test window are dropped.
cv = wf.PurgedWalkForward(train_size=756, test_size=252, label_horizon=21)

scores = cross_val_score(GradientBoostingRegressor(), X, y, cv=cv)

Without the purge, the twenty training rows before each test window carry labels that were partly computed from test-window prices. The library counts that leak in its own test suite: at a horizon of 21, a purge of 20 leaves zero leaking training rows and a purge of 19 leaves ten.

There is no embargo argument, on purpose. An embargo protects training data that sits after a test window, and a walk-forward never trains on anything after the window it is testing. walk_forward_splits(..., include_post_train=True) exposes that variant with the warning it deserves.

A full example

examples/spy_walk_forward.ipynb runs one model end to end on 25 years of daily SPY: lagged features, a ridge on a five-day forward return, 41 purged folds, a GARCH per fold continued with filter_from, and 10% vol targeting sized against the previous close. Vol targeting alone lifts the out-of-sample Sharpe from 0.63 to 0.79 and halves the drawdown; the ridge signal has no edge and the notebook says so. The same model with the lags removed reports a Sharpe of 14.6, which is the leak this package exists to make hard.

Conventions

Volatility is per period. Annualised converts as annual / sqrt(periods_per_year), so a 10% annual target on daily data is 0.10 / sqrt(252), about 0.0063.

rolling_std uses the population convention and divides by window. Pandas rolling().std() defaults to the sample convention, so the two differ by sqrt(window / (window - 1)).

A pandas Series in gives a pandas Series out, on the same index. Realigning a bare array by hand is one of the ways lookahead gets in, so two Series passed together must share an index; lag is the way to shift one.

Bad arguments raise. Bad elements are handled per function and documented on each: a non-finite return is skipped by the recursions, makes a rolling window NaN, and gives a zero position in sizing. DomainError (a ValueError) means the arguments were fine but the computation has no answer: a singular design, a sample with no variance, an overflowing recursion.

What it is checked against

The C library underneath is compared on every push to independent implementations. The numbers below come from tests/reference/reference_check.py in the repository, which needs only the packages it names.

Check Reference Result
Rolling mean and std, with gaps pandas 3.1e-11
Rolling std at a price level of 1e9 exact rational arithmetic 4.4e-16, where a two-pass computation is off by 4.9e-12
EWMA, predictive alignment pandas ewm shifted one period 3.5e-18
GARCH filter and forecast arch 4.4e-16 and 8.9e-16 relative
GARCH fit over 20 samples arch, same likelihood 5.0e-7 max parameter difference
Ridge, condition number 1e4 to 1e10 SVD least squares within 2x condition times epsilon
Ridge with a feature at level 1e6 to 1e15 exact rational OLS under 1e-14
Walk-forward splits, 1620 parameter sets independent generator 0 mismatches
No lookahead, 40 trials bitwise prefix comparison 0 violations

The C also runs under AddressSanitizer and UndefinedBehaviorSanitizer, on 32-bit and 64-bit, and gives bit-identical answers under GCC and Clang.

License

MIT. Source and the C library: https://github.com/haeganm/walkforward

Metadata

Release files for walkforward 3.4.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for walkforward 3.4.2
File Size Uploaded Provenance Actions
walkforward-3.4.2.tar.gz 92.3 kB With provenance Details for walkforward-3.4.2.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for walkforward 3.4.2
File Actions
walkforward-3.4.2-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for walkforward-3.4.2-py3-none-win_amd64.whl
walkforward-3.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64, Linux glibc 2.17+ x86-64 Details for walkforward-3.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
walkforward-3.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64, Linux glibc 2.28+ ARM64 Details for walkforward-3.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl
walkforward-3.4.2-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for walkforward-3.4.2-py3-none-macosx_11_0_arm64.whl
walkforward-3.4.2-py3-none-macosx_10_13_x86_64.whl Python 3 none macOS 10.13+ x86-64 Details for walkforward-3.4.2-py3-none-macosx_10_13_x86_64.whl

Total release size: 286.5 kB

Release files / walkforward-3.4.2.tar.gz

Download URL walkforward-3.4.2.tar.gz
Size 92.3 kB
Tags Source
SHA-256 checksum
How to use checksums
312cd92eddfcd728c16a8822c4353a4d7067848019504781c2b02ca3ee7c5da3
BLAKE2b-256 checksum
How to use checksums
54d40f61d02eb90740cf06daacd606ecac3d17a77cc83a848db8919a1477c935
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / walkforward-3.4.2-py3-none-win_amd64.whl

Download URL walkforward-3.4.2-py3-none-win_amd64.whl
Size 41.9 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
fb64141dd5f3bb911ce5c665127191ca2c6e392f43f0110a12f64e905526531a
BLAKE2b-256 checksum
How to use checksums
30950b91fe283d3ea2a9a83066dc40e3f878db557b8008c97c82932cc8d321fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / walkforward-3.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl

Download URL walkforward-3.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl
Size 38.6 kB
Tags Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
b29874c2d06efe7c9dc187f8bc25dc996389a3f8fdb889255085470fbd7d1aec
BLAKE2b-256 checksum
How to use checksums
ec535d80d69c20e2dbf14227728e681dcb968958b9bf9c86d2283c8892e9a074
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / walkforward-3.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl

Download URL walkforward-3.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl
Size 37.2 kB
Tags Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
16b91ff6312c5c2910b2a59f622f40140e5609b003b8cdcf2303664948df665d
BLAKE2b-256 checksum
How to use checksums
6e00a999134a26a4da8d28b5f43690c551cdd930f42cf5170f0d6deab166bc59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / walkforward-3.4.2-py3-none-macosx_11_0_arm64.whl

Download URL walkforward-3.4.2-py3-none-macosx_11_0_arm64.whl
Size 37.0 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
15013bbfcb9c22158b1efa812ce26606d0471c464dd2f8b4d1bb7c3400088cb5
BLAKE2b-256 checksum
How to use checksums
72f56afeec65538b76df652f8150464e2fba111359801ebf27998364b765546e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / walkforward-3.4.2-py3-none-macosx_10_13_x86_64.whl

Download URL walkforward-3.4.2-py3-none-macosx_10_13_x86_64.whl
Size 39.5 kB
Tags Python 3 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
40ccf861fe57d1f87adaee3832410080f9a0a91f48948e5e4f17870fa37a6f8e
BLAKE2b-256 checksum
How to use checksums
8ab44a729e51ae3c0986a28d6e2cad59a3787324112855e7eaa3015eec9a663a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Security for walkforward 3.4.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for walkforward-3.4.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page