Files
goldenChart/모의투자 처리과정.md
2026-05-31 17:34:26 +09:00

19 KiB
Raw Permalink Blame History

모의투자·매매 시그널 알림 처리 과정

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_accountfindByUserId 1계좌, device_iduser:{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 (HTTP X-Device-Id; 없으면 anonymous)
  • 로그인 사용자도 요청 헤더의 device_id로 계좌를 구분 ( user_id는 부가 정보)
  • 보유: gc_paper_position, 체결: gc_paper_trade, 한도: gc_paper_symbol_allocation

4. 시그널 발생 상세

4.1 데이터·구독 전제

  1. LiveStrategyStartupRunner — 기동 시 is_live_check=true 종목 WS·Ta4j 고정 구독
  2. BarBuilder.onTick — 1m 확정 후 상위 분봉(5m 등) 전파
  3. StrategyConditionTimeframeService — 전략 DSL에 포함된 분봉만 평가

4.2 방식 A — 봉 마감 (CANDLE_CLOSE)

트리거: BarBuilder.commitBar()BarCloseStrategyEvaluationService.onMaturedBarClose()

진입 조건 (종목·설정 행마다):

  1. is_live_check = true 이고 strategy_id 존재
  2. execution_type = CANDLE_CLOSE (또는 마감 시 evaluateRealtimeAtClose 보조)
  3. usesTimeframe(strategyId, candleType) — DSL에 해당 분봉 포함
  4. 동일 market:candleType:barEndEpoch 중복 평가 방지 (evaluatedBarCloseKeys)

평가: LiveStrategyEvaluator.evaluateSettingOnCandleClose()확정된 직전 봉 인덱스에서 1회

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

트리거: LiveStrategyScheduler3초 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 해당 marketis_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_done 0→1→2→3)
  • SELL 시그널마다 다음 매도 차수 금액만큼 수량 체결 (sell_stage_done 증가)
  • 전량 매도buy_stage_done·sell_stage_done 0으로 초기화
  • 분할 모드에서는 보유 중에도 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.validateBuygetOrDefault로 한도 행 없으면 초기자본 규모 한도 자동 생성
  • 체결 기록: gc_paper_trade, 원장: PaperLedgerService

8. 모의 수동매매 (시그널과 별도)

경로: TradeOrderPanelPOST /api/paper/ordersPaperTradingService.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(실행) 시:

  1. syncVirtualTargetsToBackend(targets, session, isLiveCheck=true)
  2. 종목별 saveLiveStrategySettingsisLiveCheck, executionType, positionMode
  3. putPaperAllocationsBulk — 종목별 한도 행(베스트 에포트)
  4. 전략 분봉 pin — pinStrategyEvaluationTimeframes

중지 시: stopVirtualLiveOnBackendisLiveCheck=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. 1차 매수 ≈ 950만 → 잔액 ≈ 50만
  2. 2차 ≈ 47.5만 → 잔액 ≈ 2.5만
  3. 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.tsloadTradeSignals, 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. 운영·디버깅 체크리스트

  1. 설정 → 모의투자: 활성화 ON, 자동매매 ON, 실행 대상 PAPER/BOTH
  2. 가상투자 Match: 대상 종목 isLiveCheck 백엔드 반영 여부
  3. 예수금: 투자관리 KPI 주문가능, 체결 후 cash_after
  4. 포지션: LONG_ONLY 시 매도 알림 vs 실제 보유
  5. 로그인: device_id null 이슈 — 자동 체결 기대 시 비회원 테스트 또는 백엔드 보완 여부 확인
  6. 로그 키워드: [TradeSignal] saved, [Paper] STRATEGY BUY, [Paper] auto trade failed, [OrderExecutionQueue]

15. 관련 문서


문서 기준: goldenChart 저장소 백엔드·프론트 구현 (자동 체결 백엔드 전용, 회원 live 설정 device_id=null 주문 큐 분기 포함).