분석레포트 수익률, 승률 계산오류 수정
This commit is contained in:
@@ -146,6 +146,8 @@ public class BacktestingService {
|
||||
boolean partialDone = false;
|
||||
|
||||
double equity = initCap;
|
||||
final double[] simGrossProfit = {0.0};
|
||||
final double[] simGrossLoss = {0.0};
|
||||
|
||||
int barCount = series.getBarCount();
|
||||
|
||||
@@ -185,7 +187,8 @@ public class BacktestingService {
|
||||
? (entryPrice - effExit) / entryPrice
|
||||
: (effExit - entryPrice) / entryPrice;
|
||||
double size = partialDone ? (1.0 - partialPct) : 1.0;
|
||||
equity += equity * tradeSizePct * size * (rawReturn - commission);
|
||||
equity = applyEquityPnl(equity, tradeSizePct, size, rawReturn - commission,
|
||||
simGrossProfit, simGrossLoss);
|
||||
Num numExitPrice = series.numFactory().numOf(effExit);
|
||||
Num numShares = record.getCurrentPosition().getEntry().getAmount();
|
||||
record.exit(i, numExitPrice, numShares);
|
||||
@@ -231,7 +234,8 @@ public class BacktestingService {
|
||||
? (entryPrice - effExit) / entryPrice
|
||||
: (effExit - entryPrice) / entryPrice;
|
||||
double commission = calcCommissionRate(cfg) * 2;
|
||||
equity += equity * tradeSizePct * partialPct * (partReturn - commission);
|
||||
equity = applyEquityPnl(equity, tradeSizePct, partialPct, partReturn - commission,
|
||||
simGrossProfit, simGrossLoss);
|
||||
|
||||
signals.add(Signal.builder()
|
||||
.time(time).type("PARTIAL_SELL").price(effExit).barIndex(i).build());
|
||||
@@ -248,7 +252,8 @@ public class BacktestingService {
|
||||
double netReturn = rawReturn - commission;
|
||||
|
||||
double size = partialDone ? (1.0 - partialPct) : 1.0;
|
||||
equity += equity * tradeSizePct * size * netReturn;
|
||||
equity = applyEquityPnl(equity, tradeSizePct, size, netReturn,
|
||||
simGrossProfit, simGrossLoss);
|
||||
|
||||
Num numExitPrice = series.numFactory().numOf(effExit);
|
||||
Num numShares = record.getCurrentPosition().getEntry().getAmount();
|
||||
@@ -266,7 +271,8 @@ public class BacktestingService {
|
||||
}
|
||||
|
||||
// ── AnalysisCriterion 전체 계산 ───────────────────────────────────────
|
||||
BacktestAnalysisDto analysis = calcAnalysis(series, record, cfg, initCap, equity);
|
||||
BacktestAnalysisDto analysis = calcAnalysis(series, record, cfg, initCap, equity,
|
||||
simGrossProfit[0], simGrossLoss[0]);
|
||||
|
||||
// ── Stats (하위 호환) ─────────────────────────────────────────────────
|
||||
Stats stats = toStats(analysis, signals);
|
||||
@@ -285,16 +291,18 @@ public class BacktestingService {
|
||||
// ── Ta4j AnalysisCriterion 전체 계산 ─────────────────────────────────────
|
||||
|
||||
private BacktestAnalysisDto calcAnalysis(BarSeries series, TradingRecord record,
|
||||
BacktestSettingsDto cfg, double initCap, double finalEquity) {
|
||||
BacktestSettingsDto cfg, double initCap, double finalEquity,
|
||||
double simGrossProfit, double simGrossLoss) {
|
||||
BacktestAnalysisDto.BacktestAnalysisDtoBuilder b = BacktestAnalysisDto.builder()
|
||||
.initialCapital(initCap)
|
||||
.finalEquity(finalEquity);
|
||||
|
||||
double totalReturnPct = safeCalc(() -> calcTotalReturnPct(series, record));
|
||||
double grossProfit = safeCalc(() -> calcCriterion("org.ta4j.core.criteria.pnl.GrossProfitCriterion", series, record));
|
||||
double grossLoss = safeCalc(() -> calcCriterion("org.ta4j.core.criteria.pnl.GrossLossCriterion", series, record));
|
||||
double avgReturnPct = safeCalc(() -> calcCriterion("org.ta4j.core.criteria.pnl.AverageProfitLossCriterion", series, record));
|
||||
double profitLossRatio = safeCalc(() -> calcCriterion("org.ta4j.core.criteria.pnl.ProfitLossRatioCriterion", series, record));
|
||||
// equity 시뮬레이션 기준 총 수익률 — Ta4j criterion/fallback 은 개별 거래 수익률 합산으로 부정확
|
||||
double totalReturnPct = initCap > 0 ? (finalEquity - initCap) / initCap : 0.0;
|
||||
double grossProfit = simGrossProfit;
|
||||
double grossLoss = simGrossLoss;
|
||||
double avgReturnPct = 0.0;
|
||||
double profitLossRatio = 0.0;
|
||||
|
||||
int positions = safeCalcInt(() -> calcCriterionInt("org.ta4j.core.criteria.NumberOfPositionsCriterion", series, record));
|
||||
int winning = safeCalcInt(() -> calcCriterionInt("org.ta4j.core.criteria.NumberOfWinningPositionsCriterion", series, record));
|
||||
@@ -325,6 +333,19 @@ public class BacktestingService {
|
||||
}
|
||||
if (winRate == 0 && positions > 0) winRate = (double) winning / positions;
|
||||
|
||||
if (positions > 0) {
|
||||
avgReturnPct = totalReturnPct / positions;
|
||||
} else {
|
||||
avgReturnPct = safeCalc(() -> calcCriterion(
|
||||
"org.ta4j.core.criteria.pnl.AverageProfitLossCriterion", series, record));
|
||||
}
|
||||
if (grossLoss != 0) {
|
||||
profitLossRatio = grossProfit / Math.abs(grossLoss);
|
||||
} else if (grossProfit > 0) {
|
||||
profitLossRatio = safeCalc(() -> calcCriterion(
|
||||
"org.ta4j.core.criteria.pnl.ProfitLossRatioCriterion", series, record));
|
||||
}
|
||||
|
||||
return b
|
||||
.totalReturnPct(totalReturnPct)
|
||||
.totalProfitLoss(totalPnl)
|
||||
@@ -351,24 +372,18 @@ public class BacktestingService {
|
||||
|
||||
// ── 개별 Criterion 계산 헬퍼 ─────────────────────────────────────────────
|
||||
|
||||
private double calcTotalReturnPct(BarSeries series, TradingRecord record) throws Exception {
|
||||
// 총 수익률 = (finalEquity - initialCapital) / initialCapital
|
||||
// TotalProfitLossPercentageCriterion 또는 ReturnCriterion 시도
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.ta4j.core.criteria.pnl.TotalProfitLossPercentageCriterion");
|
||||
AnalysisCriterion criterion = (AnalysisCriterion) cls.getDeclaredConstructor().newInstance();
|
||||
return criterion.calculate(series, record).doubleValue();
|
||||
} catch (Exception ignored) {}
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.ta4j.core.criteria.ReturnCriterion");
|
||||
AnalysisCriterion criterion = (AnalysisCriterion) cls.getDeclaredConstructor().newInstance();
|
||||
return criterion.calculate(series, record).doubleValue() - 1.0;
|
||||
} catch (Exception ignored) {}
|
||||
// fallback: 거래 수익 직접 계산
|
||||
return record.getPositions().stream()
|
||||
.filter(Position::isClosed)
|
||||
.mapToDouble(p -> p.getProfit().doubleValue() / p.getEntry().getNetPrice().doubleValue())
|
||||
.sum();
|
||||
/**
|
||||
* equity 시뮬레이션에 체결 손익을 반영하고, 총 이익/총 손실 누적값을 갱신한다.
|
||||
* @return 갱신된 equity
|
||||
*/
|
||||
private double applyEquityPnl(double equity, double tradeSizePct, double size, double netReturn,
|
||||
double[] grossProfitAcc, double[] grossLossAcc) {
|
||||
double eqBefore = equity;
|
||||
double next = eqBefore + eqBefore * tradeSizePct * size * netReturn;
|
||||
double delta = next - eqBefore;
|
||||
if (delta >= 0) grossProfitAcc[0] += delta;
|
||||
else grossLossAcc[0] += delta;
|
||||
return next;
|
||||
}
|
||||
|
||||
private double calcMaxDrawdown(BarSeries series, TradingRecord record) throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user