[CoinWhale] 5. "완벽한 데이터, 그리고 처참했던 실패" - 퀀트 자동매매 1, 2세대 회고와 11가지 교훈"

비비드백·2026년 5월 3일
post-thumbnail

시리즈: 암호화폐 실시간 데이터 파이프라인 & 퀀트 자동매매 구축기

1. 완벽한 파이프라인?, 그리고 헛된 희망

Kafka부터 Spark, 그리고 ClickHouse까지 이어지는 거대한 실시간 데이터 파이프라인이 완성되었을 때, 생각했습니다.

"이 데이터로 자동매매를 돌리면 돈을 벌 수 있지 않을까?"

제 피 같은 진짜 돈을 잃을 순 없으니, 우선 가상의 돈으로 투자해보는 페이퍼 트레이딩(Paper Trading, 모의투자)을 시작했습니다. 파이프라인 데이터를 믿기도 했고 결과가 궁금했습니다.

하지만 결론부터 말씀드리면, 제 자동매매 봇은 처음 두 번 모두 처참하게 망했습니다.
이 글은 처음 퀀트 투자를 시도하며 겪었던 뼈아픈 헛발질의 기록입니다.

2. 사라진 데이터, 그리고 남은 단서들

본격적인 실패담을 시작하기 전에 한 가지 고백할 것이 있습니다.

초기에는 봇의 로직이 마음에 안 들면 "에이, 데이터 다 날리고 처음부터 다시 돌리자!" 라며 ClickHouse DB를 수시로 초기화해버렸습니다. 실험 기록을 차곡차곡 모아야 한다는 기본을 무시한 채, 당장 눈앞의 봇을 수정하는 데만 급급했습니다.

덕분에 초기 실패 기록의 상당수가 날아갔지만, 다행히 백업해둔 158건의 초기 거래 내역(기록)을 찾을 수 있었습니다. 이 158번의 거래가 도대체 '왜' 끝났는지(청산 사유)만 살펴봐도, 제 봇이 얼마나 엉망이었는지 한눈에 알 수 있습니다.

청산 사유 (종료 이유) 건수 평균 수익금 승률
추세 꺾임 (momentum_exit) 46건 -0.19 USDT 28%
손절선 터치 (stop_loss) 44건 -1.83 USDT 34%
목표가 도달 (take_profit) 18건 +3.92 USDT 100%
수익 보존 (trailing_stop) 17건 +0.74 USDT 100%
반대 신호 뜸 (signal_exit) 10건 -0.29 USDT 20%

💡 쉽게 풀어보는 전략 용어
take_profit (익절): "오! 목표했던 +2% 수익 달성!" (기분 좋게 종료)
stop_loss (손절): "앗, -1%까지 떨어졌어! 더 잃기 전에 팔자!" (눈물을 머금고 종료)
momentum_exit (추세 꺾임): "목표가까진 안 갔는데, 오를 힘이 빠진 것 같아. 그냥 여기서 닫자."
trailing_stop (수익 보존): "수익 중이었는데 가격이 조금 떨어지네? 본전 되기 전에 지금 챙기자."

표를 보면 성공적인 거래(take_profit, trailing_stop)는 고작 35건뿐입니다. 나머지는 전부 손절당하거나 이도 저도 아니게 흐지부지 끝났죠.

이건 봇의 '매도능력(청산)'이 부족했다기보다는, 애초에 '매수 자리(진입)' 자체가 형편없었다는 뜻입니다. 도대체 봇은 어떤 기준으로 매수를 했길래 이런 결과가 나왔을까요?

3. 1세대 봇: "고래가 산다고? 무조건 따라 사!" (2026년 3월)

첫 번째 봇의 구조는 아주 단순했습니다.

