/*
* SPDX-License-Identifier: MIT
*/
package ta4jexamples.backtesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ta4j.core.*;
import org.ta4j.core.backtest.BacktestExecutionResult;
import org.ta4j.core.backtest.BacktestExecutor;
import org.ta4j.core.backtest.TradingStatementExecutionResult.WeightedCriterion;
import org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion;
import org.ta4j.core.criteria.pnl.NetProfitCriterion;
import org.ta4j.core.indicators.averages.SMAIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.num.DecimalNum;
import org.ta4j.core.num.Num;
import org.ta4j.core.reports.BasePerformanceReport;
import org.ta4j.core.reports.PositionStatsReport;
import org.ta4j.core.reports.TradingStatement;
import org.ta4j.core.rules.OverIndicatorRule;
import org.ta4j.core.rules.UnderIndicatorRule;
import ta4jexamples.datasources.CsvFileBarSeriesDataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Example demonstrating how to use {@link BacktestExecutor} for parallel
* strategy evaluation.
*
* This example:
*
* - Creates multiple variations of a Simple Moving Average (SMA)
* strategy.
* - Uses {@code BacktestExecutor} to run them in parallel over the data
* series.
* - Ranks the strategies based on a composite {@link WeightedCriterion}.
* - Prints a performance report for the best strategies.
*
*/
public class SimpleMovingAverageRangeBacktest {
private static final Logger LOG = LogManager.getLogger(SimpleMovingAverageRangeBacktest.class);
private static final int DEFAULT_TOP_STRATEGIES = 3;
public static void main(String[] args) {
BarSeries series = CsvFileBarSeriesDataSource.loadSeriesFromFile();
int start = 3;
int stop = 50;
int step = 5;
final List strategies = new ArrayList<>();
for (int i = start; i <= stop; i += step) {
Strategy strategy = new BaseStrategy("Sma(" + i + ")", createEntryRule(series, i),
createExitRule(series, i));
strategies.add(strategy);
}
BacktestExecutor backtestExecutor = new BacktestExecutor(series);
BacktestExecutionResult result = backtestExecutor.executeWithRuntimeReport(strategies, DecimalNum.valueOf(50),
Trade.TradeType.BUY);
List tradingStatements = selectTopStrategies(result, DEFAULT_TOP_STRATEGIES);
LOG.debug("Top {} weighted SMA strategies (7 parts net profit, 3 parts return over max drawdown)",
tradingStatements.size());
LOG.debug(printReport(tradingStatements));
}
/**
* Selects the top strategies for this example using weighted, normalized
* ranking.
*
* @param result full backtest result for the SMA parameter sweep
* @param limit maximum number of strategies to keep
* @return top strategies ordered by the example weighted criteria
*/
static List selectTopStrategies(BacktestExecutionResult result, int limit) {
Objects.requireNonNull(result, "result cannot be null");
return result.getTopStrategiesWeighted(limit, WeightedCriterion.of(new NetProfitCriterion(), 7.0),
WeightedCriterion.of(new ReturnOverMaxDrawdownCriterion(), 3.0));
}
private static Rule createEntryRule(BarSeries series, int barCount) {
Indicator closePrice = new ClosePriceIndicator(series);
SMAIndicator sma = new SMAIndicator(closePrice, barCount);
return new UnderIndicatorRule(sma, closePrice);
}
private static Rule createExitRule(BarSeries series, int barCount) {
Indicator closePrice = new ClosePriceIndicator(series);
SMAIndicator sma = new SMAIndicator(closePrice, barCount);
return new OverIndicatorRule(sma, closePrice);
}
private static String printReport(List tradingStatements) {
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append(System.lineSeparator());
for (TradingStatement statement : tradingStatements) {
resultBuilder.append(printStatementReport(statement));
resultBuilder.append(System.lineSeparator());
}
return resultBuilder.toString();
}
private static StringBuilder printStatementReport(TradingStatement statement) {
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("######### ")
.append(statement.getStrategy().getName())
.append(" #########")
.append(System.lineSeparator())
.append(printPerformanceReport(statement.getPerformanceReport()))
.append(System.lineSeparator())
.append(printPositionStats(statement.getPositionStatsReport()))
.append(System.lineSeparator())
.append("###########################");
return resultBuilder;
}
private static StringBuilder printPerformanceReport(BasePerformanceReport report) {
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("--------- performance report ---------")
.append(System.lineSeparator())
.append("total loss: ")
.append(report.totalLoss)
.append(System.lineSeparator())
.append("total profit: ")
.append(report.totalProfit)
.append(System.lineSeparator())
.append("total profit loss: ")
.append(report.totalProfitLoss)
.append(System.lineSeparator())
.append("total profit loss percentage: ")
.append(report.totalProfitLossPercentage)
.append(System.lineSeparator())
.append("---------------------------");
return resultBuilder;
}
private static StringBuilder printPositionStats(PositionStatsReport report) {
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("--------- trade statistics report ---------")
.append(System.lineSeparator())
.append("loss trade count: ")
.append(report.getLossCount())
.append(System.lineSeparator())
.append("profit trade count: ")
.append(report.getProfitCount())
.append(System.lineSeparator())
.append("break even trade count: ")
.append(report.getBreakEvenCount())
.append(System.lineSeparator())
.append("---------------------------");
return resultBuilder;
}
}