diff --git a/backend/src/main/java/com/goldenchart/service/BacktestingService.java b/backend/src/main/java/com/goldenchart/service/BacktestingService.java index acc511e..fe34790 100644 --- a/backend/src/main/java/com/goldenchart/service/BacktestingService.java +++ b/backend/src/main/java/com/goldenchart/service/BacktestingService.java @@ -88,6 +88,14 @@ public class BacktestingService { } // 요청에 포함된 파라미터가 있으면 DB 값 위에 덮어씀 (요청값 우선) Map> params = mergeIndicatorParams(dbParams, req.getIndicatorParams()); + log.info("[Backtest] 인디케이터 파라미터 로드 — deviceId={}, dbParams키={}, reqParams키={}, 최종키={}", + req.getDeviceId(), + dbParams.keySet(), + req.getIndicatorParams() != null ? req.getIndicatorParams().keySet() : "null", + params.keySet()); + if (params.containsKey("Stochastic")) { + log.info("[Backtest] Stochastic 파라미터: {}", params.get("Stochastic")); + } if (params.isEmpty()) { log.warn("[Backtest] 인디케이터 파라미터 없음 — deviceId={}, 지표 기본값 사용 가능성 있음", req.getDeviceId()); } @@ -153,6 +161,8 @@ public class BacktestingService { // positionMode: LONG_ONLY(기본) | SIGNAL_ONLY(포지션 락 우회) String posMode = cfg.getPositionMode() != null ? cfg.getPositionMode() : "LONG_ONLY"; boolean signalOnly = "SIGNAL_ONLY".equals(posMode); + log.info("[Backtest] 실행 시작 — strategyId={}, bars={}, timeframe={}, positionMode={}, signalOnly={}", + req.getStrategyId(), series.getBarCount(), req.getTimeframe(), posMode, signalOnly); double initCap = cfg.getInitialCapital() != null ? cfg.getInitialCapital().doubleValue() : 10_000_000.0; double tradeSizePct = cfg.getTradeSizeValue() != null ? cfg.getTradeSizeValue().doubleValue() / 100.0 : 1.0; @@ -175,15 +185,19 @@ public class BacktestingService { // ── SIGNAL_ONLY 모드: 포지션·자금 상태 무관, 조건 충족 시마다 전체 시그널 생성 ── if (signalOnly) { - if (entryRule.isSatisfied(i)) { + boolean entrySatisfied = entryRule.isSatisfied(i); + boolean exitSatisfied = exitRule.isSatisfied(i); + if (entrySatisfied) { double effEntry = applySlippage(closePrice, cfg, true); String buyType = "SHORT".equals(direction) ? "SHORT_ENTRY" : "BUY"; signals.add(buildFillSignal(time, buyType, effEntry, i, 1.0)); + log.debug("[Backtest:SIGNAL_ONLY] BUY @ bar={} price={}", i, effEntry); } - if (exitRule.isSatisfied(i)) { + if (exitSatisfied) { double effExit = applySlippage(exitPrice, cfg, false); String sellType = "SHORT".equals(direction) ? "SHORT_EXIT" : "SELL"; signals.add(buildFillSignal(time, sellType, effExit, i, 1.0)); + log.debug("[Backtest:SIGNAL_ONLY] SELL @ bar={} price={}", i, effExit); } continue; } @@ -248,6 +262,11 @@ public class BacktestingService { if (useLedger) ledger.markToMarket(closePrice); } + long buyCnt = signals.stream().filter(s -> "BUY".equals(s.type()) || "SHORT_ENTRY".equals(s.type())).count(); + long sellCnt = signals.stream().filter(s -> "SELL".equals(s.type()) || "SHORT_EXIT".equals(s.type())).count(); + log.info("[Backtest] 완료 — strategyId={}, bars={}, 생성시그널={}개 (매수={}, 매도={}), positionMode={}", + req.getStrategyId(), barCount, signals.size(), buyCnt, sellCnt, posMode); + double lastMarkPrice = barCount > 0 ? series.getBar(barCount - 1).getClosePrice().doubleValue() : 0.0; double finalEquity = resolveFinalEquity(ledger, useLedger, cfg, initCap, equity, inPosition,