매매실행 방식 설정 추가
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user