Files
goldenChart/전략편집기 로직 및 동작흐름.md
2026-06-05 12:22:29 +09:00

34 KiB
Raw Permalink Blame History

전략편집기 로직 및 동작흐름

goldenChart 전략편집기(Strategy Editor) 의 화면 구성, 편집·저장 방식, 그리고 저장된 전략이 백엔드에서 매매 시그널(BUY/SELL)을 감지하기까지의 전체 흐름을 정리한 문서입니다.

관련 문서: 실시간 전략체크 구현.md — 실시간 ON/OFF, CANDLE_CLOSE / REALTIME_TICK 설정 및 STOMP 프로토콜


1. 개요

구분 설명
메뉴 strategy-editor (App.tsxStrategyEditorPage)
역할 매수·매도 조건을 논리 트리(DSL) 로 구성하고 gc_strategy 테이블에 저장
평가 엔진 백엔드 Ta4jStrategyDslToTa4jAdapter가 DSL JSON → Rule 변환
실시간 시그널 LiveStrategyEvaluator + BarBuilder / LiveStrategyScheduler
레거시 화면 StrategyPage (strategy) — 동일 API·DSL, UI만 구형 트리 편집기

전략편집기에서 저장하는 것은 조건 트리 JSON(buyCondition, sellCondition)이며, 노드 좌표·팔레트 사용자 정의 항목 등은 브라우저 localStorage에만 둘 수 있습니다.

1.1 아키텍처 원칙 (단일 평가 원천)

계층 역할
전략편집기 buy_condition_json / sell_condition_json 만 DB에 저장. 그래프·목록 빌더는 동일 DSL 공유, 표현만 다름
Logic Expression 저장된 DSL의 설명용 미리보기 — 별도 조건 컬럼 없음
flow_layout_json 캔버스 좌표·START 메타 등 UI — Ta4j 평가 입력이 아님 (DSL과 동기 저장)
백엔드 Ta4j로 유일한 BUY/SELL·조건 충족 평가
프론트 가상투자 UI GET /strategy/live-conditions 표시·일치율만. 프론트에서 조건 재평가하지 않음
가상 자동매매 백엔드 STOMP signal (BUY/SELL) 수신 시에만 모의 체결

1.2 봉 마감별 전략 체크 (스킵 방지)

  1. 업비트 틱 → BarBuilder.commitBar2-Phase 처리 (§9.3 참고)
    • Phase 1: 1m 확정 후 상위봉(3m·5m·10m·15m …) 전부 스토리지에 저장
    • Phase 2: 모든 봉 저장 완료 후 전략 평가 일괄 트리거 (멀티TF 레이스 컨디션 해소)
  2. 상위봉 마감BarCloseStrategyEvaluationService.onMaturedBarCloseStrategyConditionTimeframeService.usesTimeframe 이 true 인 분봉만 평가
  3. WS 갭 → CandleGapBackfillService REST 병합 후 evaluateRecentMaturedBarsAfterBackfill 로 병합 구간 마감봉 보정
  4. REALTIME_TICK 실행 방식은 추가로 LiveStrategyScheduler(3초)가 동일 DSL 분봉만 순회

예: 비트코인 전략 START에 3m,5m,10m,15m 이면 각 분봉 마감봉마다 백엔드 평가가 1회씩 실행되어야 하며, 평가 분봉은 DSL에 없는 1h 등에서는 실행되지 않음.


2. 화면 구성 (StrategyEditorPage)

2.1 레이아웃

┌─────────────────────────────────────────────────────────────────────────┐
│  BuilderPageShell — 제목: 전략편집기 / Strategy Builder                  │
├──────────────┬──────────────────────────────────────────┬───────────────┤
│  좌측 패널    │  중앙 편집 영역                           │  우측 패널     │
│  (리사이즈)   │                                          │  (리사이즈)    │
│              │  [매수] [매도] 탭                          │               │
│  전략 목록    │  [그래프 | 목록] 편집 모드                  │  지표 | 템플릿 │
│  새 전략      │  ┌────────────────────────────────────┐  │  팔레트       │
│  삭제        │  │ StrategyEditorCanvas (React Flow)   │  │  START/AND/  │
│  Import/     │  │  또는 StrategyListEditor            │  │  OR/NOT 칩    │
│  Export      │  └────────────────────────────────────┘  │  보조/복합    │
│              │  선택 노드 설정 바 (CondEditor)            │  지표 탭      │
├──────────────┴──────────────────────────────────────────┴───────────────┤
│  하단 터미널 — LogicExpressionPreview (논리 수식 미리보기)                  │
└─────────────────────────────────────────────────────────────────────────┘

스타일: strategyEditor.css, strategyEditorTheme.css

2.2 주요 UI 상태

