티스토리 뷰

실전 운영에서 터진 것들 — 리스크 관리와 리팩토링

코드를 짜는 건 쉬웠다. 진짜 어려운 건 그 코드를 실전에서 돌리는 것이었다. 봇을 2개월 운영하면서 터진 것들, 그리고 그걸 수습하면서 배운 것들을 정리한다.

사건 1: 새벽 3시 폭락

어느 날 새벽, BTC가 10분 만에 7% 빠졌다. 봇은 손절 라인(-3.5%)을 찍고 매도했는데, 문제는 그 다음이었다. 가격이 잠깐 반등하자 봇이 다시 매수했고, 또 떨어져서 또 손절. 이걸 3번 반복했다. 하룻밤에 -8%.

이 사건 이후 비상 손절 시스템을 만들었다.

class EmergencyStop:
    def check(self, daily_loss_pct):
        # 일일 손실이 -5%를 넘으면 당일 매매 전면 중단
        if daily_loss_pct < -5.0:
            scheduler.pause()
            notifier.send("🚨 일일 손실 -5% 초과. 봇 자동 정지.")
            return True

        # 연속 3회 손절 시 1시간 쿨다운
        if self.consecutive_losses >= 3:
            self.cooldown_until = now() + timedelta(hours=1)
            notifier.send("⚠️ 연속 손절 3회. 1시간 쿨다운.")
            return True

        return False

일일 손실 한도, 연속 손절 쿨다운, 최대 포지션 크기 제한. 이 세 가지가 리스크 관리의 기본이었다.

사건 2: 메모리 누수

봇을 3일 연속 돌리면 메모리 사용량이 2GB를 넘어갔다. tracemalloc으로 추적해보니 범인은 두 가지였다.

  • market_snapshots를 메모리에 전부 들고 있었다. 쿼리 결과를 리스트로 받아서 슬라이싱하는 대신, SQL에서 LIMIT을 걸어야 했다.
  • WebSocket 연결이 끊어져도 이벤트 리스너가 해제되지 않았다. 클래식한 메모리 누수.

3일에 한 번 봇을 재시작하는 건 해결책이 아니다. 원인을 잡고 나서는 일주일을 돌려도 메모리가 300MB 이하로 유지됐다.

에러 핸들링 + 알림

운영에서 가장 중요한 건 "뭔가 잘못됐을 때 바로 아는 것"이다. Telegram 봇을 연동해서 알림 시스템을 구축했다.

  • 매매 체결 시 알림 (코인, 수량, 가격, 수익률)
  • 에러 발생 시 알림 (API 타임아웃, 잔고 부족 등)
  • 일일 정산 리포트 (총 자산, 수익률, 매매 횟수)
  • 봇 상태 변경 알림 (시작, 정지, 비상 정지)

업비트 API 에러 핸들링도 체계화했다. 429(Rate Limit)는 지수 백오프로 재시도, 5xx는 3회 재시도 후 알림, 인증 에러는 즉시 봇 정지. 에러별로 대응 전략을 명확히 정의하니까 새벽에 장애가 나도 봇이 알아서 처리하고, 내가 못 처리하는 건 알림으로 깨워줬다.

리팩토링: 클린 아키텍처

기능이 계속 붙으면서 코드가 스파게티가 되어갔다. main.py가 800줄을 넘어가는 걸 보고 리팩토링을 결심했다.

src/cryptobot/
├── core/          # 도메인 로직 (전략, 지표, 리스크 관리)
├── infra/         # 외부 연동 (업비트 API, DB, Telegram)
├── api/           # FastAPI 서버
├── services/      # 유스케이스 (매매 실행, 데이터 수집, 정산)
└── config/        # 설정 관리

핵심은 의존성 방향을 안쪽으로 통일한 것이다. coreinfra를 모른다. 전략 로직이 업비트 API를 직접 호출하지 않고, 인터페이스를 통해 데이터를 받는다. 이렇게 하니까 백테스팅할 때도 같은 전략 코드를 쓸 수 있었다.

CI/CD

GitHub Actions로 CI를 구성했다. PR마다 pytest 실행, ruff 포매팅 체크, 타입 체크. 메인 브랜치에 머지되면 서버에 자동 배포. 배포 스크립트는 봇을 graceful shutdown 하고, 코드 업데이트 후 재시작한다.

# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -e ".[dev]"
      - run: ruff check .
      - run: pytest --cov=cryptobot

시리즈를 마치며

한 달 정도 봇을 돌리면서 단순한 운영 이상의 경험을 얻었다. 실시간 시스템 운영, 장애 대응, AI를 실용적으로 활용하는 감각, 백테스팅으로 가설을 검증하는 습관 — 수익보다 큰 가치였다.

그런데 이 모든 안전장치를 다 갖췄다고 생각한 시점에, Phase 2를 실전에 올리자마자 하루 만에 hotfix 6개를 쏟아내는 사건이 터졌다. 다음 편에서 그 12시간을 풀어본다.


반응형
댓글