어제(4/10) 핫샷을 올리고 나서 며칠만 지켜볼 생각이었다. 그런데 대시보드를 열어놓고 보니 한 가지가 계속 걸렸다. 핫샷은 상승장만 찾는다. EMA 10/20/50 정배열 + 3MA 기울기 상승 + 볼륨 급등 + MACD 양수. 완벽한 LONG 신호기다. 그런데 2025년처럼 한 달에 한두 번 큰 하락 구간이 오는 시장에서, LONG만 잡는 봇은 그 구간을 통째로 잃는다.
거울 전략이 필요했다. 같은 로직, 반대 방향, 다른 거래소. 정확히 그것만 하는 시스템.
문제는 비어있지 않았다. /trade/에 쿼터샷이라는 Binance Futures 봇이 이미 돌고 있었다. 꼬리 캔들 반전 전략. 근데 누적 50% 손실 상태로 몇 주 전에 내가 직접 꺼놨다. 대시보드만 남겨두고 방치해둔 무덤이었다.
쿼터샷의 DB 테이블, API, 대시보드, 워크플로우는 전부 멀쩡했다. 전략 로직만 틀렸다. 인프라를 버리는 건 아깝고, 같은 자리에 새 로직을 올리는 건 자연스러웠다. 이름만 바꾸면 그대로 쓸 수 있는 껍데기가 남아있었다.
그래서 오늘의 작업은 이거였다. 쿼터샷을 묻고, 같은 자리에 콜드샷(ColdShot)을 세운다. /trade/ → /cold/. trade_* 테이블 → cold_*. 꼬리 캔들 → EMA 역배열. 근데 단순 rename이 아니었다. 전략 로직이 완전히 다르기 때문에 실질적으로 "모든 걸 새로 쓰되 디렉토리 구조만 쿼터샷 시대의 관성을 따른다"는 쪽에 가까웠다.
---Cold filter, 직관과 정반대
핫샷은 Top20 거래대금 ∩ Top20 상승률 교집합으로 "그 날 가장 뜨거운 코인"을 잡는다. 콜드샷의 거울 버전은 당연히 Top20 거래대금 ∩ Top20 하락률이어야 한다. "가장 많이 떨어지고 있는 유동성 있는 코인"이다.
그런데 여기서 잠깐 멈췄다. 과거에 이미 관련 실험을 했었다. EP.13 쓰기 전날, 180일 백테스트에서 Top20 vol ∩ absChg(절대 변동률 상위) 교집합을 테스트했더니 walk-forward OS에서 수익률이 81% 감쇠했던 적이 있었다. "방향성 없이 큰 움직임만 쫓는 필터는 약하다"는 결론이었다. 그래서 콜드샷의 SHORT 필터도 비슷하게 약할 거라고 내심 짐작하고 있었다.
그런데 막상 직접 돌려보기로 했다. scripts/backtest_cold_strategy.js를 새로 짜서 1년 Bybit 데이터에 Cold filter를 적용했다. 539개 심볼, 15분봉, 매 15분 재계산. 포지션 사이즈는 자산의 5% × 2 동시.
| 필터 | 기간 | 수익률 | MDD | per-trade avgRoi |
|---|---|---|---|---|
| Hot (Top20 vol ∩ 상승률) | 1년 | +170.7% | 28.3% | +1.83% |
| Cold (Top20 vol ∩ 하락률) | 1년 | +86.8% | 11.3% | +2.00% |
핫샷이 수익률은 거의 2배지만, Cold 쪽이 per-trade 기댓값은 오히려 약간 더 높았다. 시그널 수가 적은 게 총 수익에서 차이를 만든 거였고, 질은 동등하거나 더 좋았다. 결정적으로 MDD가 11.3%였다. 핫샷 28.3%의 절반 이하.
더 흥미로운 건 LONG/SHORT 분해였다. Cold filter 안에서 1,035건 중 LONG 340건은 per-trade +0.74%였고, SHORT 695건은 per-trade +2.62%였다. "하락률 Top 20 안에서 LONG 진입은 여전히 가능하긴 한데, 가격 반등으로 찾아오는 거라 기댓값이 낮다. 진짜 알파는 SHORT 쪽이다."
---핫샷에서 양방향 모두 허용했던 건 "필터가 정배열·역배열 양쪽을 자연스럽게 판별하니 로직 대칭성을 유지하자"는 취지였다. 콜드샷에선 그 대칭성을 버렸다. Cold filter 자체가 하락 편향이고, LONG 기여분은 SHORT의 3분의 1에 불과하다. SHORT 전용으로 강제하는 편이 인지 부담도 줄고 코드도 명확해진다.
Regime 판정기를 만들려다 폐기한 이야기
콜드 백테스트 결과를 보고 아이디어가 떠올랐다. 매 15분마다 시장 regime을 LONG/NEUTRAL/SHORT로 판정하고, 해당 방향으로만 진입하면 어떨까? 하락장엔 Cold만, 상승장엔 Hot만 돌리는 스마트 라우터.
짜보기는 쉬웠다. BTC 1시간봉 EMA 리본 + 시장 breadth(거래대금 상위 50개 중 몇 %가 EMA20 위인가) + BTC 도미넌스 변화율, 세 가지를 합산해서 composite score를 만들고, -1/0/+1로 판정했다. 그 기간에만 해당 방향으로 진입.
| 전략 | 1년 수익률 | MDD | 판정 |
|---|---|---|---|
| Hot baseline (양방향) | +170.7% | 28.3% | 기준 |
| Regime-Mix (Composite) | +47.5% | 23.9% | ✗ 탈락 |
수익률이 1/3로 토막났다. 이유를 풀어보니 세 가지였다.
첫째, Hot filter 자체가 이미 암묵적 regime 필터다. "거래대금 많고 상승률 높은 20개의 교집합"은 그 자체로 "지금 시장이 LONG-friendly한 구간에서만 신호를 만든다"는 뜻이다. 그 위에 composite regime을 또 올리면 이중 필터링이 되어서 좋은 거래까지 잘라낸다.
둘째, Composite는 NEUTRAL 비율이 43.7%였다. 3개 신호 중 2개 이상 합쳐야 방향 판정이 나오도록 했는데, 실제로는 절반 가까운 시간이 모호한 구간이었다. 거기선 아무것도 안 한다. 거래 빈도가 반토막 났다.
셋째, breadth와 도미넌스는 느린 지표다. D-1 일 단위로 업데이트되는 신호를 15분봉 entry에 얹으면 timing이 완전히 어긋난다. 좋은 진입점이 오기 전에 regime이 "아직 NEUTRAL"이라고 차단하고, 뒤늦게 "SHORT OK" 신호가 나올 땐 바닥권이다.
메모리에 "regime 아이디어 재제안 금지"라고 못 박았다. Hot/Cold 두 시스템을 그냥 동시에 돌리는 게 답이다. 분산은 거래소·방향·필터 셋 다에서 자연스럽게 일어나고, 상관관계도 낮다.
---Binance는 Bybit이 아니다
Hot 시스템을 Bybit V5로 썼던 경험이 있어서 Cold는 "그냥 옮기기만 하면 되겠지" 싶었다. 천만에. 거래소가 바뀌면 거의 모든 게 바뀐다.
| 작업 | Bybit (핫샷) | Binance (콜드샷) |
|---|---|---|
| 서명 | ts+key+recv+params, 본문은 JSON | 알파벳 정렬 query + HMAC, 모든 호출 전 /fapi/v1/time 동기화 |
| 잔고 | /v5/account/wallet-balance (UNIFIED) | /fapi/v2/balance (assets 배열) |
| 포지션 조회 | /v5/position/list | /fapi/v2/positionRisk |
| 진입 주문 | MARKET + inline stopLoss 필드 하나로 끝 | MARKET 따로, SL은 별도 STOP_MARKET 주문 |
| TP1 후 SL 이동 | /v5/position/trading-stop 으로 inline amend | 기존 주문 cancel → 새 STOP_MARKET 재발행 |
| 실 순손익 조회 | /v5/position/closed-pnl (한 줄에 closedPnl·cumEntry·cumExit 전부) | /fapi/v1/income (REALIZED_PNL + COMMISSION + FUNDING_FEE 합산) |
맨 마지막이 가장 중요했다. 대시보드에 "누적 손익 $X, 수수료 -$Y" 이렇게 찍혀 있으면, 그 숫자가 거래소 원장과 정확히 일치해야 한다. 그러지 않으면 내가 얼마나 벌었는지조차 내가 확신할 수 없다.
Bybit은 쉬웠다. closed-pnl 엔드포인트가 포지션이 0이 되는 순간 한 줄의 레코드를 남긴다. cumEntryValue·cumExitValue·closedPnl이 다 들어있고, 수수료는 grossPnl - closedPnl로 역산할 수 있다. Monitor 워크플로우는 TP1_then_Cross 청산 직후 이 한 번의 호출만 하면 끝난다.
Binance는 그 통합 엔드포인트가 없었다. 대신 /fapi/v1/income이라는 재무 로그가 있다. 진입·청산 시점 이후의 모든 수익 이벤트를 타입별로 반환한다. REALIZED_PNL(실현 손익), COMMISSION(수수료), FUNDING_FEE(펀딩피). 부호 처리가 이미 되어 있어서 전부 합하면 계정 잔고 변화분 그 자체가 나온다. 수수료와 펀딩피까지 완전히 반영된 진짜 순손익.
---Binance의 userTrades 엔드포인트도 있다. 체결별 realizedPnl과 commission을 돌려주지만, realizedPnl이 gross(수수료 전)라는 걸 문서에서 건너뛰기 쉽다. 예전에 쿼터샷 모니터는 이걸 몰라서 PnL에 수수료를 이중 차감하거나 아예 안 차감한 버그가 잠깐 있었다. income 엔드포인트는 그런 함정이 없다. 타입별로 분리돼 있고, 그냥 합하면 진실이 나온다.
오늘 지운 것, 오늘 세운 것
숫자로 남기면 이렇다.
지웠다: html/trade/ 폴더 9개 PHP 파일, QuarterShot_Scanner.json, QuarterShot_Monitor.json 총 3,038줄. html/admin/api/binance.php, bybit.php 두 개(이미 경로가 깨진 상태로 있던 유물). login/index.php의 관리자 livetrade(BTC 예측) + altcoin(박스샷) 섹션 약 740줄. 메인 랜딩 index.php에서 박스샷 Feature Section 전체, "AI가 매매하는 시대" 같은 잔존 AI 문구 전부.
세웠다: sql/cold_migration.sql(trade_* DROP + cold_* CREATE). html/cold/api/의 PHP 10개(config·positions·save/close/update_sl·balance·balance_public·history·stats·scan_log). html/cold/index.php 대시보드(파란색 --vivid-blue, 달력, 카카오 광고, Binance 레퍼럴 placeholder). ColdShot_Scanner.json, ColdShot_Monitor.json, ColdShot_Balance.json — n8n 워크플로우 3개.
업그레이드했다:
Hot Monitor 워크플로우에 /v5/position/closed-pnl 연동을 추가했다. 지금까지 Hot은 PnL을 (청산가 - 진입가) × 수량 × 레버리지로 추정해서 보냈는데, 이게 gross다. 실제 체결가와 슬리피지·수수료가 반영 안 된 이론값. 이제는 청산 직후 Bybit의 원장을 직접 읽어온다. 정확도가 1원 단위로 올라간다.
관리자 페이지도 핫샷·콜드샷 두 탭으로 갈아 끼웠다. 통계 8카드, 열린 포지션 테이블, 최근 청산 30건, 수동 마감·전체 초기화 버튼. 수동 마감은 DB 레코드만 닫고, 거래소 실포지션은 따로 수동 처리해야 한다는 경고가 confirm 다이얼로그에 들어있다. 실수로 거래소와 DB가 어긋나는 상황을 완전히 막긴 어려우니, 적어도 그게 일어날 때 명시적으로 알리자는 의도다.
---"AI 자동매매"라는 거짓말
오늘 메인 랜딩을 고치다 찔렸다. 헤드라인이 "AI가 매매하는 시대 - 당신도 가질 수 있습니다"였다. Stats 섹션엔 "AI 완전 자동화", "5일 최근 히스토리 학습" 같은 문구가 박혀 있었다. 박스샷·비트인덱스 시절, 정말로 AI(GPT·Gemini)가 진입 판단에 관여했던 때의 잔재였다.
지금 시스템은 AI를 안 쓴다. 핫샷·콜드샷 둘 다 순수 규칙 기반이다. EMA·MACD·볼륨 필터로 진입하고, 고정 비율(1:2 손익비)로 청산한다. 머신러닝도, LLM도, 확률 모델도 없다. 백테스트로 검증된 조건문 몇 줄이 전부다.
"AI 자동매매"라는 마케팅 용어가 트래픽은 잘 먹는다는 걸 안다. 하지만 우리 시스템이 진짜 그렇지 않은데 그렇다고 쓰는 건 거짓말이다. 대시보드는 정직하게 손익을 공개하는데 랜딩 페이지가 거짓말이면 앞뒤가 안 맞는다. 그래서 헤드라인을 "검증된 규칙이 24시간 매매합니다"로 바꿨다. 덜 세련됐지만 정확하다.
---한 가지 아이러니는, n8n·AI 자동화 강의는 여전히 교육 섹션에 남아있다는 거다. 그건 진짜 가르칠 수 있는 기술이고, 트레이딩 시스템이 AI를 쓰지 않는 것과 별개다. 가르치는 것과 운영하는 것의 경계를 명확히 긋는 게 맞다.
앞으로의 숙제
콜드샷은 코드가 전부 올라갔지만 아직 실전은 아니다. SQL 마이그레이션, n8n 워크플로우 import, Binance 레퍼럴 코드 교체, 그리고 가장 중요한 건 핫샷이 첫 청산을 남기는 걸 확인한 뒤에 활성화하는 것. 핫샷의 closed-pnl 연동이 실전에서 제대로 붙는지 확인하지 못한 상태로 콜드샷까지 동시 가동하는 건 너무 많은 변수를 한 번에 푸는 일이다.
두 시스템이 정상적으로 굴러가기 시작하면 한 달 정도 지켜볼 생각이다. 1년 백테스트 숫자가 실전에서도 비슷하게 재현되는지. 재현된다면 Hot +170% / Cold +86% 단순 합산으로 ~257%, 두 시스템이 부분적으로 상쇄되면서 MDD는 20~25% 정도로 눌릴 것으로 추정한다. 재현되지 않는다면 어디가 어긋났는지 찾아서 다시 시작이다.
테일샷 때의 교훈은 아직 유효하다. 숫자는 거짓말을 하지 않는다. 백테스트든 실전이든, 결국 남는 건 손익 장부 한 줄이다.