Technical Lab · Synthetic demonstration
Chronological backtesting
A forecast is scored on weeks that had not happened yet. A random split does not guarantee that.
Synthetic demonstration · Independent educational implementation.
Decide which weeks a forecasting model may see, and which weeks are the future it must predict.
A random split can put a later week in the training set. The error then flatters a model that was allowed to see the answer.
Move the origin
- Training
- Test
- Not used yet
- Future week used for training
- Training
- weeks 1–16
- Forecast origin
- week 17
- Test horizon
- weeks 17–20
- Not used yet
- weeks 21–24
The forecast origin is week 17, the first week the model must not have seen. Training uses weeks 1–16. The test horizon is weeks 17–20. Weeks after that (weeks 21–24) stay unused at this origin. The chronological split does not put a future week in training. The illustrative random split does: weeks 18 and 20 are labelled as training. A lower error from that split would not be evidence of a better forecast.
Implementation
The multi-step page uses the same rule when it scores H1–H6.
src/lib/lab/backtesting.ts View source on GitHub: src/lib/lab/backtesting.ts
export interface ForecastWindow {
origin: number;
trainStart: number;
trainEnd: number;
testEnd: number;
}
export function rollingOrigins(length: number, minTrain: number, horizon: number, step: number): ForecastWindow[] {
if (![length, minTrain, horizon, step].every((value) => Number.isInteger(value) && value > 0)) throw new Error('Window arguments must be positive integers.');
if (minTrain + horizon > length) throw new Error('The series cannot hold a training window and a horizon.');
const windows: ForecastWindow[] = [];
for (let origin = minTrain; origin + horizon <= length; origin += step) {
windows.push({ origin, trainStart: 0, trainEnd: origin, testEnd: origin + horizon });
}
if (windows.length === 0) throw new Error('No forecast origin fits in the series.');
return windows;
}