State 타입 용도
signalTab 'buy' | 'sell' 매수/매도 조건 편집 대상
editorMode 'graph' | 'list' React Flow vs 목록 트리 (gc_se_editor_mode)
rightTab 'indicators' | 'templates' 지표 팔레트 vs 프리셋 템플릿
indicatorSubTab 'auxiliary' | 'composite' 단일 보조지표 vs 복합(2기간 교차)
buyCondition / sellCondition LogicNode | null 저장 대상 DSL 루트
buyLayout / sellLayout SignalFlowLayoutSnapshot 노드 좌표, 엣지, START 메타 (로컬 전용)
buyOrphans / sellOrphans LogicNode[] 그래프에서 분리된 고아 노드 (DB 미저장)
selectedId number | 'draft' 선택 전략 ID

2.3 중앙 편집 영역 동작

그래프 모드 (StrategyEditorCanvas)

  • React Flow 기반: StartNode, LogicGateNode(AND/OR/NOT), ConditionFlowNode
  • 팔레트에서 드래그 앤 드롭 → makeNode / mergeAtRoot로 트리에 삽입
  • START 노드에서 평가 분봉 선택 (1m ~ 1d)
  • START를 추가하면 다중 분기 (extraStartIds) — 저장 시 AND/OR + 여러 TIMEFRAME으로 직렬화
  • 노드 이동·연결 변경 시 saveStrategyFlowLayout → localStorage gc_se_flow_layout_v1

목록 모드 (StrategyListEditor)

  • 동일 DSL을 트리/섹션 UI로 편집
  • START 섹션별 분봉·조건 트리 관리

선택 노드 설정 (CondEditorstrategyEditorShared.tsx)

  • indicatorType, conditionType, leftField, rightField, period, 복합지표 기간 등
  • buildStrategyEditorDef()차트 보조지표 설정과 분리된 고정 기본 파라미터 사용

2.4 우측 팔레트

영역 내용
시작·논리 START, AND, OR, NOT (PaletteChip)
밴드·추세 MA, EMA, BOLLINGER, DONCHIAN, ICHIMOKU 등
보조지표 RSI, MACD, CCI, 신고가/신저가(NH_PRIOR/NL_PRIOR) 등 — se-palette-auxiliary-v1
복합지표 RSI+RSI, MACD+MACD 등 2기간 교차 — se-palette-composite-v1
템플릿 strategyPresets.ts — 터틀, 돈치안, 일목 등 원클릭 삽입

드래그 payload: application/json{ type, value, label, composite?, period?, ... }

2.5 하단 Logic Expression

LogicExpressionPreviewcollectEditorBranches / collectDslBranches로 분기별 수식 표시 예:

[5m] (RSI(9) CROSS_UP K_70 AND MACD CROSS_UP)
AND [1h] (ADX CROSS_UP K_25)

3. 전략 데이터 모델 (DSL)

3.1 핵심 타입 (strategyTypes.ts)

type LogicNodeType = 'AND' | 'OR' | 'NOT' | 'CONDITION' | 'TIMEFRAME';

interface ConditionDSL {
  indicatorType: string;      // RSI, MACD, DONCHIAN, NEW_HIGH, ...
  conditionType: string;      // GT, CROSS_UP, CROSS_DOWN, ...
  leftField?: string;         // RSI_VALUE, DC_UPPER_20, NH_PRIOR_9, ...
  rightField?: string;        // K_70, SIGNAL_LINE, DC_LOWER_10, ...
  period?: number;
  composite?: boolean;        // 복합지표 2기간 교차
  leftPeriod?, rightPeriod?;
  leftCandleType?, rightCandleType?;
  // ...
}

interface LogicNode {
  id: string;
  type: LogicNodeType;
  children?: LogicNode[];
  condition?: ConditionDSL;
  candleType?: string;        // TIMEFRAME 전용
}

3.2 DB/API 저장 형태 (GcStrategy)

컬럼 내용
buy_condition_json 매수(진입) 조건 루트 LogicNode
sell_condition_json 매도(청산) 조건 루트 LogicNode
name, description, enabled 메타

API: POST /api/strategiessaveStrategy() (backendApi.ts)

3.3 CONDITION 리프 예시

{
  "id": "n_abc123",
  "type": "CONDITION",
  "condition": {
    "indicatorType": "RSI",
    "conditionType": "CROSS_UP",
    "leftField": "RSI_VALUE",
    "rightField": "K_70",
    "period": 14
  }
}

3.4 논리 노드 예시

{
  "id": "n_and1",
  "type": "AND",
  "children": [
    { "type": "CONDITION", "condition": { "...": "RSI CROSS_UP" } },
    { "type": "CONDITION", "condition": { "...": "MACD CROSS_UP" } }
  ]
}

4. START · TIMEFRAME · 다중 분봉

