Skip to main content
Every metric in ta4j is an AnalysisCriterion. The interface is intentionally small: pass a BarSeries and a TradingRecord, get back a Num.

The AnalysisCriterion interface

public interface AnalysisCriterion {
    Num calculate(BarSeries series, TradingRecord tradingRecord);
    Num calculate(BarSeries series, Position position);
    boolean betterThan(Num criterionValue1, Num criterionValue2);
}
betterThan defines the direction of improvement for ranking — higher is better for return criteria, lower is better for drawdown criteria. This is used automatically when comparing strategies in BacktestExecutor.

Calculating multiple metrics

import org.ta4j.core.criteria.pnl.NetReturnCriterion;
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
import org.ta4j.core.criteria.pnl.GrossReturnCriterion;
import org.ta4j.core.criteria.drawdown.MaximumDrawdownCriterion;
import org.ta4j.core.criteria.SharpeRatioCriterion;
import org.ta4j.core.criteria.ReturnOverMaxDrawdownCriterion;
import org.ta4j.core.cost.LinearTransactionCostModel;
import org.ta4j.core.cost.LinearBorrowingCostModel;

// Run backtest with realistic trading costs
TradingRecord record = new BarSeriesManager(series,
        new LinearTransactionCostModel(0.001),   // 0.1% fee per trade
        new LinearBorrowingCostModel(0.0001))    // 0.01% borrowing cost
        .run(strategy);

// Calculate and print metrics
System.out.printf("Trades executed: %d%n", record.getTradeCount());

System.out.printf("Net return: %.2f%%%n",
        new NetReturnCriterion()
                .calculate(series, record)
                .multipliedBy(series.numOf(100)));

System.out.printf("Max drawdown: %.2f%%%n",
        new MaximumDrawdownCriterion()
                .calculate(series, record)
                .multipliedBy(series.numOf(100)));

System.out.printf("Sharpe ratio: %s%n",
        new SharpeRatioCriterion().calculate(series, record));

Criteria by category

These measure absolute and relative profitability.
ClassPackageDescription
NetReturnCriterioncriteria.pnlTotal return after all costs
NetProfitCriterioncriteria.pnlNet profit in currency units
GrossReturnCriterioncriteria.pnlReturn before costs
NetProfitLossCriterioncriteria.pnlNet profit or loss per position
import org.ta4j.core.criteria.pnl.NetReturnCriterion;
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
import org.ta4j.core.criteria.pnl.GrossReturnCriterion;

Num netReturn  = new NetReturnCriterion().calculate(series, record);
Num netProfit  = new NetProfitCriterion().calculate(series, record);
Num grossReturn = new GrossReturnCriterion().calculate(series, record);
These reward strategies that generate returns efficiently relative to the risk taken.
ClassPackageDescription
SharpeRatioCriterioncriteriaExcess return per unit of volatility
SortinoRatioCriterioncriteriaLike Sharpe, but penalises only downside volatility
CalmarRatioCriterioncriteriaAnnualised return divided by maximum drawdown
OmegaRatioCriterioncriteriaProbability-weighted ratio of gains to losses
import org.ta4j.core.criteria.SharpeRatioCriterion;
import org.ta4j.core.criteria.SortinoRatioCriterion;
import org.ta4j.core.criteria.CalmarRatioCriterion;

// Default: zero risk-free rate, per-bar sampling, annualised
Num sharpe  = new SharpeRatioCriterion().calculate(series, record);

// With a 5% annual risk-free rate
Num sharpe5 = new SharpeRatioCriterion(0.05).calculate(series, record);

Num sortino = new SortinoRatioCriterion().calculate(series, record);
Num calmar  = new CalmarRatioCriterion().calculate(series, record);
SharpeRatioCriterion supports advanced configuration: sampling frequency (per-bar, daily, weekly, monthly), annualisation mode, risk-free rate, cash return policy, and equity curve mode.
These measure the severity of equity declines during the backtest period.
ClassPackageDescription
MaximumDrawdownCriterioncriteria.drawdownLargest peak-to-trough equity decline
AverageDrawdownCriterioncriteria.drawdownMean drawdown across all positions
MaximumDrawdownDurationCriterioncriteria.drawdownLongest bars spent in drawdown
ReturnOverMaxDrawdownCriterioncriteriaNet return divided by maximum drawdown
import org.ta4j.core.criteria.drawdown.MaximumDrawdownCriterion;
import org.ta4j.core.criteria.ReturnOverMaxDrawdownCriterion;

