본문 바로가기

AI 기반 자동매매 & 금융데이터 분석

5편. 실시간 자동매매 시스템 구축 — FastAPI · vLLM · 전략 Executor 설계

반응형

 

⚡️ AI + 주식 자동매매 프로젝트 공유

5편. 실시간 자동매매 시스템 구축 — FastAPI · vLLM · 전략 Executor 설계

실전 자동매매 시스템은 **지연(latency), 안정성, 안전장치(리스크 제어)**가 관건입니다.
여기서는 FastAPI를 실시간 API 레이어로 사용하고, vLLM(로컬/호스팅 LLM)을 신호 보정/해석에 활용하며, 전략 Executor가 주문 결정과 브로커 연동을 책임지는 구조를 제시합니다.


1. 전체 아키텍처 (권장)

[Market Data Sources] -> [Ingest / Normalizer] -> [Feature Store / Vector DB]
                                 |
                      ┌──────────┴──────────┐
                      |                     |
                [ML Inference]         [vLLM Inference]
                      |                     |
                      └─────[Meta Signal Engine]─────┐
                                         |          |
                                     FastAPI (API Layer)
                                         |          |
                                 [Strategy Executor] ──> [Broker Adapter]
                                         |
                                 [Risk Engine / Circuit Breaker]
                                         |
                                 [Trade Blotter / DB / Alerting]

주요 원칙:

  • 비동기 처리로 데이터 수집·추론과 주문 실행 분리.
  • **결정 논리(Executor)**는 항상 idempotent(같은 시점에 한 번만 실행)해야 함.
  • **로깅/감사(Trade Blotter)**는 모든 주문 전/후에 남겨야 함.

2. 컴포넌트 역할 요약

  • Ingest / Normalizer: 실시간 체결·호가를 정규화, feature store에 적재.
  • ML Inference: XGBoost/LSTM 등 빠른 예측 모델(벡터화 추론).
  • vLLM Inference: 뉴스·공시 해석, 시점별 보정·설명 생성(비교적 느리므로 분당/주기적 사용).
  • Meta Signal Engine: ML + vLLM 결과를 결합해 최종 시그널 생성.
  • FastAPI: 외부/내부로 시그널, 주문 요청, 상태 조회 제공.
  • Strategy Executor: 시그널을 주문으로 변환, 브로커에 전송, 체결 로직 적용.
  • Risk Engine: 포지션/노출 한도, 슬ippage/ADV 제한, circuit breaker.
  • Broker Adapter: 실제 브로커 API (REST/WebSocket) 연결 및 주문 관리.

3. FastAPI 핵심 설계 (비동기 + 인증 + 상태 조회)

간단한 FastAPI 서버 골격(핵심부분만):

# app/main.py
from fastapi import FastAPI, BackgroundTasks, HTTPException
from pydantic import BaseModel
import asyncio
import uvicorn

app = FastAPI(title="AutoTrader API")

# 의사 데이터 구조
class Signal(BaseModel):
    symbol: str
    side: str  # 'buy' or 'sell'
    size: int
    score: float
    ts: str

# 내부 큐(간단 예)
order_queue = asyncio.Queue()

@app.post("/api/v1/signal")
async def receive_signal(sig: Signal, background_tasks: BackgroundTasks):
    # 간단한 인증/검증은 생략(실무: OAuth2/mTLS)
    # 시그널 수신 → executor로 전달
    await order_queue.put(sig.dict())
    return {"status":"queued", "symbol": sig.symbol}

@app.get("/api/v1/status")
async def status():
    return {"queue_size": order_queue.qsize()}

# background worker example
async def executor_worker():
    while True:
        sig = await order_queue.get()
        try:
            # 실제로는 비동기 broker adapter 호출
            await execute_signal(sig)
        except Exception as e:
            # 실패 로깅 + 알람
            print("EXEC ERROR", e)
        finally:
            order_queue.task_done()

async def execute_signal(sig):
    # 여기는 Executor 로직으로 대체된다.
    print("Executing", sig)
    await asyncio.sleep(0.1)  # 모의 체결