편집기 UI의 START 노드는 DB에 그대로 저장되지 않습니다. strategyConditionSerde.ts가 저장/로드 시 변환합니다.

4.1 편집기 내부 상태 (EditorConditionState)

{
  root: LogicNode | null;              // 기본 START 분기의 조건 트리
  startMeta: { [startId]: { candleType } };
  extraStartIds: string[];             // 추가 START
  extraRoots: { [startId]: LogicNode };
  startCombineOp?: 'AND' | 'OR';       // 다중 START 결합
}
  • 기본 START ID: __strategy_start__
  • 추가 START: __strategy_start_{uuid}__

4.2 저장 시 인코딩 (encodeConditionForSave)

경우 저장 결과
조건 없음 null
단일 분기, 분봉 1m 루트 트리만 (TIMEFRAME 생략)
단일 분기, 분봉 5m { type: "TIMEFRAME", candleType: "5m", children: [root] }
다중 START { type: "AND"|"OR", children: [ TIMEFRAME×N ] }

4.3 로드 시 디코딩 (decodeConditionForEditor)

  • 최상위가 AND/OR이고 자식이 전부 TIMEFRAME → 다중 START로 복원
  • 최상위가 TIMEFRAME 단일 → 기본 START + 해당 분봉
  • 그 외 → 기본 1m START, 루트 = 전체 DSL

4.4 지원 분봉 (strategyStartNodes.ts)

1m, 3m, 5m, 10m, 15m, 30m, 1h, 4h, 1d


5. 지표·조건 종류

5.1 단일 보조지표

  • 팔레트 또는 템플릿에서 CONDITION 노드 생성
  • leftField / rightField — 지표 플롯 vs 상수(K_70) vs 다른 플롯(SIGNAL_LINE)
  • 기간은 period 또는 필드명 접미사 (RSI_VALUE_9)

5.2 복합지표 (compositeIndicators.ts)

동일 지표 두 기간 비교 — 예: RSI(9) 상향 돌파 RSI(20)

{
  "composite": true,
  "indicatorType": "RSI",
  "conditionType": "CROSS_UP",
  "leftField": "RSI_VALUE_9",
  "rightField": "RSI_VALUE_20",
  "leftPeriod": 9,
  "rightPeriod": 20
}

요소별 서로 다른 분봉(leftCandleType, rightCandleType)은 백엔드 buildCrossTimeframeCompositeRule로 처리 (라이브 + storage 필요).

5.3 신고가·신저가 (단일 지표)

  • NEW_HIGH / NEW_LOW + NH_PRIOR_N / NL_PRIOR_N
  • 의미: 직전 N봉 최고가/최저가 대비 종가 돌파/이탈 (당일 봉 제외)
  • 백엔드: PreviousValueIndicator(HighestValueIndicator(high, N), 1)
  • 주의: 라이브 환경에서 N이 클수록 warm-up 봉 수 이상의 데이터가 필요함 (§8.3 참고)

5.4 돈치안·일목 등

  • DONCHIANDC_UPPER_N, DC_LOWER_N
  • ICHIMOKU → 구름/선행스팬 전용 conditionType (ABOVE_CLOUD, CLOUD_BREAK_UP, …)

6. 저장·로드 프로세스 (프론트엔드)

6.1 저장 (handleSave)

sequenceDiagram
  participant U as 사용자
  participant SE as StrategyEditorPage
  participant Serde as encodeConditionForSave
  participant API as POST /api/strategies
  participant DB as gc_strategy

  U->>SE: 저장 클릭
  SE->>Serde: buyEditorState / sellEditorState
  Serde-->>SE: buyCondition, sellCondition (LogicNode)
  SE->>API: saveStrategy({ name, buyCondition, sellCondition })
  API->>DB: JSON 컬럼 persist
  SE->>SE: persistFlowLayout(id) — localStorage만

검증:

  • 전략명 필수
  • 매수·매도 중 하나 이상 조건 필요
  • draft 저장 시 → DB ID 발급 후 migrateStrategyFlowLayout('draft' → id)

6.2 로드

  1. loadStrategies() — 서버 우선
  2. 실패 시 loadStratsLocal() (gc_strat_v2)
  3. 전략 선택 → decodeConditionForEditor(buy/sell) + loadStrategyFlowLayout(id)

6.3 localStorage vs DB

저장 내용 DB
gc_strategy (API) buy/sell JSON
gc_se_flow_layout_v1 노드 좌표, edge, startMeta, orphans
gc_strat_v2 전략 목록 캐시 (폴백)
se-palette-auxiliary-v1 사용자 보조지표 팔레트
se-palette-composite-v1 사용자 복합지표 팔레트
gc_se_editor_mode graph / list

Import/Export: strategyImportExport.ts — DSL + flowLayout + editorMode JSON 파일