Num maxDD  = new MaximumDrawdownCriterion().calculate(series, record);
Num romad  = new ReturnOverMaxDrawdownCriterion().calculate(series, record);
These count and characterise the individual trades recorded.
ClassPackageDescription
NumberOfPositionsCriterioncriteriaTotal number of completed positions
NumberOfWinningPositionsCriterioncriteriaCount of profitable positions
NumberOfLosingPositionsCriterioncriteriaCount of losing positions
WinningPositionsRatioCriterioncriteriaWin rate (winning / total)
ExpectancyCriterioncriteriaExpected value per trade
AverageProfitLossCriterioncriteriaMean profit/loss across positions
import org.ta4j.core.criteria.NumberOfPositionsCriterion;
import org.ta4j.core.criteria.WinningPositionsRatioCriterion;
import org.ta4j.core.criteria.ExpectancyCriterion;

Num positions = new NumberOfPositionsCriterion().calculate(series, record);
Num winRate   = new WinningPositionsRatioCriterion().calculate(series, record);
Num expectancy = new ExpectancyCriterion().calculate(series, record);

Windowed analysis

AnalysisWindow lets you calculate a criterion over a specific date range or bar range, rather than the entire series.
import org.ta4j.core.analysis.AnalysisWindow;
import java.time.Duration;
import java.time.Instant;

AnalysisCriterion sharpe = new SharpeRatioCriterion();

// Past 30 days
Num last30 = sharpe.calculate(series, record,
        AnalysisWindow.lookbackDuration(Duration.ofDays(30)));

// Explicit date range
Num q1 = sharpe.calculate(series, record,
        AnalysisWindow.timeRange(
                Instant.parse("2023-01-01T00:00:00Z"),
                Instant.parse("2023-04-01T00:00:00Z")));

// Specific bar indices
Num window = sharpe.calculate(series, record,
        AnalysisWindow.barRange(100, 200));

// Past N bars
Num recent = sharpe.calculate(series, record,
        AnalysisWindow.lookbackBars(50));
For time range windows, start is inclusive and end is exclusive. Bar range windows are both inclusive.

WeightedCriterion for composite ranking

When comparing many strategies, use WeightedCriterion to produce a single composite score that combines multiple criteria:
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
import org.ta4j.core.criteria.ReturnOverMaxDrawdownCriterion;
import org.ta4j.core.backtest.BacktestExecutor;
import org.ta4j.core.backtest.BacktestExecutionResult;
import org.ta4j.core.backtest.ProgressCompletion;
import org.ta4j.core.reports.TradingStatement;

BacktestExecutionResult result = new BacktestExecutor(series)
        .executeWithRuntimeReport(strategies,
                series.numFactory().numOf(1),
                Trade.TradeType.BUY,
                ProgressCompletion.loggingWithMemory());

// Weight net profit at 70%, return over max drawdown at 30%
// Weights are normalized internally, so 7/3 and 0.7/0.3 are equivalent
AnalysisCriterion netProfit  = new NetProfitCriterion();
AnalysisCriterion romad      = new ReturnOverMaxDrawdownCriterion();

List<TradingStatement> top10 = result.getTopStrategiesWeighted(10,
        WeightedCriterion.of(netProfit, 7.0),
        WeightedCriterion.of(romad, 3.0));

top10.forEach(statement -> {
    System.out.printf("Strategy: %s, Net Profit: %s, RoMaD: %s%n",
            statement.getStrategy().getName(),
            statement.getCriterionScore(netProfit).orElse(series.numOf(0)),
            statement.getCriterionScore(romad).orElse(series.numOf(0)));
});
TradingStatement.getCriterionScore(criterion) returns the pre-computed value attached during ranking, so you do not need to recalculate each metric separately after getting the top results.

Parallel backtesting

Run hundreds of strategies and rank them by composite score.

Charting

Visualise criteria like maximum drawdown directly in subcharts.