Visualize strategies with candlestick charts, indicator overlays, and performance subcharts using ChartWorkflow.
ta4j includes a built-in charting layer on top of JFreeChart. The ChartWorkflow builder API assembles price data, indicator overlays, trading signals, and performance subcharts into a single chart with a consistent visual style.
The charting API lives in the ta4j-examples module. Add ta4j-examples as a dependency to use it.
Draws an indicator as a line on the main price panel. Use this for indicators that operate on the same scale as price (moving averages, Bollinger Bands, VWAP, etc.).
Adds an indicator in a separate panel below the price chart. Use this for indicators with a different scale than price, such as RSI (0–100), MACD, or volume.
ChartWorkflow chartWorkflow = new ChartWorkflow();JFreeChart chart = chartWorkflow.builder() .withTitle("RSI Strategy with Subchart") .withSeries(series) .withTradingRecordOverlay(record) .withSubChart(rsi) // RSI in its own panel, separate scale .toChart();
Adds a performance criterion as a subchart. The criterion is evaluated bar by bar across the trading record, giving you a time-series view of metrics like maximum drawdown or net profit.
ClosePriceIndicator close = new ClosePriceIndicator(series);RSIIndicator rsi = new RSIIndicator(close, 14);Rule entry = new CrossedDownIndicatorRule(rsi, 30); // oversoldRule exit = new CrossedUpIndicatorRule(rsi, 70); // overboughtStrategy strategy = new BaseStrategy("RSI Strategy", entry, exit);TradingRecord record = new BarSeriesManager(series).run(strategy);ChartWorkflow chartWorkflow = new ChartWorkflow();JFreeChart chart = chartWorkflow.builder() .withTitle("RSI Strategy with Subchart") .withSeries(series) .withTradingRecordOverlay(record) .withSubChart(rsi) .toChart();
TimeAxisMode.BAR_INDEX compresses non-trading periods (weekends, holidays) by mapping each bar to a sequential integer on the x-axis. The underlying bar timestamps remain unchanged.Use it when you want to avoid the visual whitespace that results from gaps in daily stock data:
Call toPlan() instead of toChart() to obtain the underlying chart plan. This lets you inspect or modify the configuration — including the shared title, domain series, and time axis mode — before rendering.
ChartPlan plan = chartWorkflow.builder() .withTitle("My Strategy") .withSeries(series) .withTradingRecordOverlay(record) .toPlan();// Inspect what will be renderedplan.context(); // shared title, time axis modeplan.metadata(); // chart structure details// Render when readyJFreeChart chart = plan.toChart();
Performance metrics
Learn which criteria can be plotted as subcharts.
Parallel backtesting
Run strategy sweeps before charting the best performers.