티스토리 뷰

Phase 2를 실전에 올리자마자 하루 만에 hotfix 6개

AI 분석, 멀티코인, 대시보드까지 완성한 Phase 2. 테스트도 통과했고, 로컬에서도 잘 돌았다. 실전에 올리면 되겠지? 올리고 나서 12시간 동안 6개의 hotfix를 쏟아냈다. 테스트에서 못 잡는 버그들이 있다.

버그 1: DataFrame의 bool 변환 — 새벽 2시 전면 중단

01:56, 전 코인 매매가 중단됐다. 에러 로그를 보니 ValueError: The truth value of a DataFrame is ambiguous.

# 문제 코드
if not df or not s:
    return

# pandas DataFrame은 bool 변환이 안 된다
# 수정
if df is None or s is None:
    return

Python 기본 문법이지만, pandas를 쓰면 이 함정에 반드시 빠진다. if not df는 빈 DataFrame에도, 데이터가 있는 DataFrame에도 에러를 뱉는다. pytest에서는 mock 데이터가 None이 아니라서 통과했고, 실전에서 API가 빈 DataFrame을 반환하는 순간 터졌다.

버그 2: LLM 스케줄러 — 재시작하면 4시간 대기

APScheduler의 interval=hours=4는 봇 시작 시점 기준이다. 봇을 껐다 키면 4시간 카운터가 리셋된다. 버그 패치하려고 재시작했더니 LLM 분석이 4시간 동안 안 돌았다.

# 수정: interval을 10분으로 줄이고, 실제 실행 여부는 DB에서 판단
scheduler.add_job(llm_analysis, 'interval', minutes=10)

def _should_run(self):
    last = db.get_last_analysis_time()
    return (now() - last).total_seconds() > 4 * 3600

스케줄러 간격과 비즈니스 로직 간격을 분리한 것. 이렇게 하면 재시작 후에도 4시간이 지났으면 10분 내 즉시 실행된다.

버그 3: LLM 파라미터 머지 누락 — rsi_oversold 4회 연속 고정

LLM이 bb_std: 2.5만 보내면, 기존 코드는 전체 파라미터를 교체해버렸다. 결과적으로 rsi_oversold가 유실되고 기본값 30으로 고정. 4번 연속 매수 기회를 놓쳤다.

# 문제: 전체 교체
strategy.params = llm_response["params"]

# 수정: 기존 값에 머지
strategy.params = {**current_params, **llm_response["params"]}

거기에 _fill_param_defaultsrsi_oversold, bb_std가 아예 없었다. LLM이 생략하면 복구 불가. 기본값 테이블에 전략 파라미터 전부 추가하고, before/after 스냅샷도 넣어서 뭐가 바뀌었는지 추적할 수 있게 했다.

버그 4: 수수료 가드 통과 후 마이너스 — 0.1%의 함정

XRP를 +0.10%에서 트레일링 스탑으로 매도했다. 수수료 가드(왕복 0.1%)를 통과했으니 수익이어야 하는데, 실제 체결 후 -0.05% 손실.

가격 기준으로는 +0.1007%로 가드를 통과했지만, 시장가 매도 시 슬리피지가 발생한다. 호가창 깊이에 따라 체결 가격이 약간 불리해지는 건 당연한데, 수수료 가드에 이 마진을 안 넣었다.

# 수정: 수수료 0.1% + 슬리피지 마진 0.05%
ROUND_TRIP_FEE_PCT = 0.15  # 기존 0.1

버그 5: 트레일링 스탑 오발동 — 46만원이 XRP에 적용되다

이게 가장 황당했다. TAO를 460,800원에 모니터링하다가 XRP 체크로 넘어갔는데, _highest_price가 TAO 값(460,800)으로 남아있었다. XRP 현재가 3,500원과 비교하니 하락률 -99.6%. 트레일링 스탑이 즉시 발동해서 +0.15% 본전에 매도.

원인은 #99에서 예측한 그대로였다. 전략 인스턴스가 코인 간 공유되면서 상태가 오염된 것.

# 수정: 코인별 상태 분리
_coin_highest_prices = {}  # {"KRW-XRP": 3510, "KRW-TAO": 460800}

# 틱 시작 시 복원, 종료 시 저장
strategy._highest_price = _coin_highest_prices.get(coin, 0)

버그 6: LLM 프롬프트 빈약 — Haiku가 보수적으로만 판단

LLM이 rsi_oversold를 4회 연속 생략했다. 프롬프트를 다시 봤더니, 매수 로직 설명이 없었다. Haiku는 전체 승률 21%를 보고 위축되어 매수 파라미터를 건드리지 않았던 것.

프롬프트에 전략 매수 로직(bb_rsi_combined의 동작 원리), 현재 적용 중인 파라미터, 전략별 승률 해석 가이드를 추가했다. 그리고 필수 파라미터 누락 시 재시도 프롬프트를 보내서 한 번 더 요청하도록 했다.

12시간의 교훈

6개 버그의 공통점: 전부 테스트에서 못 잡는 종류였다. mock이 실전과 다르고, 재시작 시나리오를 테스트 안 했고, 코인 간 상태 공유는 단일 코인 테스트에서 드러나지 않았다. 수수료 슬리피지는 백테스팅에서도 놓치기 쉬운 부분이다.

실전 투입 후 첫 24시간이 가장 중요하다. 이때 로그를 계속 보면서 잡아야 한다. 다음 글에서는 이 경험을 바탕으로 "사람이 안 봐도 되는 봇"을 만든 이야기를 쓴다.


반응형
댓글