7. 백엔드: DSL → Ta4j Rule

7.1 어댑터 (StrategyDslToTa4jAdapter)

진입: toRule(JsonNode dsl, RuleBuildContext ctx)

LogicNode.type 빌드 메서드 Ta4j
AND buildAndRule AndRule
OR buildOrRule OrRule
NOT buildNotRule NotRule
TIMEFRAME buildTimeframeRule 분기 BarSeries + CrossSeriesRule
CONDITION buildConditionRule 비교/교차 Rule

CONDITION conditionType 매핑 (요약):

conditionType Ta4j 개념
GT, LT, GTE, LTE, EQ, NEQ Over/Under/Equals
CROSS_UP, CROSS_DOWN CrossedUpIndicatorRule, CrossedDownIndicatorRule
SLOPE_UP, SLOPE_DOWN IsRisingRule, IsFallingRule
HOLD_N_DAYS N봉 연속 충족
일목 전용 구름/선행스팬 규칙

필드 해석: resolveField — OHLCV, K_* 상수, RSI/CCI/MACD/Donchian/NH_PRIOR/NL_PRIOR 등

지표 파라미터: DB indicator_settings + DSL_TO_REGISTRY 매핑

7.2 RuleBuildContext 구조

public record RuleBuildContext(
    BarSeries primarySeries,
    Map<String, Map<String, Object>> indicatorParams,
    Map<String, Map<String, Object>> indicatorVisual,
    String market,
    Ta4jStorage storage,
    /**
     * CANDLE_CLOSE 평가 시 true — CrossSeriesRule 이 상위봉의 마지막 확정봉
     * (endIndex-1)을 사용하도록 강제. provisional 봉 오염을 방지.
     */
    boolean useConfirmedOnly,
    /**
     * 백테스트용 사전 집계 시리즈 맵 candleType→BarSeries.
     * null 이 아니고 비어 있지 않으면 백테스트 모드로 동작하며
     * CrossSeriesRule 에서 타임스탬프 기반 룩업을 수행한다.
     */
    Map<String, BarSeries> seriesOverrides
)
  • withPrimary(BarSeries) — primarySeries만 교체해 하위 컨텍스트 생성
  • isBacktest()seriesOverrides가 비어 있지 않으면 true
  • resolveSeries(candleType) — seriesOverrides → Ta4jStorage 순 검색

7.3 멀티 타임프레임 (라이브)

RuleBuildContextmarket, Ta4jStorage가 있으면:

  • TIMEFRAME.candleType에 맞는 시리즈를 storage에서 로드
  • 트리거 봉(예: 1m 마감) 인덱스에서 다른 분봉 시리즈 최신 봉CrossSeriesRule로 평가
  • useConfirmedOnly=true — 상위봉은 getEndIndex()-1 (확정봉 기준, provisional 봉 오염 방지)
  • useConfirmedOnly=false — 상위봉은 getEndIndex() (진행 중 봉 포함, REALTIME_TICK 용)

7.4 멀티 타임프레임 (백테스트)

BacktestingService.run 진입 시 DSL에서 상위봉 타입을 자동 추출(collectTimeframesFromDsl)하여 기본 시리즈를 집계(aggregateSeries)합니다.

Map<String, BarSeries> seriesOverrides = buildSeriesOverrides(series, primaryTf, buyDsl, sellDsl);
Rule entryRule = adapter.toRule(buyDsl, series, params, seriesOverrides);
  • CrossSeriesRule에서 isBacktest=true타임스탬프 기반 인덱스 룩업으로 정확한 과거 봉 참조
  • 라이브와 동일한 DSL·어댑터를 사용해 백테스트 결과의 신뢰성 확보

8. 백엔드: 데이터 수집 → 평가 트리거

8.1 캔들 파이프라인

flowchart LR
  UWS[Upbit WebSocket 틱]
  MTP[MarketTickProcessor]
  BB[BarBuilder]
  TS[Ta4jStorage BarSeries]

  UWS --> MTP --> BB --> TS
  • 동일 분: updateLastBar (진행 중 봉 갱신)
  • 분 변경: commitBarPhase 1 확정봉 + 상위봉(3m~1d) 전부 저장 → Phase 2 전략 평가 일괄 트리거

8.2 어떤 분봉을 평가할지

StrategyConditionTimeframeService:

  • 전략 DSL을 walk하여 TIMEFRAME 노드의 candleType 수집
  • usesTimeframe(strategyId, candleType) — 해당 봉 마감/스케줄에서만 평가

LiveStrategyTimeframeService는 UI/설정용 gc_live_strategy_settings.candle_type resolve (가상투자 카드 시간봉 드롭다운 등).

8.3 BarSeries 윈도우 크기 (warm-up)

