Files
goldenChart/전략편집기 로직 및 동작흐름.md
T
2026-06-05 10:59:23 +09:00

640 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 전략편집기 로직 및 동작흐름
goldenChart **전략편집기(Strategy Editor)** 의 화면 구성, 편집·저장 방식, 그리고 저장된 전략이 **백엔드에서 매매 시그널(BUY/SELL)을 감지**하기까지의 전체 흐름을 정리한 문서입니다.
> 관련 문서: [실시간 전략체크 구현.md](./실시간%20전략체크%20구현.md) — 실시간 ON/OFF, `CANDLE_CLOSE` / `REALTIME_TICK` 설정 및 STOMP 프로토콜
---
## 1. 개요
| 구분 | 설명 |
|------|------|
| **메뉴** | `strategy-editor` (`App.tsx``StrategyEditorPage`) |
| **역할** | 매수·매도 조건을 **논리 트리(DSL)** 로 구성하고 `gc_strategy` 테이블에 저장 |
| **평가 엔진** | 백엔드 **Ta4j**`StrategyDslToTa4jAdapter`가 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.commitBar` — 1m 확정 시 전략 DSL에 포함된 상위봉(3m·5m·10m·15m …)까지 전파
2.**상위봉 마감**`BarCloseStrategyEvaluationService.onMaturedBarClose``StrategyConditionTimeframeService.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 섹션별 분봉·조건 트리 관리
#### 선택 노드 설정 (`CondEditor` — `strategyEditorShared.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
`LogicExpressionPreview``collectEditorBranches` / `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`)
```typescript
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/strategies``saveStrategy()` (`backendApi.ts`)
### 3.3 CONDITION 리프 예시
```json
{
"id": "n_abc123",
"type": "CONDITION",
"condition": {
"indicatorType": "RSI",
"conditionType": "CROSS_UP",
"leftField": "RSI_VALUE",
"rightField": "K_70",
"period": 14
}
}
```
### 3.4 논리 노드 예시
```json
{
"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`)
```typescript
{
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)
```json
{
"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)`
### 5.4 돈치안·일목 등
- `DONCHIAN``DC_UPPER_N`, `DC_LOWER_N`
- `ICHIMOKU` → 구름/선행스팬 전용 `conditionType` (`ABOVE_CLOUD`, `CLOUD_BREAK_UP`, …)
---
## 6. 저장·로드 프로세스 (프론트엔드)
### 6.1 저장 (`handleSave`)
```mermaid
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``market`, `Ta4jStorage`가 있으면:
- `TIMEFRAME.candleType`에 맞는 시리즈를 storage에서 로드
- 트리거 봉(예: 1m 마감) 인덱스에서 **다른 분봉 시리즈 최신 봉**과 `CrossSeriesRule`로 평가
백테스트는 단일 요청 `BarSeries`만 사용 → 교차 TF는 primary series fallback.
### 7.3 Strategy 조립 (`LiveStrategyEvaluator.buildStrategy`)
```java
Rule entryRule = buildRule(buyConditionJson, series, params, market, storage);
Rule exitRule = buildRule(sellConditionJson, series, params, market, storage);
BaseStrategy strategy = new BaseStrategy(entryRule, exitRule);
```
캐시 키: `market:candleType:strategyId`
---
## 8. 백엔드: 데이터 수집 → 평가 트리거
### 8.1 캔들 파이프라인
```mermaid
flowchart LR
UWS[Upbit WebSocket 틱]
MTP[MarketTickProcessor]
BB[BarBuilder]
TS[Ta4jStorage BarSeries]
UWS --> MTP --> BB --> TS
```
- 동일 분: `updateLastBar` (진행 중 봉 갱신)
- 분 변경: `commitBar` → 확정봉 + 상위봉(3m~1d) 집계
### 8.2 어떤 분봉을 평가할지
`StrategyConditionTimeframeService`:
- 전략 DSL을 walk하여 **`TIMEFRAME` 노드의 candleType** 수집
- `usesTimeframe(strategyId, candleType)` — 해당 봉 마감/스케줄에서만 평가
`LiveStrategyTimeframeService`는 UI/설정용 `gc_live_strategy_settings.candle_type` resolve (가상투자 카드 시간봉 드롭다운 등).
---
## 9. 시그널 감지 프로세스
### 9.1 전제: 실시간 전략 설정 (`GcLiveStrategySettings`)
| 필드 | 값 | 의미 |
|------|-----|------|
| `isLiveCheck` | true/false | 실시간 체크 ON |
| `executionType` | `CANDLE_CLOSE` / `REALTIME_TICK` | 평가 타이밍 |
| `strategyId` | FK | 사용할 `GcStrategy` |
| `positionMode` | `LONG_ONLY` / `SIGNAL_ONLY` | 포지션 추적 방식 |
| `candleType` | `1m``1d` | 설정/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**를 캐시해 중복 진입 방지.
결과: `"BUY"` | `"SELL"` | `"NONE"`
### 9.3 방식 A — 봉 마감 직후 (`CANDLE_CLOSE`)
**트리거:** `BarBuilder.commitBar()``evaluateStrategyOnBarClose()`
**조건 (각 설정 행):**
1. `isLiveCheck == true`
2. `executionType == CANDLE_CLOSE`
3. `usesTimeframe(strategyId, candleType)`
4. `Ta4jStorage`에 해당 분봉 데이터 존재
**평가:** `LiveStrategyEvaluator.evaluateCandleClose(market, candleType, maturedIndex)`
- **확정된 직전 봉 인덱스**에서 1회 판정
### 9.4 방식 B — 실시간 틱 (`REALTIME_TICK`)
**트리거:** `LiveStrategyScheduler`**3초 주기** (`@Scheduled`)
**조건:** `executionType == REALTIME_TICK` 등 동일
**평가:** `evaluateSettingRealtimeTick(setting, market, candleType)`
- `currentIndex = series.getEndIndex()`**진행 중(provisional) 봉**
- **중복 방지:** 동일 `(market, candleType:strategyId)`에서 같은 index에 이미 시그널 냈으면 skip
- **Rule 캐시 완전 우회:** 매 평가마다 신규 Rule 인스턴스 빌드 (`evaluateWithFreshRules`)
- Ta4j `CachedIndicator` stale 캐시 원천 차단
- `triggerRulesCache` 복합 연산(remove→put) 스레드 안전성 문제 제거
- **멀티 TF:** `useConfirmedOnly=false` — 비트리거 상위봉 현재 진행 중인 봉 기준 평가
### 9.5 보완 — 마감 직후 REALTIME 재평가
봉 마감 시 → `evaluateSettingRealtimeAtClose(setting, market, candleType, maturedIndex)`
- 봉 마감 시점의 **교차(CROSS)** 를 3초 스케줄러가 놓치는 경우 대비
- `useConfirmedOnly=true` — 확정봉 인덱스 기준 + 상위봉도 확정봉 사용
- Rule 캐시 우회
### 9.6 평가 흐름 (단일 종목·단일 시점)
```mermaid
flowchart TD
A[트리거: 봉 마감 또는 3초 틱]
B{isLiveCheck?}
C{usesTimeframe?}
D1[CANDLE_CLOSE: evaluate - 캐시 사용 - useConfirmedOnly=true]
D2[REALTIME_TICK: evaluateWithFreshRules - 캐시 우회 - useConfirmedOnly=false]
E[buildTriggerRules - 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
**토픽:** `/sub/charts/{market}/{candleType}`
**엔드포인트:** `ws://host/ws/trading` (SockJS)
**페이로드 (`CandleBarDto`):** OHLCV + 지표값 + **`signal`**: `"BUY"` | `"SELL"` | null
```json
{
"t": 1779229260,
"o": 92080000.0,
"c": 92050000.0,
"signal": "BUY"
}
```
별도 “시그널 전용” 토픽은 없음 — **차트 캔들 스트림에 시그널 필드**로 포함.
### 10.2 DB·알림·주문
| 단계 | 클래스 |
|------|--------|
| DB 저장 | `TradeSignalService.save``GcTradeSignal` |
| FCM | `FcmPushService` |
| 주문 큐 | `OrderExecutionQueue.submitSignal` (deviceId 있을 때) |
### 10.3 프론트 차트 반영
- STOMP 구독 → 캔들 수신 시 `signal` 필드로 **마커(setMarkers)** 표시
- `LiveSignalNotifier`, 차트 슬롯 등
---
## 11. 조건 현황 API (시그널과 별개)
**용도:** 가상투자 카드, 모니터링 UI — “지금 각 조건이 얼마나 충족됐는지”
| 항목 | 내용 |
|------|------|
| API | `GET /api/strategy/live-conditions?market=&strategyId=` |
| 구현 | `LiveConditionStatusService` |
| 방식 | buy/sell JSON의 **각 CONDITION 리프**를 개별 `Rule`로 평가 |
| matchRate | 충족 리프 수 / 평가 가능 리프 수 (전체 AND/OR 트리 충족과 다름) |
실제 BUY/SELL 발화는 §9 경로만 사용.
---
## 12. 백테스트 vs 실시간
| 항목 | 백테스트 (`BacktestingService`) | 실시간 (`LiveStrategyEvaluator`) |
|------|--------------------------------|----------------------------------|
| BarSeries | 요청 `bars`로 일회성 생성 | `Ta4jStorage` 실시간 누적 |
| 멀티 TF | **DSL에서 상위봉 타입 추출 → 집계 시리즈 생성** (→ seriesOverrides) | `Ta4jStorage` 다중 시리즈 |
| adapter | `toRule(dsl, series, params, seriesOverrides)` (백테스트 멀티 TF) | `toRule(..., market, storage)` |
| CrossSeriesRule | **타임스탬프 기반 룩업** (isBacktest=true) | getEndIndex() 또는 endIndex-1 |
| 스캔 | **전 구간** 0..barCount-1 | **단일 index** (마감 or provisional) |
| 청산 | DSL sell + **손절/익절/트레일링** OR 합성 | DSL sell (+ 리스크 모니터 별도) |
| 출력 | `BacktestResponse.signals`, `gc_backtest_result` | STOMP + `gc_trade_signal` |
| 멀티 TF | storage 없으면 단일 시리즈 fallback | `TIMEFRAME` / 교차 TF 지원 |
백테스트도 **동일 DSL·동일 어댑터**를 사용하므로, 편집기에서 저장한 전략이 백테스트와 라이브에서 같은 의미로 해석됩니다 (데이터·타이밍 차이는 있음).
---
## 13. 전략 사용 경로 (편집기 이후)
```mermaid
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[STOMP signal]
EVAL --> TSIG[gc_trade_signal]
```
1. **전략편집기** — DSL 작성·저장
2. **설정 / 가상투자** — 종목별 `strategyId`, `executionType`, `positionMode`, 평가 분봉
3. **백테스트** — 과거 봉으로 전 구간 시뮬레이션
4. **라이브** — Upbit 틱 → Ta4j → 시그널 → STOMP·DB·(자동매매)
---
## 14. 주요 소스 파일
### 프론트엔드
| 경로 | 역할 |
|------|------|
| `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` |
### 백엔드
| 경로 | 역할 |
|------|------|
| `backend/.../entity/GcStrategy.java` | 전략 엔티티 |
| `backend/.../entity/GcLiveStrategySettings.java` | 실시간 설정 |
| `backend/.../service/StrategyDslToTa4jAdapter.java` | DSL → Rule |
| `backend/.../service/LiveStrategyEvaluator.java` | 실시간 평가 |
| `backend/.../service/StrategySignalDeterminer.java` | BUY/SELL 판정 |
| `backend/.../service/BacktestingService.java` | 백테스트 |
| `backend/.../service/LiveConditionStatusService.java` | 조건 현황 API |
| `backend/.../service/StrategyConditionTimeframeService.java` | DSL 분봉 수집 |
| `backend/.../websocket/BarBuilder.java` | 봉 조립·마감 시 평가 트리거 |
| `backend/.../service/BarCloseStrategyEvaluationService.java` | 봉 마감 전략 평가 단일 진입점 |
| `backend/.../websocket/LiveStrategyScheduler.java` | 3초 REALTIME_TICK |
| `backend/.../websocket/TradingWebSocketBroker.java` | STOMP 발행 |
| `backend/.../storage/Ta4jStorage.java` | BarSeries 저장소 |
---
## 15. 핵심 체크리스트 (디버깅용)
1. **저장 DSL**`encodeConditionForSave` 후 JSON에 `TIMEFRAME` / `AND`+`TIMEFRAME` 구조가 의도대로인가?
2. **분봉** — 조건 리프의 `timeframe` vs `TIMEFRAME` 래핑 `candleType` 일치 여부
3. **실시간 ON**`isLiveCheck`, `strategyId` 연결
4. **실행 방식**`CANDLE_CLOSE` vs `REALTIME_TICK`
5. **데이터**`Ta4jStorage.exists(market, candleType)`, warm-up(`pinCandleWatch`)
6. **포지션 모드**`LONG_ONLY`에서 이미 포지션일 때 BUY 무시
7. **STOMP** — 차트 스트림 `/sub/charts/{market}/{candleType}` (하위 호환) + 시그널 전용 `/sub/signals/user/{userId}` 구독 확인
8. **UI 현황 vs 시그널**`live-conditions` `matchRate` = 리프 단순 충족률, `overallEntryMet`/`overallExitMet` = 트리 전체 평가 결과 (실제 시그널 발화와 동일 로직)
9. **마감봉 평가**`BarCloseStrategyEvaluationService` + DSL `usesTimeframe` 게이트; 갭은 `CandleGapBackfillService` 후 보정
10. **프론트 평가 금지** — 가상투자 일치율·자동매매는 백엔드 API/STOMP 만 사용
---
*문서 작성 기준: goldenChart 저장소 프론트/백엔드 소스 구조. API·클래스명은 변경될 수 있으므로 구현 확인 시 §14 파일 목록을 참고하세요.*