코인 스캘핑 봇을 폐기한 이유
로컬 LLM을 걷어내고 백테스터·무작위 대조군까지 만들어 코인 스캘핑 전략을 두 달 검증하고 폐기한 회고. 실제 10만 원을 넣고 약 5천 원을 잃으며 배운 것.
🔒 개인 저장소라 코드는 핵심 조각만 발췌한다. 실제 운영·백테스트에서 나온 수치만 적었고, 지어낸 결과는 없다.
왜 만들었나
업비트 5분봉 단타를 눈으로 보며 대응하는 건 너무 피곤했고, 손절 타이밍도 자꾸 놓쳤다. 거래대금 터지는 코인을 자동으로 잡아서, 지표로 손절·익절을 칼같이 집행하는 봇을 만들고 싶었다.
첫 구상은 이거였다 — 지표는 파이썬이 계산하고, 최종 매수/매도는 로컬 LLM(gemma4)이 판단하는 AI 스캘퍼.
스포일러: 그 봇은 지금 안 돈다. 이 글은 성공기가 아니라 왜 접었는지에 대한 기록이다.
결과부터 — 폐기했다
약 두 달을 매달렸다. 백테스트만 돌린 게 아니라 업비트 계좌에 실제로 10만 원을 넣고 자동매매를 약 두 달 정도 24시간 실테스트도 계속 돌렸다. 결과는 5천원 손실로 싸게 잘 놀았다ㅋㅋ
📸 [스크린샷: 디스코드 봇 스캔 결과 / 매수·매도 알림 화면]
남은 건 두 가지다 — ① “개인이 기술지표로 코인 스캘핑하면 왜 안 되는가”에 대한 확실한 답, ② 어떤 전략이든 빠르게 거를 수 있는 검증 프레임워크. 그 5천 원은 손실이라기보다 이걸 알아낸 수업료에 가깝다고 생각한다.
전체 구조
[업비트 API] ── (pyupbit) ── [MCP 서버 · FastAPI] ← 스캔·지표·매매 엔진
│ (HTTP)
[디스코드 알림] ────────────── [Reporter 봇 · discord.py] ← 5·15분 루프
│
bot_state.json ← 재시작 대비 상태 영속화
원래는 저 MCP 옆에 로컬 LLM(Ollama · gemma4, Azure VM) 이 “브레인”으로 붙어 있었다. 그걸 떼는 게 첫 삽질이었다.
1단계 — LLM을 걷어냈다
코인 3개를 로컬 LLM으로 분석하는 데 4분 36초가 걸렸다. 이렇게까지 느렸던 결정적 이유는 GPU였다 — 당시 LLM을 올린 Azure VM엔 VRAM(GPU)이 없어서(VRAM 있는 모델은 비싸다ㅠㅠ) 모델이 CPU만으로 돌았다. GPU 가속 없이 CPU 추론으로 굴리니 코인 몇 개 분석에 몇 분씩 걸린 거다. 스캘핑에서 이 지연은 치명적이다. 18:20 데이터로 분석을 시작하면 결과가 18:24:36에 나온다. 이미 4분 전 유령 데이터를 보고 매수하는 뒷북 매매가 되는 거다.
게다가 LLM이 하는 일이라고 해봤자, 파이썬이 이미 계산해 둔 지표 텍스트를 받아 그대로 읽어주는 수준이었다. 0.001초면 끝날 if 문을 1분 넘게 연산하는 느린 조건문이었던 셈이다. 스캘핑과 체급이 안 맞았다.
그래서 LLM을 전면 제거하고 규칙 기반으로 갈았다. 분석 시간이 4분 36초 → 몇 초로 줄었다.
💡 스캘핑에선 속도(latency)가 곧 생명이다. 이미 수치로 다 계산해 둔 걸 LLM에 다시 묻는 구조는 데이터 신선도를 파괴하는 독소 조항이었다.
2단계 — 지표·선정 로직을 계속 갈아엎었다
LLM을 뺐다고 되는 게 아니었다. 여러 라운드에 걸쳐 뜯어고쳤다.
- 일봉 거래대금 풀 → 4시간봉 급등률 풀(1.15배·거래대금 1억↑)
- BB 확장률: 1캔들 → 최근 5캔들 평균 대비(노이즈 제거)
- MACD 크로스에 임계값 추가(미세 진동 무시)
- 미완성 캔들(
iloc[-1]) → 마감봉(iloc[-2]) ← 이거 안 하면 진행 중인 봉으로 판단해서 또 뒷북 - 익절 후 재진입 쿨다운, 상태 영속화, 손익절 루프 분리(감시 3초 / 스캔 15분)
후보 선택은 “덜 오른 초기 단계”를 고르려고 6요소 품질점수까지 만들었다.
# mcp.py — 후보 코인 품질점수 (거래량·RSI·MACD·BB확장·윗꼬리·종가위치)
def quality_score(ip: dict) -> float:
vol = min(math.log1p(max(ip['vol_ratio_raw'], 0)) / math.log1p(5), 1.0)
rsi_score = max(0.0, 1.0 - abs(ip['rsi'] - 58) / 30)
macd_score = 1.0 if ip['macd_rising'] > 0 else 0.3
# ... bb / 윗꼬리 / 종가위치 점수 조합
return round(vol*0.20 + rsi_score*0.25 + macd_score*0.15 + ..., 4)
3단계 — 백테스터를 지었다
실전만 돌려선 전략이 좋은지 알 수가 없었다. 그래서 백테스터를 짰는데, 핵심은 실전과 계산을 100% 일치시키는 것이었다. interpret·build_indicators를 그대로 복사해서 썼다.
- collector로 KRW 마켓 261개 코인의 5분·1시간봉 1개월치를 parquet으로 수집
- 봉 안의 고가/저가로 손익절 판정
- 룩어헤드 방지: 신호 다음 봉 시가에 진입, 한 봉에서 손절·익절 동시 터치 시 손절 우선 가정
- 수수료·슬리피지 반영
4단계 — 전략이 다 틀렸다는 걸 데이터로 확인했다
여기가 핵심이다. 무작위 대조군을 도입했다. “내 진입 신호가 무작위로 찍는 것보다 나은가?“를 직접 붙여서 검증했다.
| 전략 | 결과 |
|---|---|
| 5분봉 추세추종 (673건) | 무작위 대비 +2.1%p = 사실상 무작위 |
| 5분봉 평균회귀 (153건) | −0.6%p = 무작위 |
| 15분봉 추세추종 (lookahead 포함) | +12.7%p 우위였지만 표본 45건(과신 금물) |
| 15분봉 (lookahead 제거) | 승률 우위는 남았으나 기대값 −0.40% |
| 15분봉 + 품질점수 선택 | 개선 없음. 그리드 50칸 전부 마이너스 |
정리하면 이렇다. 5분봉은 방향 자체가 무작위였다. 15분봉은 진입에 미세한 승률 우위는 있었다 — 맞히긴 했다. 근데 그 우위가 왕복 비용 0.4%(수수료+슬리피지)를 못 넘어서 기대값이 음수였다.
🎯 이게 이 프로젝트의 한 줄 결론이다 — “맞히지만 못 버는 전략.” 진입을 좀 맞혀도 비용이 그 우위를 먹어버리면 기대값은 마이너스다.
후보 선택을 거래량 1등 → 품질점수로 바꿔도 결과가 그대로였다. 즉 선택 로직이 범인이 아니라, 전략 자체에 엣지가 없었다.
5단계 — 실제 돈을 넣고 돌렸다, 그리고 폐기
머리로는 “안 된다”가 나왔지만 미련이 남아서, 백테스트 최선 설정(15분봉 추세추종 + 품질점수, 손절 −1.5% / 익절 +3.0%)을 실제 10만 원 시드로 라이브 투입했다. 매수 3만 원 단위, 감시 3초, 누적 손실이 한도에 닿으면 신규 매수를 영구 중단하는 킬스위치까지 달았다.
두 달을 돌린 결과, 데이터가 내린 결론이 실전에서도 그대로였다.
- 약 5천 원 손실(시드의 5% 수준) — 크게 터진 것도, 버는 것도 아닌 느린 출혈
- 스캔 6,923회 중 조건 충족 코인 없음 3,193회(46%) — 조건이 빡세서 절반은 공침
- MCP 서버 연결 실패 등 인프라 에러도 잦았다
새벽 시간대엔 급등 풀이 아예 안 만들어져서 거래 자체가 드물었다. 그렇게 미련을 정리하고 접었다.
선물로 갈아탈까도 생각했는데 접었다. 음의 기대값 전략에 레버리지를 얹는 건 그냥 파산 가속기다.
회고 — 남은 것
10만 원 넣고 5천 원 잃었다. 근데 돈을 잃어서 접은 게 아니다. “개인이 기술지표로 코인 스캘핑하는 건 구조적으로 안 된다”를 데이터로 증명하고 접었다. 그 5천 원은 그걸 실증한 수업료였다.
그리고 진짜 자산은 따로 남았다 — 재사용 가능한 검증 프레임워크. 룩어헤드 방지, 무작위 대조군, 비용 모델링, 승률 대신 expectancy·PF로 보는 눈. 이제 어떤 전략을 들고 와도 며칠이면 거를 수 있다. (외부 피드백 3건도 여기 반영됐다 — lookahead 체크, 슬리피지 0.05→0.3% 보수화, 승률→expectancy 전환.)
멋진 “AI 매매봇”을 원했는데, 남은 건 “안 되는 걸 빠르게 아는 법” 이었다. 근데 그게 더 쓸모 있다.

댓글
익명 댓글입니다. 닉네임과 내용이 저장되며, 서로 존중하는 표현을 부탁드려요. 부적절하거나 광고성 댓글은 예고 없이 삭제될 수 있습니다.