설정 기본값 설명
Ta4jStorage.MAX_BAR_COUNT 1800봉 인메모리 최대 보관 수
upbit.api.warmup-count 1500봉 구독 시 초기 로드 목표치

원칙: 전략에서 사용하는 최대 period × 2~3배 이상으로 warmup-count를 설정해야 지표 NaN 반환을 방지할 수 있다.
예) HMA(200), 신고가(120봉) 사용 시 → warmup-count ≥ 600봉.
현재 기본값(1500봉)은 EMA(200) 완전 수렴(~1150봉)을 포함하여 충분한 여유를 제공한다.


9. 시그널 감지 프로세스

9.1 전제: 실시간 전략 설정 (GcLiveStrategySettings)

필드 의미
isLiveCheck true/false 실시간 체크 ON
executionType CANDLE_CLOSE / REALTIME_TICK 평가 타이밍
strategyId FK 사용할 GcStrategy
positionMode LONG_ONLY / SIGNAL_ONLY 포지션 추적 방식
candleType 1m1d 설정/UI 기본 분봉

가상투자·설정 화면에서 PUT /api/strategy/settings로 반영.

9.2 시그널 판정 (StrategySignalDeterminer)

positionMode BUY SELL
LONG_ONLY strategy.shouldEnter(index, record) strategy.shouldExit(index, record)
SIGNAL_ONLY entryRule.isSatisfied(index) exitRule.isSatisfied(index)

LONG_ONLY는 evaluator가 가상 TradingRecord + positionOpenCache를 캐시해 중복 진입 방지.

서버 재시작 시 포지션 상태 자동 복원:
positionOpenCache 캐시 미스 발생 시 gc_trade_signal 테이블의 최신 시그널을 DB에서 조회하여 포지션 상태(BUY = 열림, SELL/없음 = 닫힘)를 자동 복원한다. 서버 재시작 후 중복 BUY 발화를 방지한다.

결과: "BUY" | "SELL" | "NONE"

9.3 방식 A — 봉 마감 직후 (CANDLE_CLOSE)

트리거: BarBuilder.commitBar() (Phase 2) → BarCloseStrategyEvaluationService.onMaturedBarClose()

2-Phase 처리 흐름:

Phase 1 (스토리지 확정)
  ├─ ta4jStorage.addBar("1m", confirmedBar)
  ├─ ta4jStorage.updateLastBar("3m", ...) 또는 addBar (마감 시)
  ├─ ta4jStorage.updateLastBar("5m", ...) 또는 addBar (마감 시)
  └─ ... 모든 상위봉 처리 완료

Phase 2 (전략 평가 — 모든 봉이 확정된 후)
  ├─ onMaturedBarClose("1m", ...)   ← 1m CrossSeriesRule이 1h 참조해도 1h가 이미 확정됨
  ├─ onMaturedBarClose("5m", ...)   ← 5m이 마감된 경우
  └─ onMaturedBarClose("1h", ...)   ← 1h가 마감된 경우

조건 (각 설정 행):

  1. isLiveCheck == true
  2. executionType == CANDLE_CLOSE
  3. usesTimeframe(strategyId, candleType)
  4. Ta4jStorage에 해당 분봉 데이터 존재

평가: LiveStrategyEvaluator.evaluateSettingOnCandleClose(setting, market, candleType, maturedIndex)

  • 확정된 봉 인덱스에서 1회 판정
  • useConfirmedOnly=true — 상위봉 CrossSeriesRule도 확정봉(getEndIndex()-1) 기준

9.4 방식 B — 실시간 틱 (REALTIME_TICK)

트리거: LiveStrategyScheduler3초 주기 (@Scheduled(fixedDelay = 3000))

조건: executionType == REALTIME_TICK 등 동일

평가: evaluateSettingRealtimeTick(setting, market, candleType)

  • currentIndex = series.getEndIndex()진행 중(provisional) 봉
  • 중복 방지: 동일 (market, candleType:strategyId)에서 같은 index에 이미 시그널 냈으면 skip
  • Rule 캐시 완전 우회: 매 평가마다 신규 Rule 인스턴스 빌드 (evaluateWithFreshRules)
    • Ta4j CachedIndicator stale 캐시 원천 차단 (provisional 봉 가격 변경 반영)
    • triggerRulesCache 복합 연산(remove→put) 스레드 안전성 문제 제거
  • 멀티 TF: useConfirmedOnly=false — 비트리거 상위봉 현재 진행 중인 봉 기준 평가
  • 오버헤드 경감: 앱-레벨 캐시(strategyCache, indicatorCache, 30초 TTL)로 매 평가마다 DB 조회하지 않음

9.5 보완 — 마감 직후 REALTIME 재평가

