Files
goldenChart/ta4j-master/ta4j-examples/src/main/java/ta4jexamples/backtesting/MovingAverageCrossOverRangeBacktest.java
T
2026-05-23 15:11:48 +09:00

154 lines
6.9 KiB
Java

/*
* 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.BacktestExecutor;
import org.ta4j.core.indicators.KalmanFilterIndicator;
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.CrossedDownIndicatorRule;
import org.ta4j.core.rules.CrossedUpIndicatorRule;
import ta4jexamples.datasources.JsonFileBarSeriesDataSource;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
public class MovingAverageCrossOverRangeBacktest {
private static final Logger LOG = LogManager.getLogger(MovingAverageCrossOverRangeBacktest.class);
private static final int DEFAULT_DECIMAL_PRECISION = 16;
public static void main(String[] args) {
DecimalNum.configureDefaultPrecision(DEFAULT_DECIMAL_PRECISION);
String resourceName = "Binance-ETH-USD-PT5M-20230313_20230315.json";
InputStream resourceStream = MovingAverageCrossOverRangeBacktest.class.getClassLoader()
.getResourceAsStream(resourceName);
if (resourceStream == null) {
LOG.error("File not found in classpath: {}", resourceName);
return;
}
BarSeries series = JsonFileBarSeriesDataSource.DEFAULT_INSTANCE.loadSeries(resourceStream);
if (series == null || series.isEmpty()) {
LOG.error("Bar series was null or empty: {}", series);
return;
}
int barCountStart = 3;
int barCountStop = 200;
int barCountStep = 3;
final List<Strategy> strategies = new ArrayList<>();
for (int shortBarCount = barCountStart; shortBarCount <= barCountStop; shortBarCount += barCountStep) {
for (int longBarCount = shortBarCount
+ barCountStep; longBarCount <= barCountStop; longBarCount += barCountStep) {
String strategyName = String.format("Sma(%d) CrossOver Sma(%d)", shortBarCount, longBarCount);
strategies.add(
new BaseStrategy(strategyName, createSmaCrossEntryRule(series, shortBarCount, longBarCount),
createSmaCrossExitRule(series, shortBarCount, longBarCount)));
}
}
Instant startInstant = Instant.now();
BacktestExecutor backtestExecutor = new BacktestExecutor(series);
List<TradingStatement> tradingStatements = backtestExecutor.execute(strategies, DecimalNum.valueOf(50),
Trade.TradeType.BUY);
LOG.debug("Back-tested {} strategies on {}-bar series using decimal precision of {} in {}", strategies.size(),
series.getBarCount(), DEFAULT_DECIMAL_PRECISION, Duration.between(startInstant, Instant.now()));
LOG.debug(printReport(tradingStatements));
}
private static Rule createSmaCrossEntryRule(BarSeries series, int shortBarCount, int longBarCount) {
Indicator<Num> closePrice = new ClosePriceIndicator(series);
KalmanFilterIndicator kalmanFilteredClosePrice = new KalmanFilterIndicator(closePrice);
SMAIndicator smaShort = new SMAIndicator(kalmanFilteredClosePrice, shortBarCount);
SMAIndicator smaLong = new SMAIndicator(kalmanFilteredClosePrice, longBarCount);
return new CrossedUpIndicatorRule(smaShort, smaLong);
}
private static Rule createSmaCrossExitRule(BarSeries series, int shortBarCount, int longBarCount) {
Indicator<Num> closePrice = new ClosePriceIndicator(series);
KalmanFilterIndicator kalmanFilteredClosePrice = new KalmanFilterIndicator(closePrice);
SMAIndicator smaShort = new SMAIndicator(kalmanFilteredClosePrice, shortBarCount);
SMAIndicator smaLong = new SMAIndicator(kalmanFilteredClosePrice, longBarCount);
return new CrossedDownIndicatorRule(smaShort, smaLong);
}
private static String printReport(List<TradingStatement> tradingStatements) {
StringJoiner resultJoiner = new StringJoiner(System.lineSeparator());
for (TradingStatement statement : tradingStatements) {
resultJoiner.add(printStatementReport(statement).toString());
}
return resultJoiner.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;
}
}