CoinWhale 파이프라인은 고래들의 매수/매도 흐름(CVD)을 집계해 gold.signals라는 테이블에 쌓아줍니다. 1세대 봇은 딱 이 테이블만 폴링(Polling)하고 있다가, 수많은 신호 중에서도 확실하다고 판단되는 "CRITICAL 등급(level=CRITICAL)"의 알람이 울리면 묻지도 따지지도 않고 따라 사는 전략을 썼습니다. (참고로 약한 신호인 WARNING 등급도 있지만 봇은 이를 무시했습니다.)

진입: 고래의 강한 매수(CVD 상승) 알람이 울리면 롱(매수).
청산: 선물 시장의 기준 가격(mark_price)을 보고, 진입가 대비 +2% 오르면 익절, -1% 떨어지면 손절.

💥 무엇이 문제였을까? (백테스팅의 배신)

나름 그럴싸해 보이지만, 과거 데이터를 넣고 시뮬레이션(백테스팅)을 돌려보니 충격적인 사실이 드러났습니다. (2026년 3월 초순, 10일 치 BTC 10초봉 데이터 기준)

제가 설정했던 '고래의 강한 매수 알람' 기준점(CVD > 50)은 사실 너무 낮은 수치였습니다. 승률을 계산해 보니 50.2%가 나왔습니다. 수수료를 떼고 나면 사실상 동전 던지기보다 못한 노이즈(가짜 신호)에 불과했던 겁니다. 정말로 강력한 추세를 타려면 기준점을 훨씬 높여야(CVD > 300) 승률이 60.9% 위로 올라온다는 걸 뒤늦게 깨달았습니다.

더 충격적인 건 '청산 데이터'의 배신이었습니다. 코인판에는 "숏 친 사람들이 강제 청산을 당하면서 가격이 위로 솟구친다(숏 스퀴즈)"라는 유명한 통념이 있습니다. 저도 이 통념을 믿고 청산 알람이 뜰 때 롱을 타도록 로직을 짰는데, 막상 10일 치 데이터를 까보니 승률이 45%로 오히려 거꾸로 가는 경우가 더 많았습니다. 근거 없는 통념이 계좌를 갉아먹고 있었던 셈입니다.

4. 2세대 봇: "온갖 지표에 LLM까지 더하면 완벽하겠지?" (2026년 4월)

1세대의 뼈아픈 실패를 거울삼아, 2세대 봇(Hybrid Trader)은 훨씬 똑똑하게 만들기로 다짐했습니다.

1세대 봇이 단순히 '시그널 알람 테이블'만 쳐다봤다면, 2세대 봇은 데이터 아키텍처를 바꿔 ClickHouse에 쌓인 '시장 인사이트 뷰(gold.market_insights)'를 직접 길게 읽어오도록 했습니다.

이때 봇은 최근 30분 치의 지표를 분석해야 하는데, 데이터 파이프라인 앞단의 Spark가 트래픽 폭주 등으로 실시간 처리가 아주 미세하게 늦어질(Delay) 때가 있습니다. 이를 방어하기 위해 처리 시간 버퍼(Buffer) 2분을 더 주어 '최대 32분 치'의 데이터를 넉넉하게 긁어오도록 설계했습니다.

이렇게 안전하게 읽어온 30분 치의 넓은 데이터 위에 MACD, 볼린저 밴드, RSI 등 온갖 유명한 지표를 섞어서 아주 정교한 진입 신호를 만들었습니다. 그리고 혹시 모를 가짜 신호를 걸러내기 위해 최신 AI(LLM)에게 "지금 진짜 사도 될까?"라고 마지막 승인(Veto)을 받는 기능까지 넣었습니다.

"이 정도면 승률이 있어보인다!"

그런데 봇의 스위치를 켰더니... 매매를 아예 안 하는 겁니다.

💥 또 무엇이 문제였을까?

원인은 저의 '감(Feeling)'에 있었습니다. 지표들이 넘어야 할 허들(임계값)을 설정할 때, 전체 데이터의 평균이나 분포를 차분히 분석하지 않았습니다. 그저 최근 며칠 동안 비트코인이 미친 듯이 내렸던 기억만 가지고 "진짜 확실한 추세라면 이 정도 높은 수치는 찍어야지!" 라며 허들을 너무 높게 잡아버린 겁니다.