봉 마감 시 → evaluateSettingRealtimeAtClose(setting, market, candleType, maturedIndex)

  • 봉 마감 시점의 교차(CROSS) 를 3초 스케줄러가 놓치는 경우 대비
  • useConfirmedOnly=true — 확정봉 인덱스 기준 + 상위봉도 확정봉 사용
  • Rule 캐시 우회

9.6 평가 흐름 (단일 종목·단일 시점)

flowchart TD
  A[트리거: 봉 마감 또는 3초 틱]
  B{isLiveCheck?}
  C{usesTimeframe?}
  D1[CANDLE_CLOSE: evaluate - 캐시 사용 - useConfirmedOnly=true]
  D2[REALTIME_TICK: evaluateWithFreshRules - 캐시 우회 - useConfirmedOnly=false]
  E[buildTriggerRules - 앱캐시DSL - DSL to Ta4j with CrossSeriesRule]
  F[StrategySignalDeterminer.determineSignal]
  G{결과}
  H[설정별 독립 publishStrategySignal]
  I[NONE - 종료]

  A --> B
  B -->|false| I
  B -->|true| C
  C -->|false| I
  C -->|CANDLE_CLOSE| D1
  C -->|REALTIME_TICK| D2
  D1 --> E --> F --> G
  D2 --> E
  G -->|BUY/SELL| H
  G -->|NONE| I

주의: BarCloseStrategyEvaluationService / LiveStrategyScheduler설정별 독립 루프로 멀티 전략 시그널을 각각 발행한다. evaluateCandleClose(market), evaluateRealtimeTick(market) 마켓 단위 API 는 첫 non-NONE 만 반환하므로 실시간 시그널 발행 경로에서 사용 금지 (@Deprecated).


10. 시그널 발행·후처리

10.1 STOMP WebSocket — 이중 채널 구조

시그널은 두 개의 독립된 STOMP 채널로 발행된다.

채널 토픽 용도
차트 캔들 스트림 /sub/charts/{market}/{candleType} 차트 마커 렌더링 (하위 호환)
시그널 전용 채널 /sub/signals/user/{userId} 미션 크리티컬 BUY/SELL 알림
/sub/signals/device/{deviceId} 비로그인 사용자 전용

엔드포인트: ws://host/ws/trading (SockJS)

차트 스트림 페이로드 (CandleBarDto):

{
  "t": 1779229260,
  "o": 92080000.0,
  "c": 92050000.0,
  "signal": "BUY",
  "strategyId": 5,
  "userId": 123
}

시그널 전용 페이로드 (SignalEventDto):

{
  "market": "KRW-BTC",
  "candleType": "1m",
  "strategyId": 5,
  "signalType": "BUY",
  "price": 92050000.0,
  "candleTime": 1779229260,
  "executionType": "CANDLE_CLOSE",
  "timestamp": 1779229263000
}

채널 분리 이유:
차트 스트림은 틱 단위로 대용량 메시지가 발생하는 고부하 채널이다. 브로커 병목 발생 시 매매 타이밍에 치명적인 시그널이 지연될 수 있으므로, 빈도는 낮지만 실시간성이 보장되어야 하는 시그널을 별도 채널로 분리한다.

프론트엔드 구독 (useLiveStrategyMarkers):
두 채널을 병행 구독하며 중복 시그널은 (market:candleType:time:signal) 키로 dedup한다.

10.2 DB·알림·주문

단계 클래스
DB 저장 TradeSignalService.saveGcTradeSignal
FCM FcmPushService
주문 큐 OrderExecutionQueue.submitSignal

10.3 프론트 차트 반영

  • STOMP 구독 → 캔들 수신 시 signal 필드로 마커(setMarkers) 표시
  • 시그널 전용 토픽 수신 시 알림 팝업 표시 (차트 스트림 병목과 독립)

11. 조건 현황 API (시그널과 별개)

용도: 가상투자 카드, 모니터링 UI — "지금 각 조건이 얼마나 충족됐는지"

항목 내용
API GET /api/strategy/live-conditions?market=&strategyId=
구현 LiveConditionStatusService
방식 buy/sell JSON의 각 CONDITION 리프를 개별 Rule로 평가

응답 필드:

필드 타입 설명
matchRate int (0~100) 충족 리프 수 / 평가 가능 리프 수 — 참고용, AND/OR 논리 미반영
overallEntryMet Boolean 매수 DSL 전체 논리 트리 평가 결과 (null=평가 불가)
overallExitMet Boolean 매도 DSL 전체 논리 트리 평가 결과 (null=평가 불가)
rows List 개별 조건 리프 충족 여부 상세

matchRate vs overallEntryMet 차이:

전략: A AND (B OR C)
현재: A=false, B=true, C=true

matchRate     = 2/3 = 66%  (B, C 충족)
overallEntryMet = false     (A가 false 이므로 AND 전체가 false → 실제 BUY 미발화)

