Use this file to discover all available pages before exploring further.
Backtesting in ta4j means replaying a Strategy bar-by-bar over a historical BarSeries and recording every entry and exit the strategy would have generated. The result is a TradingRecord you can query for performance metrics.
BarSeriesManager is the backtesting engine. Construct it with a series and optional cost models, then call run():
import org.ta4j.core.backtest.BarSeriesManager;import org.ta4j.core.TradingRecord;BarSeriesManager manager = new BarSeriesManager(series);TradingRecord record = manager.run(strategy);
run() iterates from series.getBeginIndex() to series.getEndIndex() and, for each bar, asks the strategy whether to enter or exit. Trades are recorded in the returned TradingRecord.
// Specify trade type explicitly (default is strategy.getStartingType())TradingRecord longRecord = manager.run(strategy, TradeType.BUY);TradingRecord shortRecord = manager.run(strategy, TradeType.SELL);// Run over a sub-range of the seriesTradingRecord partial = manager.run(strategy, 100, 500);// Specify position size (default is 1 unit)Num amount = series.numFactory().numOf(10);TradingRecord sized = manager.run(strategy, TradeType.BUY, amount);
Real trading incurs transaction fees and, for leveraged positions, borrowing costs. Pass cost models to BarSeriesManager:
import org.ta4j.core.analysis.cost.LinearTransactionCostModel;import org.ta4j.core.analysis.cost.LinearBorrowingCostModel;BarSeriesManager manager = new BarSeriesManager( series, new LinearTransactionCostModel(0.001), // 0.1% fee per trade new LinearBorrowingCostModel(0.0001) // 0.01% holding cost per period);TradingRecord record = manager.run(strategy);
LinearTransactionCostModel scales fee by rate × tradeValue. LinearBorrowingCostModel scales borrowing cost by rate × positionValue × barsHeld. Both implement the CostModel interface so you can supply custom implementations.
The default execution model is TradeOnNextOpenModel, which fills orders at the open of the bar following the signal bar. This is the most conservative model and avoids look-ahead bias.
// Fills at the open price of the next bar after the signalBarSeriesManager manager = new BarSeriesManager(series);TradingRecord record = manager.run(strategy);
After run(), query the TradingRecord for trade and position data:
// Number of individual trades (each entry and exit is one trade)List<Trade> trades = record.getTrades();System.out.println("Trades: " + trades.size());// Number of completed positions (entry + matching exit)List<Position> positions = record.getPositions();System.out.println("Positions: " + positions.size());// Last entry and exit tradesTrade lastEntry = record.getLastEntry();Trade lastExit = record.getLastExit();// Current (open) position, if anyPosition current = record.getCurrentPosition();
Single backtests are susceptible to overfitting. BarSeriesManager.runWalkForward() splits the series into in-sample training windows and out-of-sample validation windows:
import org.ta4j.core.walkforward.WalkForwardConfig;import org.ta4j.core.backtest.StrategyWalkForwardExecutionResult;WalkForwardConfig config = WalkForwardConfig.builder() .withInSampleBars(252) // ~1 year of daily bars for training .withOutOfSampleBars(63) // ~1 quarter for validation .build();StrategyWalkForwardExecutionResult wfResult = manager.runWalkForward(strategy, config);
Look-ahead bias: Every indicator or rule that reads data beyond the current bar index introduces look-ahead bias. Ta4j indicators are designed to avoid this — result at index i depends only on bars beginIndex through i. Do not access series.getBar(i + 1) or later inside a rule.
Overfitting: A backtest that looks exceptional across a single time window is likely overfit to that data. Use walk-forward analysis, out-of-sample validation, or hold-out periods before trusting a result.
Survivorship bias: Historical data sources often include only assets that still exist today. Strategies that look good on surviving assets may have failed on assets that were delisted during the test period.
Unrealistic cost assumptions: The default ZeroCostModel applies no fees. Always add realistic transaction costs before drawing conclusions about net profitability.