심볼 감으로 설정한 CVD 허들 실제 시장의 평균 60분 CVD
BTCUSDT 1,000 이상이어야 매수 대략 ~162 수준

실제 평상시 비트코인 평균 수치는 160 정도였으니, 제가 설정한 1,000은 봇 입장에서는 평생 뛰어넘을 수 없는 벽이었던 것이죠.

허들을 확 낮추고 다시 봇을 돌렸습니다. 그 결과 836건의 거래가 발생했는데 (내부 운영 로그 기준), 여기서 두 번째 참사가 일어납니다.

2세대 봇 청산 사유 건수 비율
너무 오래 들고 있어서 강제 종료 (max_hold_time) 615건 73.6%
지표 조건 달성으로 조기 익절 (bb_upper_touch 등) 144건 17.2%
기타 (반대 신호, 손절 등) 77건 9.2%
지정해둔 목표가 도달 (take_profit) 0건 0%

볼린저 밴드 상단 터치 등 지표를 통한 소소한 조기 익절은 있었지만, 정작 기계적으로 지정해둔 시원한 수익 목표(TP)에 닿은 적은 단 한 번도 없었습니다. 무려 73%의 거래가 "방향이 안 나와서 지정된 시간(예: 8시간)이 초과되어 강제 종료" 되었습니다.

왜 그랬을까요? 허들을 너무 낮췄더니, 가격이 위로 갈지 아래로 갈지 모르는 애매한 횡보장에서도 봇이 "오! 신호가 떴다!"며 무작정 샀기 때문입니다. 애매한 자리에서 샀으니 가격이 시원하게 오를 리가 없고, 결국 시간만 죽이다가 수수료만 내고 포지션을 닫은 것입니다.

게다가 봇은 자금 관리의 개념도 없었습니다. AI가 보기에 승률 90%짜리 확실한 자리든, 50%짜리 애매한 자리든 똑같이 100만 원씩 베팅했습니다. 리스크 관리가 전혀 안 되는 도박꾼이나 다름없었죠.

5. 뼈아픈 실패가 남긴 11가지 교훈 (체크리스트)

