ProjectsCase study
walkforward
A quantitative finance library written in C11 with Python bindings, published on PyPI with cross-platform wheels.
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

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
Against libraries it didn't write
A reference suite compiles the C into a shared library and compares every function against pandas, numpy, scikit-learn and arch on every push. Rolling statistics stay accurate to 4.4e-16 at a price level of 1e9, where a plain two-pass computation is already off by 4.9e-12.
Against brute force
78 Nelder-Mead starts on the same likelihood, over 13 series built to be awkward, from near-IGARCH to a 50-sigma outlier. The fitter lands within 3e-11 log-likelihood units of the best on all 13.
No peeking, by construction
Forecasts are tested for prefix stability: filtering the first half of a series gives exactly the first half of the full filter. A spike at bar k moves the output at k+1 and nothing before it.
Fed garbage on every run
A fuzz sweep makes 4,000 rounds of calls across the whole API with NaN, Inf, denormals, 1e308 and SIZE_MAX arguments, under AddressSanitizer and UBSan in CI. It found two bugs, and both now fail closed.
Releases without a stored token
Releases build with cibuildwheel and publish through PyPI trusted publishing, with provenance attestations. CodeQL, Dependabot, secret scanning and push protection are on.
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.