매매실행 방식 설정 추가

This commit is contained in:
Macbook
2026-06-20 09:59:18 +09:00
parent fd63c101e5
commit 8bf157137a
12 changed files with 195 additions and 21 deletions
@@ -107,4 +107,11 @@ public class BacktestSettingsDto {
*/
@Builder.Default
private String analysisMethod = "MARK_TO_MARKET";
/**
* SCAN_SIGNALS — 조건 스캔(봉 종가·DSL 충족, 차트 마커와 동일)
* BACKTEST_ENGINE — 슬리피지·손절/익절·진입/청산가 등 백테스트 설정 반영 (기본)
*/
@Builder.Default
private String tradeExecutionMode = "BACKTEST_ENGINE";
}
@@ -124,6 +124,11 @@ public class GcBacktestSettings {
@Builder.Default
private String analysisMethod = "MARK_TO_MARKET";
/** SCAN_SIGNALS | BACKTEST_ENGINE */
@Column(name = "trade_execution_mode", nullable = false, length = 32)
@Builder.Default
private String tradeExecutionMode = "BACKTEST_ENGINE";
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@@ -54,6 +54,10 @@ public class BacktestSettingsService {
PortfolioLedger.REALIZED_ONLY.equals(dto.getAnalysisMethod())
? PortfolioLedger.REALIZED_ONLY
: PortfolioLedger.MARK_TO_MARKET);
entity.setTradeExecutionMode(
"SCAN_SIGNALS".equals(dto.getTradeExecutionMode())
? "SCAN_SIGNALS"
: "BACKTEST_ENGINE");
return toDto(repo.save(entity));
}
@@ -84,6 +88,10 @@ public class BacktestSettingsService {
.partialExitPct(e.getPartialExitPct())
.positionMode(e.getPositionMode() != null ? e.getPositionMode() : "LONG_ONLY")
.analysisMethod(e.getAnalysisMethod() != null ? e.getAnalysisMethod() : PortfolioLedger.MARK_TO_MARKET)
.tradeExecutionMode(
"SCAN_SIGNALS".equals(e.getTradeExecutionMode())
? "SCAN_SIGNALS"
: "BACKTEST_ENGINE")
.build();
}
}
@@ -50,6 +50,15 @@ public class BacktestingService {
private static final BacktestSettingsDto DEFAULT_SETTINGS = new BacktestSettingsDto();
/** 조건 스캔 — 종가·DSL 충족 (슬리피지·손절/익절 Rule 미적용) */
public static final String TRADE_EXEC_SCAN_SIGNALS = "SCAN_SIGNALS";
/** 백테스트 엔진 — 슬리피지·리스크 Rule·진입/청산가 반영 */
public static final String TRADE_EXEC_BACKTEST_ENGINE = "BACKTEST_ENGINE";
private static boolean isScanSignalsExecution(BacktestSettingsDto cfg) {
return cfg != null && TRADE_EXEC_SCAN_SIGNALS.equals(cfg.getTradeExecutionMode());
}
// ── Public API ────────────────────────────────────────────────────────────
public BacktestResponse run(BacktestRequest req) {
@@ -152,6 +161,9 @@ public class BacktestingService {
// ── 청산 규칙 합성 ────────────────────────────────────────────────────────
private Rule buildExitRule(Rule baseExit, BarSeries series, BacktestSettingsDto cfg) {
if (isScanSignalsExecution(cfg)) {
return baseExit;
}
Rule result = baseExit;
ClosePriceIndicator close = new ClosePriceIndicator(series);
@@ -196,7 +208,8 @@ public class BacktestingService {
double initCap = cfg.getInitialCapital() != null ? cfg.getInitialCapital().doubleValue() : 10_000_000.0;
double tradeSizePct = cfg.getTradeSizeValue() != null ? cfg.getTradeSizeValue().doubleValue() / 100.0 : 1.0;
boolean partialExit = Boolean.TRUE.equals(cfg.getPartialExitEnabled());
final boolean scanExec = isScanSignalsExecution(cfg);
boolean partialExit = !scanExec && Boolean.TRUE.equals(cfg.getPartialExitEnabled());
double partialPct = cfg.getPartialExitPct() != null ? cfg.getPartialExitPct().doubleValue() / 100.0 : 0.5;
boolean partialDone = false;
@@ -208,10 +221,12 @@ public class BacktestingService {
int barCount = series.getBarCount();
int loopStart = Math.max(0, Math.min(evalStartIndex, barCount));
final String entryPriceType = scanExec ? "CLOSE" : cfg.getEntryPriceType();
final String exitPriceType = scanExec ? "CLOSE" : cfg.getExitPriceType();
for (int i = loopStart; i < barCount; i++) {
double closePrice = getPrice(series, req.getBars(), i, cfg.getEntryPriceType());
double exitPrice = getPrice(series, req.getBars(), i, cfg.getExitPriceType());
double closePrice = getPrice(series, req.getBars(), i, entryPriceType);
double exitPrice = getPrice(series, req.getBars(), i, exitPriceType);
long time = barStartEpoch(series, i);
// ── SIGNAL_ONLY: 조건 충족 봉마다 시그널 (live-conditions 충족과 동일 기준) ──
@@ -727,6 +742,9 @@ public class BacktestingService {
}
private double applySlippage(double price, BacktestSettingsDto cfg, boolean isBuy) {
if (isScanSignalsExecution(cfg)) {
return price;
}
double slip = cfg.getSlippageRate() != null ? cfg.getSlippageRate().doubleValue() : 0.0005;
return isBuy ? price * (1 + slip) : price * (1 - slip);
}
@@ -0,0 +1,5 @@
-- 매매 체결 방식: SCAN_SIGNALS(조건 스캔·종가) | BACKTEST_ENGINE(슬리피지·리스크 Rule)
ALTER TABLE gc_backtest_settings
ADD COLUMN trade_execution_mode VARCHAR(32) NOT NULL DEFAULT 'BACKTEST_ENGINE'
COMMENT 'SCAN_SIGNALS | BACKTEST_ENGINE'
AFTER analysis_method;