@app.on_event("startup")
async def startup_event():
    # 워커 3개 띄우기
    for _ in range(3):
        asyncio.create_task(executor_worker())

if __name__ == "__main__":
    uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=False)

실무 권장 사항:

  • Uvicorn + Gunicorn(uvloop) 조합으로 멀티 워커 운영.
  • 인증: mTLS 또는 OAuth2 + JWT, 내부 서비스는 mTLS 권장.
  • Health check(읽기 전용 endpoint)와 readiness probe 설정.

4. Strategy Executor 설계 (핵심 로직)

Executor는 단순 주문 송신기가 아니라 체결 모델, 리스크 체크, 재시도, 부분체결 처리를 담당합니다.

핵심 흐름:

  1. 시그널 수신 → ID 발급(UID) → 시그널 검증(중복/타임스탬프)
  2. 리스크 엔진 호출(노출 한도, 포지션 제한, 시장 상황)
  3. 주문 분할(슬리피지/ADV 대비) → Broker Adapter로 전송
  4. 체결 응답 수신 → 트레이드 블로터 기록
  5. 포지션·캐시 업데이트 → 알람(조건 충족 시)

간단한 Executor 코드 (핵심만):

# executor.py
import uuid
from datetime import datetime

class RiskEngine:
    def __init__(self, max_notional):
        self.max_notional = max_notional

    def permit(self, symbol, side, notional):
        # 예시: 총 노출 제한 검사 (실무는 포지션별/시장별 고도 로직)
        return notional <= self.max_notional

class BrokerAdapter:
    async def place_order(self, symbol, side, qty, price=None):
        # 실제 브로커 호출 (REST/WebSocket)
        # 응답: {order_id, status, filled_qty, avg_price}
        return {"order_id": "abc", "status": "filled", "filled_qty": qty, "avg_price": price or 100.0}

async def handle_signal(sig, risk_engine:RiskEngine, broker:BrokerAdapter):
    uid = str(uuid.uuid4())
    ts = datetime.fromisoformat(sig['ts'])
    notional = sig['size'] * 100  # 예시 가격 가정
    if not risk_engine.permit(sig['symbol'], sig['side'], notional):
        # 거부 로깅 및 알림
        return {"uid": uid, "status": "rejected"}

    # 주문 분할 로직 (ADV 대비)
    chunks = [sig['size']]  # 단순화: 실제는 분할
    trades = []
    for qty in chunks:
        resp = await broker.place_order(sig['symbol'], sig['side'], qty)
        trades.append(resp)
        # 부분 체결/재시도 로직 추가
    # 트레이드 로그 저장
    return {"uid": uid, "status": "executed", "trades": trades}

주의:

  • 브로커 장애 시 롤백 불가 → idempotency key로 중복 주문 방지.
  • 브로커 별 응답 시간 다름 → 타임아웃 엄격 설정.

5. vLLM(또는 LLM) 활용 패턴 (실시간 보정 전략)

vLLM은 대형 모델을 로컬 혹은 전용 서버에 배포해 느리지만 해석적 보정을 맡기는 방식으로 쓰세요.

패턴:

  • Batch inference: 뉴스/공시가 발생할 때마다 배치로 LLM 호출(분 단위)
  • 요약 + 영향도 평가: LLM이 요약문과 영향 점수를 반환 → Meta Signal Engine에서 가중치 적용
  • Explainability: LLM 결과를 로그로 남겨 판단 근거 제공 (운영자 모니터링에 중요)

프롬프트 설계 원칙:

  • 입력 길이 제한 고려(핵심 문장만 제공)
  • 명확한 출력 형식(JSON)을 요구
  • 비용·지연 최소화를 위해 사전 필터링(중요 기사만) 적용

6. 리스크 엔진 & Circuit Breaker

실전에서는 다음 안전 장치 필수:

  • 노출 상한: 종목별/종목군별/포트폴리오 총액 상한
  • 포지션 체크포인트: 실시간 마크투마켓 손익 기준으로 자동 청산 트리거
  • 시장 서지 보호: 거래량 폭증/스프레드 급팽창 시 자동 거래 중지
  • 시간 기반 차단: 일정 시간 내 연속 실패(예: 5회) 시 휴지 상태로 전환
  • Kill Switch: 운영자가 즉시 전체 자동매매 정지 가능

