백테스팅 SIGNAL_ONLY 수정 — 포지션 무관 전체 시그널 생성 + 기본값 적용

- BacktestingService: SIGNAL_ONLY 모드에서 inPosition 포지션 락 제거,
  entryRule/exitRule 충족 시마다 BUY/SELL 시그널 무조건 생성
- BacktestSettingsDto: positionMode 기본값 LONG_ONLY → SIGNAL_ONLY
- backendApi.ts: DEFAULT_BACKTEST_SETTINGS positionMode 기본값 변경
- BacktestSettingsModal: positionMode UI 항목(시그널 생성 방식) 최상단에 추가

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Macbook
2026-06-11 17:22:26 +09:00
parent 7b4a7e1950
commit 8d019ddd26
4 changed files with 29 additions and 50 deletions
@@ -160,56 +160,18 @@ public class BacktestingService {
double exitPrice = getPrice(series, req.getBars(), i, cfg.getExitPriceType());
long time = series.getBar(i).getEndTime().getEpochSecond();
// ── SIGNAL_ONLY 모드: 지표 시그널은 포지션 무관, 체결·레포트는 LONG_ONLY와 동일 ──
// ── SIGNAL_ONLY 모드: 포지션·자금 상태 무관, 조건 충족 시마다 전체 시그널 생성 ──
if (signalOnly) {
if (!inPosition) {
if (i - lastExitBar <= reentryWait && lastExitBar >= 0) {
if (useLedger) ledger.markToMarket(closePrice);
continue;
}
if (entryRule.isSatisfied(i)) {
double effEntry = applySlippage(closePrice, cfg, true);
double qty = enterPosition(ledger, useLedger, equity, tradeSizePct,
effEntry, closePrice, record, i, series, time);
if (qty > 0) {
String sigType = "SHORT".equals(direction) ? "SHORT_ENTRY" : "BUY";
signals.add(buildFillSignal(time, sigType, effEntry, i, qty));
entryPrice = effEntry;
entryBarIdx = i;
inPosition = true;
partialDone = false;
if (useLedger) equity = ledger.portfolioValue(closePrice);
}
}
} else {
if (partialExit && !partialDone && exitRule.isSatisfied(i)) {
double effExit = applySlippage(exitPrice, cfg, false);
double qty = applyExit(ledger, useLedger, equity, tradeSizePct, entryPrice,
effExit, partialPct, direction, cfg, simGrossProfit, simGrossLoss, time, i);
if (qty > 0) {
signals.add(buildFillSignal(time, "PARTIAL_SELL", effExit, i, qty));
partialDone = true;
if (useLedger) equity = ledger.portfolioValue(closePrice);
}
} else if (exitRule.isSatisfied(i)) {
double effExit = applySlippage(exitPrice, cfg, false);
double size = partialDone ? (1.0 - partialPct) : 1.0;
double qty = applyExit(ledger, useLedger, equity, tradeSizePct, entryPrice, effExit,
size, direction, cfg, simGrossProfit, simGrossLoss, time, i);
if (qty > 0) {
Num numExitPrice = series.numFactory().numOf(effExit);
Num numShares = record.getCurrentPosition().getEntry().getAmount();
record.exit(i, numExitPrice, numShares);
String sigType = "SHORT".equals(direction) ? "SHORT_EXIT" : "SELL";
signals.add(buildFillSignal(time, sigType, effExit, i, qty));
inPosition = false;
lastExitBar = i;
partialDone = false;
if (useLedger) equity = ledger.portfolioValue(closePrice);
}
}
if (entryRule.isSatisfied(i)) {
double effEntry = applySlippage(closePrice, cfg, true);
String buyType = "SHORT".equals(direction) ? "SHORT_ENTRY" : "BUY";
signals.add(buildFillSignal(time, buyType, effEntry, i, 1.0));
}
if (exitRule.isSatisfied(i)) {
double effExit = applySlippage(exitPrice, cfg, false);
String sellType = "SHORT".equals(direction) ? "SHORT_EXIT" : "SELL";
signals.add(buildFillSignal(time, sellType, effExit, i, 1.0));
}
if (useLedger) ledger.markToMarket(closePrice);
continue;
}