Files
goldenChart/실시간 전략체크 구현.md
2026-05-23 15:11:48 +09:00

5.9 KiB

🎯 구현 목표 (Context & Goal) 현재 구축된 Backend (Spring Boot + Ta4j)와 Frontend (Lightweight Charts) 시스템에 "실시간 매매전략 실행 설정 기능"을 도입합니다. 유저가 화면에서 설정한 옵션(실행 타이밍, 모니터링 여부)에 따라 백엔드 연산 주기와 프론트엔드 차트 시그널 마커(setMarkers)가 유기적으로 연동되도록 아키텍처를 확장해 주세요.

🛠️ 1. 요구사항 명세 및 정책 (Requirements) ① 설정 화면 UI 구성 (Frontend) 실시간 전략 체크 토글: ON / OFF 스위치

매매전략 실행 방식 라디오 버튼: (실시간 전략 체크가 ON 일 때만 활성화)

A. 봉 마감 직후 체크 (At Candle Close)

B. 실시간 틱(장중) 체크 (Intraday Tick)

주의: 이 설정값들은 전역 상태(Redux/Zustand 등)로 관리되며, 변경 즉시 백엔드로 REST API를 통해 반영되어 런타임에 동적으로 감시 스레드가 제어되어야 합니다.

② 백엔드 실행 코어 제어 정책 (Backend Threading) 설정값 OFF 시: 해당 종목 및 유저에 대한 기술적 분석 지표 및 전략 체크 루프를 완전히 중단하여 서버 자원을 방어합니다.

설정값 ON + 방식 A (봉 마감 직후):

업비트 웹소켓에서 들어온 틱 데이터로 분봉 조립 중, '분이 바뀌어 새로운 Bar가 추가되는 시점(addBar)'에만 해당 종목의 Strategy.isSatisfied(lastMaturedBarIndex)를 1회 판정합니다.

설정값 ON + 방식 B (실시간 틱 체크):

백엔드의 3초 주기 라운드 로빈 스케줄러가 돌아갈 때, 아직 마감되지 않은 현재 진행 중인 캔들 인덱스(series.getEndIndex())를 기준으로 전략 만족 여부를 강제 판정합니다.

💾 2. 데이터 구조 및 인터페이스 설계 (Data Struct) ① [PUT] /api/strategy/settings (전략 설정 변경 API) JSON { "market": "KRW-BTC", "isLiveCheck": true, // 실시간 전략 체크 ON/OFF "executionType": "CANDLE_CLOSE" // "CANDLE_CLOSE" (방식A) 또는 "REALTIME_TICK" (방식B) } ② [STOMP WebSocket] 확장된 실시간 데이터 프로토콜 백엔드에서 프론트엔드로 실시간 캔들을 발행할 때, 설정 상태와 매칭된 시그널 유무를 명확히 DTO 포맷에 주입합니다.

Topic: /sub/charts/{market}/{type}

Payload (JSON):

JSON { "t": 1779229260, "o": 92080000.0, "h": 92120000.0, "l": 92010000.0, "c": 92050000.0, "v": 1.845, "rsi": 38.40, "signal": "BUY" // "BUY", "SELL", "NONE" 중 하나 (전략 체크가 OFF이거나 조건 미달 시 무조건 "NONE") } 💻 3. AI 구현 가이드라인 코드 (Reference Code for AI) [Backend] 가상 코드를 참고하여 동적 분기 서비스 레이어를 작성해 주세요. Java @Service @RequiredArgsConstructor public class LiveStrategyEvaluator {

private final Ta4jStorage ta4jStorage;
private final UserStrategySettingsRepository settingsRepository;

// 방식 A: 봉 마감 엔진에서 트리거됨
public String evaluateCandleClose(String market, String candleType, int maturedIndex) {
    UserSetting setting = settingsRepository.findByMarket(market);
    if (!setting.isLiveCheck() || !setting.getExecutionType().equals("CANDLE_CLOSE")) {
        return "NONE";
    }
    
    Strategy strategy = ta4jStorage.getStrategy(market);
    BarSeries series = ta4jStorage.getSeries(market, candleType);
    
    if (strategy.getEntryRule().isSatisfied(maturedIndex)) return "BUY";
    if (strategy.getExitRule().isSatisfied(maturedIndex)) return "SELL";
    
    return "NONE";
}

// 방식 B: 3초 스케줄러 엔진에서 트리거됨
public String evaluateRealtimeTick(String market, String candleType) {
    UserSetting setting = settingsRepository.findByMarket(market);
    if (!setting.isLiveCheck() || !setting.getExecutionType().equals("REALTIME_TICK")) {
        return "NONE";
    }
    
    BarSeries series = ta4jStorage.getSeries(market, candleType);
    Strategy strategy = ta4jStorage.getStrategy(market);
    int currentIndex = series.getEndIndex(); // 마감되지 않은 현재 진행형 캔들
    
    if (strategy.getEntryRule().isSatisfied(currentIndex)) return "BUY";
    if (strategy.getExitRule().isSatisfied(currentIndex)) return "SELL";
    
    return "NONE";
}

} [Frontend] STOMP 데이터를 받아 Lightweight Charts에 마커를 누적 처리하는 훅(Hook) 또는 컴포넌트 로직을 작성해 주세요. JavaScript // 프론트엔드 캔들 및 마커 실시간 업데이트 제어부 let activeMarkers = []; // 차트에 표시된 마커 상태 캐시 배열

stompClient.subscribe(/sub/charts/${market}/${candleType}, (message) => { const data = JSON.parse(message.body);

// 1. 기본 캔들 렌더링 무조건 실행
candleSeries.update({
    time: data.t,
    open: data.o,
    high: data.h,
    low: data.l,
    close: data.c
});

// 2. 백엔드에서 주입된 signal 판별 (NONE이 아닐 때만 차트 오버레이 마커 추가)
if (data.signal !== 'NONE') {
    // 중복 타임스탬프 마커 삽입 방지 (방식 B 장중 틱 동적 체크 대응)
    const isDuplicate = activeMarkers.some(m => m.time === data.t && m.signalType === data.signal);
    
    if (!isDuplicate) {
        const newMarker = {
            time: data.t,
            position: data.signal === 'BUY' ? 'belowBar' : 'aboveBar',
            color: data.signal === 'BUY' ? '#26a69a' : '#ef5350',
            shape: data.signal === 'BUY' ? 'arrowUp' : 'arrowDown',
            text: data.signal === 'BUY' ? '실시간 매수' : '실시간 매도',
            signalType: data.signal // 커스텀 식별자
        };
        
        activeMarkers.push(newMarker);
        candleSeries.setMarkers(activeMarkers);
    }
}

});