복합지표 전략 추가
This commit is contained in:
@@ -199,21 +199,23 @@ public class BacktestingService {
|
||||
double exitPrice = getPrice(series, req.getBars(), i, cfg.getExitPriceType());
|
||||
long time = barStartEpoch(series, i);
|
||||
|
||||
// ── SIGNAL_ONLY 모드: 포지션·자금 상태 무관, 조건 충족 시마다 전체 시그널 생성 ──
|
||||
// ── SIGNAL_ONLY: 조건 충족 '시작' 봉만 시그널 (GTE 유지형 연속 중복 방지) ──
|
||||
if (signalOnly) {
|
||||
boolean entrySatisfied = entryRule.isSatisfied(i);
|
||||
boolean exitSatisfied = exitRule.isSatisfied(i);
|
||||
if (entrySatisfied) {
|
||||
boolean entryEdge = entrySatisfied && (i <= loopStart || !entryRule.isSatisfied(i - 1));
|
||||
boolean exitEdge = exitSatisfied && (i <= loopStart || !exitRule.isSatisfied(i - 1));
|
||||
if (entryEdge) {
|
||||
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);
|
||||
log.debug("[Backtest:SIGNAL_ONLY] BUY(edge) @ bar={} price={}", i, effEntry);
|
||||
}
|
||||
if (exitSatisfied) {
|
||||
if (exitEdge) {
|
||||
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);
|
||||
log.debug("[Backtest:SIGNAL_ONLY] SELL(edge) @ bar={} price={}", i, effExit);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -930,6 +930,14 @@ public class StrategyDslToTa4jAdapter {
|
||||
};
|
||||
}
|
||||
// MA (SMA)
|
||||
if (field.startsWith("CLOSE_MAX_")) {
|
||||
int period = parseTrailingInt(field, "CLOSE_MAX_", 33);
|
||||
return new HighestValueIndicator(close, period);
|
||||
}
|
||||
if (field.startsWith("CLOSE_MIN_")) {
|
||||
int period = parseTrailingInt(field, "CLOSE_MIN_", 33);
|
||||
return new LowestValueIndicator(close, period);
|
||||
}
|
||||
if (field.startsWith("MA") && !field.startsWith("MACD")) {
|
||||
try { return new SMAIndicator(close, Integer.parseInt(field.substring(2))); }
|
||||
catch (NumberFormatException ignored) { /* fall */ }
|
||||
|
||||
@@ -57,8 +57,8 @@ public class StrategySignalDeterminer {
|
||||
int index, String positionMode) {
|
||||
try {
|
||||
if ("SIGNAL_ONLY".equals(positionMode)) {
|
||||
boolean enterOk = entryRule != null && entryRule.isSatisfied(index);
|
||||
boolean exitOk = exitRule != null && exitRule.isSatisfied(index);
|
||||
boolean enterOk = entryRule != null && isRisingEdge(entryRule, index);
|
||||
boolean exitOk = exitRule != null && isRisingEdge(exitRule, index);
|
||||
if (enterOk) return "BUY";
|
||||
if (exitOk) return "SELL";
|
||||
return "NONE";
|
||||
@@ -84,8 +84,15 @@ public class StrategySignalDeterminer {
|
||||
}
|
||||
|
||||
private String determineSignalOnly(Strategy strategy, int index) {
|
||||
if (strategy.getEntryRule().isSatisfied(index)) return "BUY";
|
||||
if (strategy.getExitRule().isSatisfied(index)) return "SELL";
|
||||
if (isRisingEdge(strategy.getEntryRule(), index)) return "BUY";
|
||||
if (isRisingEdge(strategy.getExitRule(), 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