HM

ProjectsCase study

walkforward

A quantitative finance library written in C11 with Python bindings, published on PyPI with cross-platform wheels.

  • C11
  • CMake
  • Python
  • ctypes
  • cibuildwheel
  • PyPI

The problem

Most backtests that look good are quietly peeking one bar ahead. The estimator that sizes a position already knows the return it is about to earn, or a training label was computed from prices inside the test window. Both are one line of ordinary-looking code.

walkforward is built so neither can happen by accident: volatility forecasts that have not seen the period they forecast, position sizes built from them, and walk-forward splits whose training labels cannot reach into the test window.

How it works

One timing convention runs through the library. Every forecast at index t is built only from data before t, so a position sized from sigma[t] can be scored against returns[t]. An estimator that uses the current bar says so, and takes a lag argument to fix it.

Purged walk-forward splits take a label horizon instead of a purge count, because the count is where people slip, and they drop straight into scikit-learn's cross_val_score.

GARCH(1,1) is fitted by maximum likelihood with no optimizer dependency, from several Nelder-Mead starts. Sizing fails closed: a bad price gives a zero position, never a NaN, and a leverage cap that would overflow is refused.

The C builds as strict ISO C11 under GCC, Clang and MSVC with warnings as errors and no fused multiply-add, so results agree across compilers to the last bit. Python reaches it through ctypes, and the binding allocates every output itself, so a numpy view can never alias an output buffer.

The C11 core has five parts and no dependency beyond libm: rolling statistics and EWMA; volatility, with GARCH(1,1), Parkinson and Garman-Klass; sizing, with volatility targeting, Kelly and drawdown scaling; purged walk-forward splits; and ridge regression by Householder QR. A Python package reaches the core through ctypes. Every push runs 12 CI jobs: builds and tests on Linux, macOS, Windows and 32-bit, sanitizers with a fuzz sweep, the reference suite against pandas, numpy, scikit-learn and arch, and packaging checks. A version tag builds 5 platform wheels and an sdist with cibuildwheel and publishes them to PyPI through trusted publishing.

The C core does the work, Python is a thin binding, and every push runs the checks.

Drawn from walkforward README (The C library, Validation), .github/workflows/ci.yml and release.yml at 5e6d8df; CI run 42554672243 has 12 jobs.

What was measured

12
CI jobs on every push: C builds on Linux, macOS, Windows and 32-bit, sanitizers, an install-and-consume check, the reference suite, and the Python package on three systemsSource: gh api, CI run 42554672243 on main at 5e6d8df (2026-11-09): total_count 12
5.0e-7
the largest GARCH(1,1) parameter difference from the arch package across 20 fresh samples fitted on the same likelihoodSource: walkforward README, How it stays honest and Validation, at 5e6d8df
0
lookahead violations: for 40 random series, everything after a random t is rewritten, and every output through t must stay bit-identicalSource: walkforward README, Validation (No lookahead, 40 trials, 4 functions) and How it stays honest, at 5e6d8df
1,620
walk-forward split configurations matched against an independent generator, with 0 mismatchesSource: walkforward README, Validation, at 5e6d8df
10.1%
realized volatility against a 10% target, out of sample on 20 years of daily SPY, with the GARCH forecast refitted for each foldSource: walkforward README, A full walk-forward on SPY (2006 to 2026, 41 folds), at 5e6d8df
5
platform wheels plus an sdist for version 3.4.2 on PyPI, published through trusted publishing with no stored tokenSource: PyPI JSON API for walkforward, read 2026-11-09 (version 3.4.2: 5 wheels and an sdist); .github/workflows/release.yml at 5e6d8df
Line chart of the annualized GARCH(1,1) volatility forecast for SPY from 2006 to 2026. It stays mostly between 0.1 and 0.3, with spikes to about 0.75 in late 2008, about 0.5 in 2011, about 1.15 in early 2020 and about 0.7 in 2025.

The annualized GARCH(1,1) forecast, fitted for each fold and carried onto its test window with filter_from: out of sample on daily SPY, 2006 to 2026.

Source: walkforward examples/spy_walk_forward.png at 5e6d8df, lower panel, converted to monochrome. Tap or click the figure to open it full size.

How it was checked

Limits

walkforward is a set of building blocks, not a backtester or a portfolio system. It knows nothing about transaction costs, borrow, calendars or corporate actions. Its Kelly sizing is the textbook upper bound from a historical sample, not an allocation model, and a converged GARCH fit means the optimizer met its tolerances, not that the parameters are identified. It is research software, not investment advice.