처참했던 두 번의 실패는 제게 퀀트 투자의 아주 매운맛을 보여주었습니다. 혹시라도 직접 자동매매 봇을 만들어보려는 분들이 계신다면, 제가 겪었던 시행착오를 피할 수 있도록 11가지 팁을 남깁니다.

  1. 지표 이름에 속지 말고 '단일 승률'부터 확인하세요. 아무리 유명하고 멋진 보조지표라도, 그 지표 하나만으로 과거 차트에서 승률이 50%를 넘지 못한다면 그냥 쓰레기입니다. 여러 지표를 섞기 전에 하나씩 검증하세요.
  1. 조건값(허들)은 '감'이 아니라 '통계'로 정하세요.
    "이 정도면 높겠지?"는 통하지 않습니다. 실제 데이터의 상위 10%, 상위 5% 수치를 직접 뽑아서 기준으로 삼아야 합니다.
  1. '목표가 도달(Take Profit)'이 0건이면 진입 로직이 틀린 겁니다.
    아무리 손절과 시간제한을 잘 걸어둬도, 익절이 안 나간다면 봇이 '수익이 날 수 없는 애매한 자리'에서 자꾸 물건을 사고 있다는 뜻입니다.
  1. '보유 시간 초과'가 많다고 보유 시간을 늘리지 마세요.
    시간이 지나서 강제 청산되는 일이 잦다면, 진입 자체를 더 깐깐하게 하도록 필터를 씌워야 합니다.
  1. 코인마다 성격이 다릅니다. 똑같은 잣대를 대지 마세요.
    비트코인과 솔라나는 움직임의 스케일이 다릅니다. 하나의 설정값을 모든 코인에 똑같이 복붙하면 무조건 사고가 납니다.
  1. "반대 신호가 나오면 즉시 판다"는 생각보다 위험합니다.
    코인 시장은 잔파도(노이즈)가 심합니다. 반대 신호가 살짝 떴다고 샀다 팔았다를 반복하면 수수료로 계좌가 다 녹습니다.
  1. 거래가 끝난 직후엔 봇에게 '쉬는 시간(쿨다운)'을 주세요.
    손절이 나갔는데 1초 뒤에 또 똑같은 자리에서 진입하는 바보 같은 짓을 막아줍니다.
  1. 포지션마다 '이름표(고유 ID)'를 확실히 붙이세요.
    "지금 비트코인 산 거 팔아"라고 명령하면 봇이 헷갈립니다. "오후 1시에 샀던 주문번호 123번 팔아"라고 명확히 관리해야 버그가 없습니다.
  1. 백테스팅(과거 시뮬레이션)과 실제 운영 코드는 100% 똑같아야 합니다.
    과거 데이터 돌릴 때 나오는 수익률을 맹신하지 마세요. 실제 봇에 달아둔 '방어 로직'들이 시뮬레이션에 빠져있으면, 수익률은 그저 허상일 뿐입니다.
  1. AI(LLM)는 요술 방망이가 아닙니다.
    구린 데이터를 AI에게 주면서 "수익 내줘"라고 해봤자 덜 구린 손실을 고를 뿐입니다. 확실한 신호를 먼저 만들어내는 게 우선입니다.
  1. 망한 기록도 데이터입니다. 절대 DB를 함부로 날리지 마세요.
    "전략 구리네, 리셋!" 하는 습관은 내일의 내가 똑같은 실수를 반복하게 만듭니다. 실패 기록을 영구히 보존해야 원인을 분석할 수 있습니다.

6. 핵심 인사이트 (한 번에 읽는 요약)

데이터와 시그널은 다릅니다: 1세대는 정제된 signals 테이블의 CRITICAL만 봤고, 2세대는 뷰를 직접 길게 읽어왔습니다. 세대마다 바라보는 '로직'이 다르면 결과도 달라집니다.

그럴듯한 통념은 의심하세요: "청산이 터지면 반등한다"는 통념은 실제 백테스트에서 역방향으로 나왔습니다. 반드시 단일 승률부터 검증하세요.

청산 사유(exit_reason)가 전략의 진짜 실력입니다: TP(목표가 도달)가 0건이고 max_hold_time이 압도적이라면, 그것은 익절 전략이 아니라 그저 '시간 때우기' 전략일 뿐입니다.

LLM은 알파(수익)가 아니라 필터입니다: 나쁜 신호들만 주어지면 LLM은 그저 "덜 나쁜 손실"을 고를 뿐입니다.

실패는 원장 없이 무한히 반복됩니다: DB를 쉽게 리셋하는 습관을 버리고, 반드시 과거의 매매 이벤트들을 보존하는 아카이브를 구축하세요.

7. 다음은 진짜 투자를 위한 '자본 관리'다

"봇이 거래를 자주 한다고 돈을 버는 게 아니다. 정말 확실한 자리에만 들어가고, 그 확실함에 비례해서 투자 금액을 조절해야 한다."

두 번의 실패 끝에 내린 결론입니다.

지금까지의 봇은 그저 '차트에 줄 긋고 신호등 켜지면 매수하는' 수준이었습니다. 다음 편에서는 이 뼈아픈 교훈들을 바탕으로, 봇에게 '돈을 관리하는 법(포트폴리오 매니저)''유연하게 손절/익절하는 법(ATR 기반 동적 청산)'을 가르쳐 환골탈태한 v3.0 봇 구축기를 다루어보겠습니다.

다음 파이프라인 글: 파이프라인 5편 — Spark OOM + PM2 재시작 루프

다음 퀀트 글: 퀀트 2편 — v3.0: 자본관리와 동적 SL/TP로 다시 짜다

profile
비비드백

0개의 댓글