실제 BUY/SELL 발화는 §9 경로만 사용하며, overallEntryMet이 실제 발화 여부와 일치한다.


12. 백테스트 vs 실시간

항목 백테스트 (BacktestingService) 실시간 (LiveStrategyEvaluator)
BarSeries 요청 bars로 일회성 생성 Ta4jStorage 실시간 누적
멀티 TF DSL에서 상위봉 타입 추출 → aggregateSeries 집계 (seriesOverrides) Ta4jStorage 다중 시리즈
adapter toRule(dsl, series, params, seriesOverrides) toRule(..., market, storage)
CrossSeriesRule 타임스탬프 기반 룩업 (isBacktest=true) getEndIndex()-1 (확정봉) 또는 getEndIndex() (provisional)
스캔 전 구간 0..barCount-1 단일 index (마감 or provisional)
청산 DSL sell + 손절/익절/트레일링 OR 합성 DSL sell (+ 리스크 모니터 별도)
출력 BacktestResponse.signals, gc_backtest_result STOMP + gc_trade_signal
useConfirmedOnly 항상 false (과거 고정 데이터) CANDLE_CLOSE: true, REALTIME_TICK: false

백테스트도 동일 DSL·동일 어댑터를 사용하고 멀티 TF 시리즈를 실시간과 동일하게 집계하므로, 편집기에서 저장한 전략의 백테스트 결과와 라이브 결과가 일관되게 유지된다.


13. 전략 사용 경로 (편집기 이후)

flowchart TB
  SE[전략편집기 저장]
  DB[(gc_strategy)]
  SE --> DB

  DB --> BT[백테스팅 화면]
  DB --> LIVE[실시간 전략 설정]
  DB --> VT[가상투자 카드]

  LIVE --> LSS[gc_live_strategy_settings]
  LSS --> EVAL[LiveStrategyEvaluator]
  VT --> LC[live-conditions API]

  EVAL --> STOMP_CHART[/sub/charts/{market}/{candleType}]
  EVAL --> STOMP_SIG[/sub/signals/user/{userId}]
  EVAL --> TSIG[gc_trade_signal]
  1. 전략편집기 — DSL 작성·저장
  2. 설정 / 가상투자 — 종목별 strategyId, executionType, positionMode, 평가 분봉
  3. 백테스트 — 과거 봉으로 전 구간 시뮬레이션 (멀티TF 포함)
  4. 라이브 — Upbit 틱 → Ta4j → 시그널 → STOMP 이중 채널·DB·(자동매매)

14. 주요 개선 이력

[Round 1] 기본 안정성 개선

항목 개선 내용
REALTIME_TICK 캐시 우회 evaluateWithFreshRules 도입 — CachedIndicator stale 캐시 원천 차단
멀티TF 확정봉 제어 useConfirmedOnly 플래그 — CANDLE_CLOSE 시 상위봉 확정봉 인덱스 강제
백테스트 멀티TF 지원 aggregateSeries + seriesOverrides — 라이브와 동일 방식으로 상위봉 평가
마켓 단위 API Deprecated evaluateCandleClose(market), evaluateRealtimeTick(market)@Deprecated

[Round 2] 고급 신뢰성·확장성 개선

항목 개선 내용
[항목1] REALTIME_TICK 오버헤드 경감 전략 DSL · 지표 파라미터 앱-레벨 캐시(30s TTL) 도입 → DB 조회 최소화
[항목2] 멀티TF 레이스 컨디션 해소 BarBuilder.commitBar 2-Phase 처리 — Phase 1: 전 타임프레임 스토리지 확정, Phase 2: 전략 평가
[항목3] LONG_ONLY 포지션 DB 복원 서버 재시작 후 gc_trade_signal 최신 레코드로 포지션 상태 자동 복원 (중복 BUY 방지)
[항목4] 지표 윈도우 크기 명세화 warmup-count: 1500, MAX_BAR_COUNT: 1800 — 최대 period×3배 여유 기준 명시
[항목5] 시그널 전용 STOMP 채널 분리 SignalEventBroker 신설 — /sub/signals/user/{userId} 전용 채널로 병목 독립
[항목6] live-conditions 논리 트리 평가 overallEntryMet/overallExitMet 필드 추가 — AND/OR/NOT 완전 반영한 전체 트리 결과

15. 주요 소스 파일

프론트엔드

