Walk-Forward Cross-Validation

SkillAI & models

Rolling or expanding window CV that preserves temporal order. Use when evaluating ML models on time-series data where standard k-fold causes temporal leakage.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Walk-Forward Cross-Validation skill

What this skill tells your AI

The instructions your AI receives, as published by ml4t/skills in validation/walk-forward-cv/SKILL.md and read by ahel’s review.

A single train/test split tells you nothing about how a model adapts over time. Walk-forward CV slides a window through the data, training and testing sequentially, revealing how performance evolves across market regimes.

The Problem

A single 80/20 train/test split produces one score from one market period. The model may excel in bull markets but fail in drawdowns - you cannot tell. Standard k-fold shuffles time, leaking future data. You need sequential evaluation that mirrors live deployment: train on the past, predict the future, advance, repeat. This exposes regime sensitivity and stationarity failures.

The Pattern

WRONG

from sklearn.model_selection import train_test_split

# Single split - one market regime, no adaptation signal
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, shuffle=True  # Shuffling leaks future
)
model.fit(X_train, y_train)
print(f"Score: {model.score(X_test, y_test):.3f}")  # One number

CORRECT

import numpy as np
from sklearn.model_selection import TimeSeriesSplit

# Walk-forward with gap for label horizon
n_splits = 5
gap = 5  # Purge gap matching label horizon
tscv = TimeSeriesSplit(n_splits=n_splits, gap=gap)

scores = []
for fold, (train_idx, test_idx) in enumerate(tscv.split(X)):
    model.fit(X[train_idx], y[train_idx])
    score = model.score(X[test_idx], y[test_idx])
    scores.append(score)
    print(f"Fold {fold}: train={len(train_idx)}, "
          f"test={len(test_idx)}, score={score:.3f}")

# Performance trajectory across time
print(f"Mean: {np.mean(scores):.3f}, Std: {np.std(scores):.3f}")

Expanding vs Rolling

Expanding:  [-------train-------][test]  (more data, slower adaptation)
Rolling:         [----train----][test]   (fixed window, faster adaptation)

Expanding is the default with TimeSeriesSplit. For rolling, limit training size:

# Rolling window: keep only most recent 504 training samples
max_train = 504  # ~2 years daily
for train_idx, test_idx in tscv.split(X):
    if len(train_idx) > max_train:
        train_idx = train_idx[-max_train:]
    model.fit(X[train_idx], y[train_idx])

When to Use Each

VariantBest forTrade-off
ExpandingStable relationships, large feature setsSlow to adapt to regime changes
RollingRegime-sensitive strategies, non-stationary dataLess data per fold, higher variance

Guardrails

  • Never shuffle time-series data in any CV scheme
  • When using label_horizon, purging is automatic; gap adds an extra buffer on top
  • Test folds should span different market conditions (bull, bear, sideways)
  • Expanding window scores should be compared to rolling - divergence signals non-stationarity
  • More splits = more variance in estimates; fewer splits = more bias
  • Evaluate at three levels: model diagnostics (loss, R²), signal diagnostics (IC, turnover), strategy outcomes (Sharpe) - divergence across levels reveals translation failures

Production Implementation

ml4t-diagnostic provides WalkForwardCV with built-in purging and embargo:

from ml4t.diagnostic.splitters import WalkForwardCV

cv = WalkForwardCV(
    n_splits=5,
    expanding=True,       # False for rolling window
    label_horizon=5,      # Automatic purge gap
    embargo_size=2,
)
for train_idx, test_idx in cv.split(X):
    model.fit(X[train_idx], y[train_idx])
    pred = model.predict(X[test_idx])

Checklist

  • Temporal order preserved - no shuffling
  • Gap/purge >= label horizon between train and test
  • Embargo applied for autocorrelated features
  • Both expanding and rolling variants compared
  • Performance trajectory inspected across folds (not just mean)

Signals

GitHub stars
20
Forks
11
Last commit
Sep 2026
Advanced
Item type
skill
Key
ml4t-walk-forward-cv
Source
github.com/ml4t/skills