19 KiB
모의투자·매매 시그널 알림 처리 과정
goldenChart에서 매매 시그널 알림과 모의투자(투자관리) 자동·수동 체결이 어떻게 연결되는지, 처리 조건·흐름·관련 코드를 정리한 문서입니다.
핵심: 시그널 알림과 모의 체결은 같은 전략 평가에서 출발하지만, 이후 파이프라인과 통과 조건이 다릅니다.
알림이 많아도 체결이 적을 수 있는 구조가 정상 동작에 가깝습니다.
1. 개요
| 구분 | 역할 | 결과물 |
|---|---|---|
| 시그널 발생 | 실시간 전략이 BUY/SELL 조건 충족 판정 | STOMP, gc_trade_signal, FCM, 프론트 알림 목록 |
| 모의 자동 체결 | 설정·예수금·포지션 규칙 통과 시 모의 계좌 주문 | gc_paper_trade, 원장·KPI 갱신 |
| 모의 수동 체결 | 사용자가 투자관리·차트에서 주문 | 동일 gc_paper_* 계좌 |
자동 체결은 백엔드 전용입니다. 브라우저·가상매매 화면이 꺼져 있어도 서버가 업비트 WS·봉 데이터를 받는 한, Match로 동기화된 live 설정이 있으면 평가·체결 시도가 이어집니다.
1.1 사용자 범위 (정식 로그인 vs 게스트)
| 구분 | 식별 | 매매·모의·전략·자동매매 |
|---|---|---|
| 정식 로그인 | X-User-Id (회원 ID) |
지원 — 모의 계좌·설정·live 설정·체결 모두 user_id 단일 기준 (기기 무관) |
| 게스트 입장 | X-Device-Id만 (비로그인) |
미지원 — API 403, 프론트 FormalLoginGate·로그인 유도 |
- 모의 계좌:
gc_paper_account는findByUserId1계좌,device_id는user:{userId}합성 키(UK용). - 시그널·주문 큐:
gc_live_strategy_settings.user_id IS NOT NULL인 행만 평가·OrderExecutionQueue적재. - 마이그레이션
V54: 기존 거래·시그널 데이터 초기화, device-only live 체크 OFF.
2. 전체 아키텍처
flowchart TB
subgraph ingest [시세·봉]
WS[업비트 WebSocket 틱]
BB[BarBuilder 1m 확정·상위봉 전파]
TS[Ta4jStorage]
WS --> BB --> TS
end
subgraph eval [전략 평가]
BCE[BarCloseStrategyEvaluationService]
LSS[LiveStrategyScheduler 3초]
LSE[LiveStrategyEvaluator]
TS --> BCE
TS --> LSS
BCE --> LSE
LSS --> LSE
end
subgraph signal_out [시그널 산출 - 알림 경로]
STOMP[TradingWebSocketBroker STOMP]
TSS[TradeSignalService.save]
FCM[FcmPushService]
LSE -->|BUY/SELL| BCE
LSE -->|BUY/SELL| LSS
BCE --> STOMP
BCE --> TSS
LSS --> STOMP
LSS --> TSS
TSS --> FCM
end
subgraph order_path [주문 경로 - 체결]
OEQ[OrderExecutionQueue]
TES[TradingExecutionService]
PTS[PaperTradingService.tryExecuteOnSignal]
BCE -->|deviceId != null| OEQ
LSS -->|deviceId != null| OEQ
OEQ --> TES
TES --> PTS
end
subgraph fe [프론트]
LSN[LiveSignalNotifier / STOMP]
TNC[TradeNotificationContext]
PTP[PaperTradingPage / TradeOrderPanel]
STOMP --> LSN --> TNC
PTS --> PTP
end
3. 사전 설정 (DB·앱)
3.1 종목별 실시간 전략 — gc_live_strategy_settings
| 필드 | 설명 |
|---|---|
market |
종목 코드 (예: KRW-BTC) |
user_id / device_id |
회원: user_id만 저장, device_id=null · 비회원: device_id만 |
strategy_id |
사용할 GcStrategy |
is_live_check |
실시간 체크 ON → 평가·시그널 대상 |
execution_type |
CANDLE_CLOSE(봉 마감) 또는 REALTIME_TICK(3초 틱) |
position_mode |
LONG_ONLY(포지션 추적) 또는 SIGNAL_ONLY(규칙만) |
candle_type |
UI 기본 분봉; 실제 평가 분봉은 DSL StrategyConditionTimeframeService 기준 |
저장 API: PUT /api/strategy/settings (LiveStrategySettingsService)
- 가상투자 Match 시
syncVirtualTargetsToBackend()→ 종목별isLiveCheck=true일괄 반영 (frontend/src/utils/virtualLiveStrategySync.ts) - 차트·설정의 전역 실시간 체크는
gc_app_settings.live_strategy_check/live_strategy_id와 연동
3.2 모의투자·실행 모드 — gc_app_settings
| 필드 | 자동 체결 관련 |
|---|---|
paper_trading_enabled |
모의투자 마스터 ON |
paper_auto_trade_enabled |
전략 시그널 → 모의 자동 체결 ON |
paper_auto_trade_budget_pct |
자동 매수 시 가용 현금 대비 사용 비율 (기본 95%) |
paper_initial_capital |
초기 자본 |
paper_fee_rate_pct / paper_slippage_pct |
수수료·슬리피지 |
paper_min_order_krw |
최소 주문 금액 (기본 5,000원) |
trading_mode |
PAPER / LIVE / BOTH — 자동 체결 대상 분기 |
live_strategy_check |
전역 실시간 체크 (종목별 is_live_check 없을 때 보조 조건) |
조회·저장: GET/POST /api/app-settings (X-Device-Id, 로그인 시 X-User-Id)
3.3 모의 계좌 — gc_paper_account
- 키:
device_id(HTTPX-Device-Id; 없으면anonymous) - 로그인 사용자도 요청 헤더의
device_id로 계좌를 구분 (user_id는 부가 정보) - 보유:
gc_paper_position, 체결:gc_paper_trade, 한도:gc_paper_symbol_allocation
4. 시그널 발생 상세
4.1 데이터·구독 전제
LiveStrategyStartupRunner— 기동 시is_live_check=true종목 WS·Ta4j 고정 구독BarBuilder.onTick— 1m 확정 후 상위 분봉(5m 등) 전파StrategyConditionTimeframeService— 전략 DSL에 포함된 분봉만 평가
4.2 방식 A — 봉 마감 (CANDLE_CLOSE)
트리거: BarBuilder.commitBar() → BarCloseStrategyEvaluationService.onMaturedBarClose()
진입 조건 (종목·설정 행마다):
is_live_check = true이고strategy_id존재execution_type = CANDLE_CLOSE(또는 마감 시evaluateRealtimeAtClose보조)usesTimeframe(strategyId, candleType)— DSL에 해당 분봉 포함- 동일
market:candleType:barEndEpoch중복 평가 방지 (evaluatedBarCloseKeys)
평가: LiveStrategyEvaluator.evaluateSettingOnCandleClose() — 확정된 직전 봉 인덱스에서 1회
4.3 방식 B — 실시간 틱 (REALTIME_TICK)
트리거: LiveStrategyScheduler — 3초 fixedDelay (@Scheduled)
조건: findAllByIsLiveCheckTrue() 중 execution_type = REALTIME_TICK
평가: evaluateSettingRealtimeTick() — 진행 중 봉 series.getEndIndex()
동일 (market, candleType, index) 중복 시그널 방지
4.4 시그널 판정 (LiveStrategyEvaluator + StrategySignalDeterminer)
position_mode |
BUY | SELL |
|---|---|---|
| LONG_ONLY | 진입 규칙 + 가상 TradingRecord 미보유 |
청산 규칙 + 보유 중 |
| SIGNAL_ONLY | entryRule 충족 |
exitRule 충족 |
LONG_ONLY에서 이미 보유인데 BUY, 무포지션인데 SELL이면 evaluator 내부에서 NONE으로 정리할 수 있음 (캐시된 포지션 상태 기준).
결과: "BUY" | "SELL" | "NONE"
5. 시그널 후처리 (알림 파이프라인)
BUY/SELL이 확정되면 설정 행(GcLiveStrategySettings)마다 아래가 순서대로 실행됩니다.
5.1 STOMP 실시간 푸시
| 항목 | 내용 |
|---|---|
| 토픽 | /sub/charts/{market}/{candleType} |
| 페이로드 | CandleBarDto — OHLCV + signal, strategyId, userId, deviceId, executionType |
| 발행 | TradingWebSocketBroker.publish() |
5.2 DB 이력 저장
TradeSignalService.save(deviceId, userId, market, strategyId, …) → gc_trade_signal
- 로그인:
user_id저장,device_id는 null일 수 있음 - 비회원:
device_id저장
API: GET /api/trade-signals — 로그인 시 user_id, 비로그인 시 device_id 기준 목록
5.3 FCM 푸시 (선택)
FcmPushService.sendTradeSignalIfEnabled() — 앱 알림 설정·토큰 등록 시
로그인 사용자는 userId 기준 발송 (deviceId null 허용)
5.4 주문 큐 적재 (자동 체결 시도만)
// BarCloseStrategyEvaluationService, LiveStrategyScheduler 공통
if (s.getDeviceId() != null) {
orderExecutionQueue.submitSignal(
s.getDeviceId(), s.getUserId(), market,
s.getStrategyId(), signal, price);
}
주문 큐:
user_id가 있는 live 설정만OrderExecutionQueue에 적재합니다. (게스트 device-only 설정은 평가 대상에서 제외)
6. 프론트 — 매매 시그널 알림
6.1 STOMP 수신
| 컴포넌트 | 역할 |
|---|---|
useLiveStrategyMarkers |
/sub/charts/... 구독, signal 필드 처리 |
signalBelongsToCurrentSession |
userId 또는 deviceId 소유자 필터 (tradeSignalOwnership.ts) |
LiveSignalNotifier |
App.tsx 전역, monitoredMarkets 대상 |
구독 대상 (monitoredMarkets):
- 가상투자 실행 중 → 가상투자 대상 종목
- 아니면 →
liveStrategyCheck+liveStrategyId+ 관심종목
6.2 알림 Context
TradeNotificationContext:
- STOMP 수신 →
addNotification()(미확인 배지·토스트·사운드) - 20초마다
loadTradeSignals()→ 서버 이력과 병합 - 알림 ID:
{market}:{candleTime}:{signalType} - 알림 목록 화면:
TradeNotificationListPage(메뉴 알림목록)
6.3 알림 vs 체결 (프론트)
- 프론트는 시그널 수신 시
placePaperOrder를 호출하지 않음 (과거 STOMP→주문 훅 제거됨) - 투자관리·가상매매 UI는 모니터링·수동 주문·설정; 자동 체결은 백엔드만
7. 모의 자동매매 실행 파이프라인
7.1 주문 큐
OrderExecutionQueue — 단일 워커 스레드 + RateLimiter (업비트 429 완화)
| 단계 | 동작 |
|---|---|
submitSignal |
OrderRequest.strategySignal 큐 적재 |
executeNow |
deviceId blank면 즉시 return |
| 실행 | TradingExecutionService.executeSignal() |
7.2 실행 대상 분기 — TradingExecutionService
trading_mode:
PAPER → paperTradingService.tryExecuteOnSignal (paper_trading_enabled 시)
LIVE → liveTradingService.tryExecuteOnSignal
BOTH → 둘 다 시도
7.3 모의 체결 본체 — PaperTradingService.tryExecuteOnSignal
진입 전 필수 (하나라도 실패 시 return, 예외는 warn 로그):
| # | 조건 |
|---|---|
| 1 | deviceId not blank |
| 2 | signalType ∈ {BUY, SELL} |
| 3 | paper_trading_enabled = true |
| 4 | paper_auto_trade_enabled = true |
| 5 | live_strategy_check = true OR 해당 market에 is_live_check=true 설정 존재 |
| 6 | trading_mode가 PAPER 또는 BOTH (TradingExecutionService 단계) |
포지션 모드 LONG_ONLY (분할 매매 미설정 시):
| 시그널 | 스킵 조건 |
|---|---|
| BUY | 해당 종목 이미 보유 (quantity > 0) |
| SELL | 해당 종목 미보유 |
분할 매매 (종목별 1~3차, gc_paper_symbol_allocation):
- 투자관리 설정 탭 → 선택 종목:
buy_stage1~3_krw,sell_stage1~3_krw(KRW) - 매수·매도 단계 금액 중 하나라도 > 0 이면 분할 모드 (
PaperTradeStageService.usesStagedTrading) - BUY 시그널마다 다음 매수 차수 금액만 체결 (
buy_stage_done0→1→2→3) - SELL 시그널마다 다음 매도 차수 금액만큼 수량 체결 (
sell_stage_done증가) - 전량 매도 후
buy_stage_done·sell_stage_done0으로 초기화 - 분할 모드에서는 보유 중에도 2·3차 추가 매수 가능 (LONG_ONLY 일괄 매수 스킵 해제)
- 단계 금액이 모두 0이면 기존 방식(가용현금 % 일괄 매수·전량 매도)
매수 금액 (분할 미설정 시):
budget = (cash_balance - reserved_cash) × paper_auto_trade_budget_pct / 100
execPrice = price × (1 + slippage%)
qty = budget / (execPrice × (1 + fee%))
| # | 매수 스킵 |
|---|---|
| 7 | budget < paper_min_order_krw |
| 8 | qty × execPrice < min_order |
매도 (분할 미설정): 보유 수량 전량(가용 수량, 미체결 매도 예약 제외)
체결: executeTrade(..., source="STRATEGY", autoTrade=true)
- 종목 한도:
PaperAllocationService.validateBuy—getOrDefault로 한도 행 없으면 초기자본 규모 한도 자동 생성 - 체결 기록:
gc_paper_trade, 원장:PaperLedgerService
8. 모의 수동매매 (시그널과 별도)
경로: TradeOrderPanel → POST /api/paper/orders → PaperTradingService.placeOrder
| 구분 | 자동 (tryExecuteOnSignal) |
수동 (placeOrder) |
|---|---|---|
paper_auto_trade_enabled |
필수 ON | 불필요 |
| 예산 | 전역 paper_auto_trade_budget_pct |
요청 수량·금액·한도 |
source |
STRATEGY |
MANUAL 등 |
| 한도 검증 | autoTrade=true |
autoTrade=false |
수동 주문도 paper_trading_enabled 및 동일 계좌·한도 규칙을 따릅니다.
9. 가상투자 ↔ 백엔드 동기화
가상투자 Match(실행) 시:
syncVirtualTargetsToBackend(targets, session, isLiveCheck=true)- 종목별
saveLiveStrategySettings—isLiveCheck,executionType,positionMode putPaperAllocationsBulk— 종목별 한도 행(베스트 에포트)- 전략 분봉 pin —
pinStrategyEvaluationTimeframes
중지 시: stopVirtualLiveOnBackend — isLiveCheck=false
서버는 이후 BarClose / LiveStrategyScheduler만으로 평가·시그널·(조건 충족 시) 체결을 진행합니다.
10. 처리 조건 요약표
10.1 시그널 알림이 쌓이려면
| 조건 | 필수 |
|---|---|
gc_live_strategy_settings.is_live_check = true |
✓ |
strategy_id 지정 |
✓ |
DSL에 해당 candle_type 포함 |
✓ |
| Ta4jStorage에 봉 데이터 존재 | ✓ |
| 전략 규칙 BUY/SELL 판정 | ✓ |
→ DB 저장·STOMP·(설정 시) FCM·프론트 알림 (deviceId 없어도 가능)
10.2 모의 자동 체결이 되려면
위 시그널 조건 + 아래 전부 (실무상):
| 조건 | 필수 |
|---|---|
live 설정 user_id != null (주문 큐 진입) |
✓ |
paper_trading_enabled |
✓ |
paper_auto_trade_enabled |
✓ |
trading_mode ∈ {PAPER, BOTH} |
✓ |
live_strategy_check OR 종목 is_live_check |
✓ |
| LONG_ONLY: BUY=미보유, SELL=보유 | ✓ |
| 예수금·한도·최소주문금액 | ✓ |
11. 알림은 많은데 체결이 적은 대표 사례
| 현상 | 원인 |
|---|---|
| 미확인 알림 수백 건, 체결 3건 | 종목 다수 × 5분봉 × 매수·매도 시그널; 체결은 예수금·포지션 규칙으로 소수만 통과 |
| 연속 매수 알림, 체결 없음 | 예수금 소진 — 자동 매수가 남은 현금의 95%씩 사용 (1천만 → 약 3종목 후 5천원 미만) |
| 매도 알림만 많음 | LONG_ONLY + 미보유 → 매도 시그널은 알림만, 체결 스킵 |
| 같은 종목 매수 알림 반복 | 이미 보유 → BUY 체결 스킵 (알림은 evaluator/DB 경로에 따라 발생 가능) |
| 게스트 입장 | 매매 API 403, STOMP 시그널 구독 없음 |
| KPI 1천만, 거래 탭 과거 3건 | 계좌 초기화 후 현금 복구, 체결 이력은 유지 |
| 자동매매 OFF | paper_auto_trade_enabled=false — 수동만 가능 |
예수금 소진 예 (초기 1천만, budget 95%):
- 1차 매수 ≈ 950만 → 잔액 ≈ 50만
- 2차 ≈ 47.5만 → 잔액 ≈ 2.5만
- 3차 ≈ 2.4만 → 잔액 ≈ 1천원대 → 이후 BUY 시그널 전부
budget < min_order스킵
12. 엔드투엔드 시퀀스 (봉 마감 + 자동 모의)
sequenceDiagram
participant UB as 업비트 WS
participant BB as BarBuilder
participant BCE as BarCloseEval
participant LSE as LiveStrategyEvaluator
participant TSS as TradeSignalService
participant OEQ as OrderExecutionQueue
participant PTS as PaperTradingService
participant FE as 프론트 STOMP
UB->>BB: tick
BB->>BCE: onMaturedBarClose (5m 등)
BCE->>LSE: evaluateSettingOnCandleClose
LSE-->>BCE: BUY|SELL|NONE
alt BUY or SELL
BCE->>FE: STOMP CandleBarDto.signal
BCE->>TSS: save → gc_trade_signal
alt setting.deviceId != null
BCE->>OEQ: submitSignal
OEQ->>PTS: tryExecuteOnSignal
alt 모든 체결 조건 통과
PTS->>PTS: executeTrade STRATEGY
else 조건 실패
PTS-->>PTS: silent return
end
end
end
13. 주요 클래스·파일
백엔드
| 영역 | 클래스 |
|---|---|
| 봉·마감 평가 | BarBuilder, BarCloseStrategyEvaluationService |
| 틱 평가 | LiveStrategyScheduler |
| 전략 판정 | LiveStrategyEvaluator, StrategySignalDeterminer |
| 시그널 저장 | TradeSignalService, GcTradeSignal |
| STOMP | TradingWebSocketBroker |
| 주문 큐 | OrderExecutionQueue, OrderRequest |
| 실행 분기 | TradingExecutionService, TradingMode |
| 모의 체결 | PaperTradingService, PaperAllocationService |
| Live 설정 | LiveStrategySettingsService, GcLiveStrategySettings |
| 설정 | AppSettingsService, GcAppSettings |
프론트
| 영역 | 파일 |
|---|---|
| 전역 STOMP 알림 | LiveSignalNotifier.tsx, useLiveStrategyMarkers.ts |
| 알림 상태 | TradeNotificationContext.tsx, TradeNotificationListPage.tsx |
| 소유자 필터 | tradeSignalOwnership.ts |
| 투자관리 UI | PaperTradingPage.tsx, TradeOrderPanel.tsx |
| 가상 동기화 | virtualLiveStrategySync.ts |
| API | backendApi.ts — loadTradeSignals, paper APIs |
DB (핵심 테이블)
| 테이블 | 용도 |
|---|---|
gc_live_strategy_settings |
종목별 실시간 전략·체크 ON |
gc_trade_signal |
매매 시그널 알림 이력 |
gc_app_settings |
모의·자동매매·실행모드 전역 |
gc_paper_account |
모의 계좌 (device_id) |
gc_paper_trade |
체결 내역 |
gc_paper_symbol_allocation |
종목별 투자 한도 |
14. 운영·디버깅 체크리스트
- 설정 → 모의투자: 활성화 ON, 자동매매 ON, 실행 대상 PAPER/BOTH
- 가상투자 Match: 대상 종목
isLiveCheck백엔드 반영 여부 - 예수금: 투자관리 KPI
주문가능, 체결 후cash_after - 포지션: LONG_ONLY 시 매도 알림 vs 실제 보유
- 로그인:
device_idnull 이슈 — 자동 체결 기대 시 비회원 테스트 또는 백엔드 보완 여부 확인 - 로그 키워드:
[TradeSignal] saved,[Paper] STRATEGY BUY,[Paper] auto trade failed,[OrderExecutionQueue]
15. 관련 문서
- 전략편집기 로직 및 동작흐름.md — §9 시그널 감지, §10 시그널 발행
- docs/모의투자_투자금_관리_설계.md — 계좌·한도·원장 설계
- backend/FCM_BACKEND.md — 푸시 연동
- db_struct.md — 테이블 상세
문서 기준: goldenChart 저장소 백엔드·프론트 구현 (자동 체결 백엔드 전용, 회원 live 설정 device_id=null 주문 큐 분기 포함).