간단한 Circuit Breaker 상태 머신:

STATE: NORMAL -> (n 실패) -> DEGRADED -> (m 실패) -> HALT

7. 모니터링·로깅·알림

  • Trade Blotter: DB(ClickHouse/Postgres)에 모든 주문·체결·수수료 기록
  • Metrics: Prometheus로 latency, queue depth, success rate, P&L 집계
  • Dashboards: Grafana/Streamlit로 실시간 포지션/PNL/시그널 품질 시각화
  • Alerting: Slack/Teams/메일 + SMS(중요)로 실패·리스크 이상 알림
  • Trace: OpenTelemetry로 요청 트레이스 (FastAPI → Executor → Broker)

8. 배포·운영(실무 권장)

  • 컨테이너화: Docker + Kubernetes(또는 ECS)
  • 구성 관리: Helm/Terraform으로 인프라 선언적 관리
  • 비밀 관리: Secrets Manager / Vault (API 키, 브로커 자격증명)
  • Canary 배포: 모델/전략 코드 변경 시 Canary로 소량 자금에만 적용 후 전체 롤아웃
  • 로컬 개발: 브로커 에뮬레이터(샌드박스)로 실제 주문 없이 통합 테스트 수행

9. 실제 운영에서 마주친 문제와 해결 사례

  • 문제: 브로커 주문 응답 지연으로 중복 주문 발생
    → 해결: idempotency key + 주문 상태 폴링 대신 WebSocket 체결 콜백 사용으로 정합성 확보.
  • 문제: vLLM 추론 비용·지연으로 실시간 의사결정이 느려짐
    → 해결: LLM은 보정용(분 단위), ML은 실시간(초 단위) 역할 분리. LLM 결과는 포스트팩터로만 반영.
  • 문제: 급락장(Flash Crash)에서 포지션 과다 노출
    → 해결: ADV 기반 포지션 제한 및 시장충격 감지 시 자동 비상정지(계정 단위).

10. 실전 팁 3가지

✔ TIP 1 — 실시간과 비실시간 역할을 명확히 분리하라

실시간(초 단위): 가격 피드·ML 예측·즉시 체결.
비실시간(분~시간): LLM 해석·심층 리포트.
역할을 섞으면 지연·비용·리스크가 늘어납니다.


✔ TIP 2 — 모든 주문에 대해 Idempotency Key를 사용하라

네트워크/브로커 장애로 인한 중복 주문 위험을 막습니다.
strategy_id + signal_ts + uid 조합으로 키를 만들면 충분합니다.


✔ TIP 3 — Canary 배포로 실제 자금 위험을 줄여라

새 전략/모델을 바로 전체 자금에 적용하지 말고, 소액으로 Canary 테스트 후 문제 없을 때만 확장하세요.
실거래 로그와 백테스트 로그를 항상 대조해 이상 징후를 빨리 찾을 수 있습니다.


11. 코드 · 템플릿 저장소 제안 (운영 체크리스트)

프로젝트 루트에 다음 저장소 구조 권장:

/infra       # k8s, terraform, helm
/app
  /api       # FastAPI
  /executor  # 전략 Executor
  /models    # ML 모델 서빙 코드
  /llm       # vLLM 프롬프트/서빙
/tests       # 통합 테스트 / broker emulator
/docs

각 파트에 README.md로 실행·테스트 절차를 명시하세요.


12. 다음 편 예고 (원하시면 작성)

6편. 운영 중 발생하는 모델 드리프트 탐지와 온라인 재학습 파이프라인

  • 데이터·성능 드리프트 감지
  • 포괄적 A/B 테스트와 룰 기반 롤백
  • 안전한 온라인 학습 전략

원하시면 6편을 지금 바로 작성해 드립니다.


📌 추천 태그

#자동매매 #실시간트레이딩 #FastAPI #vLLM #AI트레이딩
#전략엔진 #리스크관리 #백오피스 #Kubernetes #Quant

 

반응형