Validate strategy robustness by optimizing on in-sample data and testing on out-of-sample windows.
Backtesting a single strategy on the full dataset can produce results that look good in-sample but fail in production — the strategy has been overfit to historical noise rather than capturing a real edge. Walk-forward analysis addresses this by splitting the dataset into a series of sequential folds: each fold optimizes on an in-sample window and validates on an untouched out-of-sample window.The out-of-sample results are what matter. If a strategy that scores well in-sample also performs well across many independent out-of-sample windows, that is much stronger evidence of a genuine edge.
StrategyWalkForwardExecutor is the highest-level entry point for walk-forward testing a single strategy. Its API mirrors BarSeriesManager, so the transition from backtest to walk-forward is minimal.
1
Create the executor
// Simple: uses default cost models and next-open executionStrategyWalkForwardExecutor executor = new StrategyWalkForwardExecutor(series);
Or with custom cost and execution models:
StrategyWalkForwardExecutor executor = new StrategyWalkForwardExecutor( series, new LinearTransactionCostModel(0.001), // 0.1% fee per trade new ZeroCostModel(), new SlippageExecutionModel(series.numFactory().numOf(0.0005)));
2
Build the walk-forward configuration
// Use the series-aware default configurationWalkForwardConfig config = WalkForwardConfig.defaultConfig(series);
Or specify fold geometry explicitly:
WalkForwardConfig config = new WalkForwardConfig( 120, // minTrainBars: minimum in-sample bars per fold 40, // testBars: out-of-sample bars per fold 20, // stepBars: how many bars to advance between folds 1, // purgeBars: bars purged before each test fold 1, // embargoBars: gap between train end and test start 40, // holdoutBars: trailing bars reserved for final holdout 15, // primaryHorizonBars: horizon for optimization scoring List.of(7, 30), // reportingHorizons 3, // optimizationTopK List.of(1, 5), // reportingTopKs 42L); // seed for reproducibility
3
Run walk-forward
StrategyWalkForwardExecutionResult result = executor.execute(strategy, config);
BacktestExecutor supports running backtest-only, walk-forward-only, or both in one call. Use this when comparing many strategies and you want both full-series and fold-level results side by side.
Understanding how folds are constructed helps you choose parameters that avoid overfitting in the fold construction itself.
|<--- minTrainBars (in-sample) --->|purgeBars|embargo|<- testBars (OOS) ->| ↑ ↑ train end test end
After each fold, the window advances by stepBars bars. The holdoutBars parameter reserves a final set of trailing bars that are never part of any training fold and serve as a final out-of-sample validation.
WalkForwardConfig fields
Field
Default
Description
minTrainBars
120
Minimum in-sample bars required per fold
testBars
40
Out-of-sample bars per test fold
stepBars
20
Bars to advance between folds
purgeBars
1
Bars purged at the boundary to avoid look-ahead
embargoBars
1
Gap between train end and test start
holdoutBars
40
Trailing bars reserved for final holdout validation
WalkForwardEngine is the generic engine underlying the strategy-oriented executors. Use it directly when your prediction pipeline is not a ta4j Strategy — for example when you want to walk-forward test a machine-learning classifier or a ranking model.
WalkForwardEngine<MyContext, MyPrediction, MyOutcome> engine = new WalkForwardEngine<>( splitter, predictionProvider, outcomeLabeler, List.of(accuracyMetric, precisionMetric));WalkForwardRunResult<MyPrediction, MyOutcome> result = engine.run(series, context, config);// Global metrics for the primary horizonMap<String, Num> globalMetrics = result.globalMetricsForHorizon(config.primaryHorizonBars());// Per-fold metricsMap<String, Map<String, Num>> foldMetrics = result.foldMetricsForHorizon(config.primaryHorizonBars());
The WalkForward example evaluates four strategies (CCI Correction, Global Extrema, Moving Momentum, RSI-2) on Bitstamp Bitcoin data, ranks them by average out-of-sample gross return, then runs the winner through BacktestExecutor.executeWithWalkForward(...) for a combined backtest and walk-forward summary.
Keep your WalkForwardConfig fixed across all candidates in a single study. Changing fold geometry or horizon settings between runs makes the metric values incomparable.
Walk-forward analysis reduces — but does not eliminate — the risk of overfitting. A strategy that survives many out-of-sample windows is stronger evidence of a genuine edge, but past performance still does not guarantee future results.