전략평가 오류 수정
This commit is contained in:
@@ -49,21 +49,27 @@ public class BacktestingController {
|
||||
}
|
||||
|
||||
long userId = TradingControllerSupport.requireRegisteredUser(headers);
|
||||
String deviceId = headers.get("x-device-id");
|
||||
if (deviceId == null || deviceId.isBlank()) {
|
||||
deviceId = req.getDeviceId();
|
||||
}
|
||||
req.setUserId(userId);
|
||||
req.setDeviceId(deviceId);
|
||||
|
||||
java.util.Map<String, java.util.Map<String, Object>> dbParams =
|
||||
indicatorSettingsService.getAll(userId, null);
|
||||
if (!dbParams.isEmpty()) {
|
||||
java.util.Map<String, java.util.Map<String, Object>> merged =
|
||||
new java.util.HashMap<>(dbParams);
|
||||
if (req.getIndicatorParams() != null) {
|
||||
req.getIndicatorParams().forEach((type, params) ->
|
||||
merged.merge(type, params, (dbP, reqP) -> {
|
||||
java.util.Map<String, Object> m = new java.util.HashMap<>(dbP);
|
||||
m.putAll(reqP);
|
||||
return m;
|
||||
})
|
||||
);
|
||||
}
|
||||
indicatorSettingsService.getAll(userId, deviceId);
|
||||
java.util.Map<String, java.util.Map<String, Object>> merged =
|
||||
new java.util.HashMap<>(dbParams);
|
||||
if (req.getIndicatorParams() != null) {
|
||||
req.getIndicatorParams().forEach((type, params) ->
|
||||
merged.merge(type, params, (dbP, reqP) -> {
|
||||
java.util.Map<String, Object> m = new java.util.HashMap<>(dbP);
|
||||
m.putAll(reqP);
|
||||
return m;
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!merged.isEmpty()) {
|
||||
req.setIndicatorParams(merged);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,4 +63,7 @@ public class BacktestRequest {
|
||||
|
||||
/** 디바이스 ID (결과 저장용) */
|
||||
private String deviceId;
|
||||
|
||||
/** 인증 사용자 ID — 컨트롤러에서 설정, 지표 설정 조회용 */
|
||||
private Long userId;
|
||||
}
|
||||
|
||||
@@ -77,13 +77,16 @@ public class BacktestingService {
|
||||
}
|
||||
}
|
||||
|
||||
// DB 저장 인디케이터 설정을 기본으로 로드 (디바이스별 저장값 → 하드코딩 기본값 방지)
|
||||
// DB 저장 인디케이터 설정 — live-conditions/evaluate 와 동일 (userId + deviceId)
|
||||
Map<String, Map<String, Object>> dbParams = Map.of();
|
||||
Map<String, Map<String, Object>> visual = Map.of();
|
||||
if (req.getDeviceId() != null && !req.getDeviceId().isBlank()) {
|
||||
try {
|
||||
dbParams = indicatorSettingsService.getAll(null, req.getDeviceId());
|
||||
dbParams = indicatorSettingsService.getAll(req.getUserId(), req.getDeviceId());
|
||||
visual = indicatorSettingsService.getAllVisual(req.getUserId(), req.getDeviceId());
|
||||
} catch (Exception e) {
|
||||
log.warn("[Backtest] 인디케이터 설정 DB 로드 실패 (deviceId={}): {}", req.getDeviceId(), e.getMessage());
|
||||
log.warn("[Backtest] 인디케이터 설정 DB 로드 실패 (userId={}, deviceId={}): {}",
|
||||
req.getUserId(), req.getDeviceId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
// 요청에 포함된 파라미터가 있으면 DB 값 위에 덮어씀 (요청값 우선)
|
||||
@@ -111,9 +114,12 @@ public class BacktestingService {
|
||||
Map<String, BarSeries> seriesOverrides = buildSeriesOverrides(
|
||||
series, primaryTf, buyDsl, sellDsl);
|
||||
|
||||
Rule entryRule = adapter.toRule(buyDsl, series, params, seriesOverrides);
|
||||
StrategyDslToTa4jAdapter.RuleBuildContext ruleCtx =
|
||||
new StrategyDslToTa4jAdapter.RuleBuildContext(
|
||||
series, params, visual, null, null, false, seriesOverrides);
|
||||
Rule entryRule = adapter.toRule(buyDsl, ruleCtx);
|
||||
Rule baseExitRule = (sellDsl != null && !sellDsl.isNull())
|
||||
? adapter.toRule(sellDsl, series, params, seriesOverrides)
|
||||
? adapter.toRule(sellDsl, ruleCtx)
|
||||
: new BooleanRule(false);
|
||||
Rule exitRule = buildExitRule(baseExitRule, series, cfg);
|
||||
|
||||
@@ -199,23 +205,21 @@ public class BacktestingService {
|
||||
double exitPrice = getPrice(series, req.getBars(), i, cfg.getExitPriceType());
|
||||
long time = barStartEpoch(series, i);
|
||||
|
||||
// ── SIGNAL_ONLY: 조건 충족 '시작' 봉만 시그널 (GTE 유지형 연속 중복 방지) ──
|
||||
// ── SIGNAL_ONLY: 조건 충족 봉마다 시그널 (live-conditions 충족과 동일 기준) ──
|
||||
if (signalOnly) {
|
||||
boolean entrySatisfied = entryRule.isSatisfied(i);
|
||||
boolean exitSatisfied = exitRule.isSatisfied(i);
|
||||
boolean entryEdge = entrySatisfied && (i <= loopStart || !entryRule.isSatisfied(i - 1));
|
||||
boolean exitEdge = exitSatisfied && (i <= loopStart || !exitRule.isSatisfied(i - 1));
|
||||
if (entryEdge) {
|
||||
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(edge) @ bar={} price={}", i, effEntry);
|
||||
log.debug("[Backtest:SIGNAL_ONLY] BUY @ bar={} price={}", i, effEntry);
|
||||
}
|
||||
if (exitEdge) {
|
||||
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(edge) @ bar={} price={}", i, effExit);
|
||||
log.debug("[Backtest:SIGNAL_ONLY] SELL @ bar={} price={}", i, effExit);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ public class StrategySignalDeterminer {
|
||||
int index, String positionMode) {
|
||||
try {
|
||||
if ("SIGNAL_ONLY".equals(positionMode)) {
|
||||
boolean enterOk = entryRule != null && isRisingEdge(entryRule, index);
|
||||
boolean exitOk = exitRule != null && isRisingEdge(exitRule, index);
|
||||
boolean enterOk = entryRule != null && entryRule.isSatisfied(index);
|
||||
boolean exitOk = exitRule != null && exitRule.isSatisfied(index);
|
||||
if (enterOk) return "BUY";
|
||||
if (exitOk) return "SELL";
|
||||
return "NONE";
|
||||
@@ -84,15 +84,10 @@ public class StrategySignalDeterminer {
|
||||
}
|
||||
|
||||
private String determineSignalOnly(Strategy strategy, int index) {
|
||||
if (isRisingEdge(strategy.getEntryRule(), index)) return "BUY";
|
||||
if (isRisingEdge(strategy.getExitRule(), index)) return "SELL";
|
||||
Rule entry = strategy.getEntryRule();
|
||||
Rule exit = strategy.getExitRule();
|
||||
if (entry != null && entry.isSatisfied(index)) return "BUY";
|
||||
if (exit != null && exit.isSatisfied(index)) return "SELL";
|
||||
return "NONE";
|
||||
}
|
||||
|
||||
/** GTE 등 유지형 조건 — 충족 시작 봉만 true (연속 시그널 방지) */
|
||||
private boolean isRisingEdge(Rule rule, int index) {
|
||||
if (rule == null || !rule.isSatisfied(index)) return false;
|
||||
if (index <= 0) return true;
|
||||
return !rule.isSatisfied(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user