Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ta4j/ta4j/llms.txt

Use this file to discover all available pages before exploring further.

Risk-adjusted criteria measure how much return a strategy earns per unit of risk. They are essential for comparing strategies that achieve similar raw returns but with very different risk profiles.

Sharpe ratio

Description: Measures excess return per unit of total volatility (standard deviation of returns).Formula: SR = mean(excessReturn) / stdev(excessReturn)Where excessReturn is the return above the risk-free rate for each sampled period. Higher is better.Constructor signatures:
// Zero risk-free rate, per-bar sampling, annualized
new SharpeRatioCriterion()

// Custom annual risk-free rate
new SharpeRatioCriterion(double annualRiskFreeRate)

// Full control over sampling and annualization
new SharpeRatioCriterion(double annualRiskFreeRate,
                          SamplingFrequency samplingFrequency,
                          Annualization annualization,
                          ZoneId groupingZoneId)
Parameters:
ParameterDescription
annualRiskFreeRateAnnualized risk-free rate (e.g., 0.05 for 5%). Defaults to 0.
samplingFrequencyReturn aggregation: BAR, DAY, WEEK, MONTH, etc.
annualizationANNUALIZED scales the ratio by √periodsPerYear; PERIOD leaves it unscaled.
groupingZoneIdTime zone for period boundary detection.
Interpretation: SR > 1 is generally acceptable; SR > 2 is good; SR > 3 is excellent. Values below 1 suggest returns do not adequately compensate for volatility.
// Annualized Sharpe with 2% risk-free rate, monthly sampling
SharpeRatioCriterion sharpe = new SharpeRatioCriterion(
        0.02,
        SamplingFrequency.MONTH,
        Annualization.ANNUALIZED,
        ZoneId.of("America/New_York"));

Num sr = sharpe.calculate(series, record);
SharpeRatioCriterion requires at least 2 samples. It returns 0 when fewer are available.

Sortino ratio

Description: Like the Sharpe ratio, but uses only downside deviation (negative excess returns) in the denominator. This avoids penalizing upside volatility.Formula: Sortino = mean(excessReturn) / downsideDeviationWhere downsideDeviation = sqrt(mean(min(excessReturn, 0)²)).Constructor signatures:
new SortinoRatioCriterion()
new SortinoRatioCriterion(double annualRiskFreeRate)
new SortinoRatioCriterion(double annualRiskFreeRate,
                           SamplingFrequency samplingFrequency,
                           Annualization annualization,
                           ZoneId groupingZoneId)
Interpretation: Same scale as Sharpe. Because it ignores upside volatility, Sortino is generally higher than Sharpe for the same strategy. Returns NaN when there is no downside deviation (all returns non-negative).
SortinoRatioCriterion sortino = new SortinoRatioCriterion(0.02);
Num sr = sortino.calculate(series, record);

Calmar ratio

Description: Compares annualized return (CAGR) against maximum drawdown. Particularly useful for evaluating trend-following strategies where drawdowns can be large but infrequent.Formula: Calmar = CAGR / maximumDrawdownConstructor signatures:
new CalmarRatioCriterion()
new CalmarRatioCriterion(EquityCurveMode equityCurveMode)
new CalmarRatioCriterion(OpenPositionHandling openPositionHandling)
new CalmarRatioCriterion(ReturnRepresentation returnRepresentation)
new CalmarRatioCriterion(ReturnRepresentation returnRepresentation,
                          EquityCurveMode equityCurveMode,
                          OpenPositionHandling openPositionHandling)
Interpretation: Higher is better. A Calmar ratio of 1.0 means the strategy earns one full maximum drawdown per year. A ratio of 3+ is considered strong. Returns the annualized return directly when maximum drawdown is zero.
CalmarRatioCriterion calmar = new CalmarRatioCriterion();
Num cr = calmar.calculate(series, record);
The Calmar ratio is especially meaningful over 3-year rolling windows, as it gives large drawdowns enough time to recover.

Omega ratio

Description: Computes the ratio of aggregate upside excess returns to aggregate downside shortfalls relative to a threshold. Unlike Sharpe, Omega uses the entire return distribution, not just mean and variance.Formula:
Omega(τ) = Σ max(r_i − τ, 0)  /  Σ max(τ − r_i, 0)
Constructor signatures:
new OmegaRatioCriterion()                          // zero threshold
new OmegaRatioCriterion(double threshold)          // custom threshold (decimal)
new OmegaRatioCriterion(double threshold,
                         ReturnRepresentation returnRepresentation)
new OmegaRatioCriterion(double threshold,
                         EquityCurveMode equityCurveMode,
                         OpenPositionHandling openPositionHandling)
Parameters:
ParameterDescription
thresholdReturn threshold in decimal form (e.g., 0.01 for 1%). Defaults to 0.
Interpretation: Omega > 1 means upside exceeds downside relative to the threshold. At threshold = 0, Omega > 1 is equivalent to a positive expected return. Returns NaN when downside shortfall is zero but upside excess exists (theoretically infinite).
// Require returns to exceed 0.5% per bar on average
OmegaRatioCriterion omega = new OmegaRatioCriterion(0.005);
Num or = omega.calculate(series, record);

Return over maximum drawdown (RoMaD)

Description: Total net return divided by maximum drawdown. A simpler drawdown-adjusted return metric than Calmar (no annualization).Formula: RoMaD = netReturn / maximumDrawdown
import org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion;