경로 역할
frontend/src/components/StrategyEditorPage.tsx 전략편집기 메인
frontend/src/components/strategyEditor/StrategyEditorCanvas.tsx React Flow 캔버스
frontend/src/components/strategyEditor/StrategyListEditor.tsx 목록 편집기
frontend/src/components/strategyEditor/LogicExpressionPreview.tsx 논리 수식 미리보기
frontend/src/utils/strategyTypes.ts DSL 타입·조건 상수
frontend/src/utils/strategyConditionSerde.ts START↔TIMEFRAME 직렬화
frontend/src/utils/strategyEditorShared.tsx CondEditor, 트리 조작, DEF
frontend/src/utils/strategyEditorLayoutStorage.ts Flow 레이아웃 localStorage
frontend/src/utils/compositeIndicators.ts 복합지표 정의
frontend/src/utils/strategyStartNodes.ts 분봉·START 상수
frontend/src/utils/strategyPresets.ts 템플릿
frontend/src/utils/backendApi.ts saveStrategy, loadStrategies
frontend/src/hooks/useLiveStrategyMarkers.ts 차트+시그널 STOMP 구독 (이중 채널)
frontend/src/utils/tradeSignalOwnership.ts 시그널 소유자 검증

백엔드

경로 역할
backend/.../entity/GcStrategy.java 전략 엔티티
backend/.../entity/GcLiveStrategySettings.java 실시간 설정
backend/.../entity/GcTradeSignal.java 시그널 이력 엔티티
backend/.../repository/GcTradeSignalRepository.java 시그널 이력 저장소 (포지션 복원 쿼리 포함)
backend/.../service/StrategyDslToTa4jAdapter.java DSL → Rule (RuleBuildContext, CrossSeriesRule)
backend/.../service/LiveStrategyEvaluator.java 실시간 평가 (앱캐시, 포지션 복원)
backend/.../service/StrategySignalDeterminer.java BUY/SELL 판정
backend/.../service/StrategyTriggerBranchEvaluator.java 분기 트리거 평가 (useConfirmedOnly)
backend/.../service/BacktestingService.java 백테스트 (멀티TF seriesOverrides)
backend/.../service/LiveConditionStatusService.java 조건 현황 API (overallEntryMet/overallExitMet)
backend/.../service/BarCloseStrategyEvaluationService.java 봉 마감 전략 평가 단일 진입점
backend/.../service/StrategyConditionTimeframeService.java DSL 분봉 수집
backend/.../websocket/BarBuilder.java 봉 조립 · 2-Phase 마감 처리
backend/.../websocket/LiveStrategyScheduler.java 3초 REALTIME_TICK
backend/.../websocket/TradingWebSocketBroker.java 차트 캔들 STOMP 발행
backend/.../websocket/SignalEventBroker.java 시그널 전용 STOMP 발행 (/sub/signals/user/{userId})
backend/.../dto/SignalEventDto.java 시그널 전용 페이로드
backend/.../dto/LiveConditionStatusDto.java 조건 현황 응답 (overallEntryMet 포함)
backend/.../storage/Ta4jStorage.java BarSeries 저장소 (MAX_BAR_COUNT=1800)
backend/.../config/WebSocketConfig.java STOMP 브로커 설정

16. 핵심 체크리스트 (디버깅용)

  1. 저장 DSLencodeConditionForSave 후 JSON에 TIMEFRAME / AND+TIMEFRAME 구조가 의도대로인가?
  2. 분봉 — 조건 리프의 timeframe vs TIMEFRAME 래핑 candleType 일치 여부
  3. 실시간 ONisLiveCheck, strategyId 연결
  4. 실행 방식CANDLE_CLOSE vs REALTIME_TICK
  5. 데이터 warm-upTa4jStorage.exists(market, candleType), warmup-count ≥ max_period×2
  6. 포지션 모드LONG_ONLY에서 서버 재시작 후 포지션 상태가 DB에서 올바르게 복원됐는지 (gc_trade_signal 최신 레코드)
  7. 멀티TF 레이스 컨디션BarBuilder.commitBar Phase 1(스토리지 전부 확정) 후 Phase 2(평가) 순서 준수 확인
  8. STOMP 이중 채널 — 차트 /sub/charts/{market}/{candleType} + 시그널 전용 /sub/signals/user/{userId} 구독 모두 확인
  9. UI 현황 vs 시그널matchRate(리프 단순 비율, 참고용) vs overallEntryMet(전체 트리 평가, 실제 발화와 일치)
  10. 마감봉 평가BarCloseStrategyEvaluationService + DSL usesTimeframe 게이트; 갭은 CandleGapBackfillService 후 보정
  11. 프론트 평가 금지 — 가상투자 일치율·자동매매는 백엔드 API/STOMP 만 사용
  12. @Deprecated APIevaluateCandleClose(market), evaluateRealtimeTick(market) 은 멀티 전략 환경에서 사용 금지

문서 작성 기준: goldenChart 저장소 프론트/백엔드 소스 구조. API·클래스명은 변경될 수 있으므로 구현 확인 시 §15 파일 목록을 참고하세요.