package com.goldenchart.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.ta4j.core.Rule;
import org.ta4j.core.Strategy;
import org.ta4j.core.TradingRecord;
/**
* 포지션 종속성 선택에 따른 매매 시그널 판정 코어 컴포넌트.
*
*
모드 설명
*
* - LONG_ONLY — Ta4j 표준 엄격 모드.
* {@code strategy.shouldEnter/shouldExit} 를 사용하므로,
* TradingRecord 에 열린 포지션이 없으면 매도 시그널을 내보내지 않는다.
* - SIGNAL_ONLY — 포지션 락 우회 모드.
* 내부 Rule 인스턴스를 직접 호출({@code rule.isSatisfied(index)})하므로,
* 이전 매수 기록 여부와 무관하게 지표 수식만 충족되면 시그널을 확정한다.
*
*/
@Component
@Slf4j
public class StrategySignalDeterminer {
/**
* positionMode 에 따라 포지션 제약 유무를 분기하여 시그널을 결정한다.
*
* @param strategy Ta4j Strategy (entryRule / exitRule 포함)
* @param tradingRecord 현재 TradingRecord 상태 (LONG_ONLY 모드에서 참조)
* @param index BarSeries 인덱스
* @param positionMode "LONG_ONLY" | "SIGNAL_ONLY" (null → LONG_ONLY 기본 적용)
* @return "BUY" | "SELL" | "NONE"
*/
public String determineSignal(Strategy strategy, TradingRecord tradingRecord,
int index, String positionMode) {
try {
if ("SIGNAL_ONLY".equals(positionMode)) {
return determineSignalOnly(strategy, index);
}
// LONG_ONLY (기본)
return determineLongOnly(strategy, tradingRecord, index);
} catch (Exception e) {
log.warn("[SignalDeterminer] 시그널 판정 오류 idx={} mode={}: {}", index, positionMode, e.getMessage());
return "NONE";
}
}
// ── 보조 오버로드: Rule[] 직접 전달 (LiveStrategyEvaluator 호환) ─────────────
/**
* Rule 배열을 직접 받아 positionMode 에 따라 시그널 판정.
* entryRule = rules[0], exitRule = rules[1]
*/
public String determineSignalFromRules(Rule entryRule, Rule exitRule,
TradingRecord tradingRecord,
int index, String positionMode) {
try {
if ("SIGNAL_ONLY".equals(positionMode)) {
boolean enterOk = entryRule != null && entryRule.isSatisfied(index);
boolean exitOk = exitRule != null && exitRule.isSatisfied(index);
if (enterOk) return "BUY";
if (exitOk) return "SELL";
return "NONE";
}
// LONG_ONLY — tradingRecord 를 이용한 엄격 모드
boolean enterOk = entryRule != null && entryRule.isSatisfied(index, tradingRecord);
boolean exitOk = exitRule != null && exitRule.isSatisfied(index, tradingRecord);
if (enterOk) return "BUY";
if (exitOk) return "SELL";
return "NONE";
} catch (Exception e) {
log.warn("[SignalDeterminer] Rule 판정 오류 idx={} mode={}: {}", index, positionMode, e.getMessage());
return "NONE";
}
}
// ── Private ───────────────────────────────────────────────────────────────
private String determineLongOnly(Strategy strategy, TradingRecord record, int index) {
if (strategy.shouldEnter(index, record)) return "BUY";
if (strategy.shouldExit(index, record)) return "SELL";
return "NONE";
}
private String determineSignalOnly(Strategy strategy, int index) {
if (strategy.getEntryRule().isSatisfied(index)) return "BUY";
if (strategy.getExitRule().isSatisfied(index)) return "SELL";
return "NONE";
}
}