ReturnOverMaxDrawdownCriterion roMaD = new ReturnOverMaxDrawdownCriterion();
Num value = roMaD.calculate(series, record);
Interpretation: Higher is better. A value of 5 means the strategy returned 5× its maximum drawdown. This is one of the most commonly used risk-adjusted metrics in systematic trading.
Use org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion. The top-level alias at org.ta4j.core.criteria.ReturnOverMaxDrawdownCriterion is deprecated since 0.19 and will be removed in 0.24.0.

System Quality Number (SQN)

Description: Van Tharp’s System Quality Number measures the consistency of a trading system by comparing the average trade profit to its standard deviation across trades.Formula: SQN = (avgPnL / stdev(PnL)) × √(numberOfTrades)Constructor signatures:
new SqnCriterion()                                 // uses NetProfitLossCriterion
new SqnCriterion(AnalysisCriterion criterion)      // custom per-trade metric
new SqnCriterion(AnalysisCriterion criterion,
                 Integer nPositions)               // cap positions at N for √N term
Interpretation:
SQNSystem quality
< 1.6Poor
1.6 – 1.9Below average
2.0 – 2.4Average
2.5 – 2.9Good
3.0 – 5.0Excellent
> 5.0Holy Grail
SqnCriterion sqn = new SqnCriterion();
Num score = sqn.calculate(series, record);
Pass nPositions = 100 to cap the square-root term when your sample size exceeds 100 trades, following Van Tharp’s convention.

Value at Risk (VaR)

Description: The maximum loss not exceeded at a given confidence level over the return distribution. A VaR of -0.02 at 95% confidence means losses exceed 2% only 5% of the time.Constructor signatures:
new ValueAtRiskCriterion(Double confidence)
new ValueAtRiskCriterion(Double confidence, ReturnRepresentation returnRepresentation)
Parameters:
ParameterDescription
confidenceConfidence level as a decimal (e.g., 0.95 for 95%)
returnRepresentationHow the result is expressed — DECIMAL, PERCENTAGE, LOG, or MULTIPLICATIVE
Interpretation: VaR is non-positive. Values closer to zero indicate lower tail risk. Higher criterion values are better (betterThan returns true when the first value is greater).
ValueAtRiskCriterion var95 = new ValueAtRiskCriterion(0.95);
Num tailLoss = var95.calculate(series, record);
// e.g. -0.018 means the 5% worst days lose at least 1.8%

Expected Shortfall (CVaR)

Description: Also known as Conditional Value at Risk (CVaR). Measures the average loss in the tail beyond the VaR threshold — capturing the severity of extreme losses, not just their frequency.Constructor signatures:
new ExpectedShortfallCriterion(double confidence)
new ExpectedShortfallCriterion(double confidence, ReturnRepresentation returnRepresentation)
Interpretation: Like VaR, Expected Shortfall is non-positive. It is always ≤ VaR at the same confidence level (more conservative). Values closer to zero indicate lower average tail loss.
ExpectedShortfallCriterion cvar = new ExpectedShortfallCriterion(0.95);
Num avgTailLoss = cvar.calculate(series, record);
Expected Shortfall is considered a more robust tail-risk measure than VaR because it accounts for the full shape of the loss distribution beyond the threshold.

R-multiple

Description: Measures each trade’s profit relative to the initial risk (defined by a PositionRiskModel, typically a stop-loss distance). Average R-multiple across all trades summarizes how well the strategy captures reward relative to its defined risk per trade.Formula: R-multiple = profit / risk per position; averaged across all valid positions.Constructor signature:
new RMultipleCriterion(PositionRiskModel riskModel)
Parameters:
ParameterDescription
riskModelProvides the per-trade risk amount — e.g., StopLossPositionRiskModel
Interpretation: R > 1 on average means winners are larger than the initial risk. A system with an average R of 2 earns 2× its risk on each winning trade on average.
import org.ta4j.core.criteria.risk.RMultipleCriterion;
import org.ta4j.core.criteria.risk.StopLossPositionRiskModel;

StopLossRule stopRule = new StopLossRule(close, 2.0);
RMultipleCriterion rMultiple = new RMultipleCriterion(
        new StopLossPositionRiskModel(stopRule));

Num avgR = rMultiple.calculate(series, record);

Strategy ranking with weighted criteria

Use WeightedCriterion inside BacktestExecutor to rank strategies by a composite score that balances multiple criteria:
import org.ta4j.core.*;
import org.ta4j.core.backtest.*;
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
import org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion;

// Run a parameter sweep across many strategies
BacktestExecutionResult result = new BacktestExecutor(series)
        .executeWithRuntimeReport(
                strategies,
                series.numFactory().numOf(1),
                Trade.TradeType.BUY,
                ProgressCompletion.loggingWithMemory());

// Weight net profit at 70% and RoMaD at 30%
// (Weights are normalized internally — 7/3 and 0.7/0.3 are equivalent)
List<TradingStatement> top = result.getTopStrategiesWeighted(10,
        WeightedCriterion.of(new NetProfitCriterion(), 7.0),
        WeightedCriterion.of(new ReturnOverMaxDrawdownCriterion(), 3.0));

top.forEach(statement -> {
    Num profit = statement.getCriterionScore(new NetProfitCriterion())
                          .orElse(series.numOf(0));
    Num roMaD  = statement.getCriterionScore(new ReturnOverMaxDrawdownCriterion())
                          .orElse(series.numOf(0));
    System.out.printf("%-40s  profit=%.4f  RoMaD=%.4f%n",
            statement.getStrategy().getName(),
            profit.doubleValue(),
            roMaD.doubleValue());
});
A high Sharpe or Sortino ratio during in-sample optimization is a common over-fitting signal. Always validate on out-of-sample data before drawing conclusions.