같은 시그널, 다른 숫자
무인 시스템을 돌린 지 몇 주. 텔레그램 알림은 정확했고, 손절은 한 번도 밀리지 않았다. 그런데 n8n이 한 줄씩 남긴 기록을 트레이딩뷰 전략 테스터의 거래 목록 옆에 놓고 보니, 같은 시그널인데 숫자가 조금씩 어긋나 있었다.
백테스트는 84,120.5에 샀다고 했는데 실제 체결은 84,163이었다. 청산도 마찬가지로 몇십 달러씩 불리한 쪽으로 밀려 있었다. 한 번은 아무것도 아니다. 그런데 이게 44번 쌓이면, 그리고 앞으로 수백 번 쌓이면 어떻게 되는가.
문제는 내 실전 로그가 아직 44번을 채우려면 한참 멀었다는 것이다. 몇 주치 기록으로 “괴리가 얼마다”라고 말하는 건 표본 셋으로 승률을 논하는 것과 같다. 그래서 순서를 바꿨다. 실전 로그가 쌓이기를 기다리는 대신, 백테스트의 44타점에 비용을 가정치로 주입해 본다. 실전에서 확인된 어긋남의 크기를 가정으로 넣고, 수식이 약속한 수익이 얼마나 깎이는지를 먼저 계산해 두는 것이다.
백테스트가 모르는 세 가지 비용
전략 테스터는 봉 마감 가격에 정확히 체결됐다고 가정한다. 현실에는 그 사이에 세 가지가 끼어든다.
수수료. 진입과 청산, 두 번 낸다. 시장가(테이커) 기준 편도 0.05% 안팎이 흔하다. 왕복 0.1%.
슬리피지. 시장가 주문은 호가창을 먹으면서 체결된다. 조용한 시간대에는 0.02~0.05%, 봉 마감 직후 시그널이 몰리는 순간이나 변동성이 터진 새벽에는 0.1% 이상이 나온다. 이것도 왕복이다.
집행 지연. 봉이 마감되고, 트레이딩뷰가 알림을 쏘고, n8n이 잔고를 조회하고, 주문이 거래소에 닿기까지 몇 초. 그 몇 초 동안 가격은 이미 움직이고 있다. 추세 추종 진입은 정의상 가격이 달리기 시작할 때 들어가므로, 지연은 거의 항상 불리한 방향으로 작용한다.
셋 다 각각은 소수점 아래 숫자다. 그래서 백테스트를 볼 때는 무시하고 싶어진다. 나도 그랬다.
시나리오 여섯 개, 같은 44타점
같은 44번의 거래에 비용만 다르게 주입했다. 가정은 이렇다. 승률 59%, 손실은 항상 시드의 1%, 손절 거리는 ATR 기준 평균 3.5%, 2022년 빙하기의 8연패 구간 포함. 지난 기록들의 조건 그대로다.

비용이 0인 이상적 세계에서 이 44타점은 시드를 약 64% 불린다. 수수료만 넣으면 62%. 슬리피지를 편도 0.05% 얹으면 59%. 슬리피지가 0.10%로 커지면 57%. 여기에 집행 지연 10bp를 더하면 55%. 수수료·슬리피지·지연이 전부 나쁜 쪽으로 겹치는 최악의 경우에는 46%다.
이상적 세계와 현실적 세계(수수료 0.05 + 슬리피지 0.10 + 지연) 사이의 간격은 약 9%포인트. 7년 치 수익의 14%가 백테스트에 없던 비용으로 증발한다. 최악의 경우는 28%다.
MDD도 움직인다. 지난 기록에서 자랑했던 8.08%는 수수료만 반영한 숫자였다. 슬리피지와 지연까지 넣으면 8.8% 근처로 올라간다. 파산하지 않는 몸은 여전히 파산하지 않지만, 보험증서에 적힌 숫자보다는 조금 더 아프다.

진짜 문제는 손절 거리에 있었다
여기까지는 예상 범위였다. 수익이 좀 깎이는구나, 정도. 그런데 비용을 거래별로 뜯어보다가 이상한 패턴을 발견했다.
비용이 큰 거래는 큰 거래가 아니었다. 손절이 타이트한 거래였다.
이유는 1% 룰 자체에 있다. 투입 금액 = (시드 × 1%) ÷ 손절 퍼센트. 손절이 -1%로 가까우면 시드의 100%를 투입하고, -5%로 멀면 20%만 투입한다. 그런데 수수료와 슬리피지는 투입 금액에 비례한다. 손절이 가까울수록 포지션이 커지고, 포지션이 클수록 비용이 커진다. 리스크는 언제나 1%로 고정인데, 비용은 손절 거리에 반비례해서 불어난다.

왕복 비용을 0.3%(수수료 0.1 + 슬리피지 0.2)로 잡으면, 손절 -5%인 거래에서 비용은 리스크 예산 1%의 6%다. 손절 -3%면 10%. 손절 -2%면 15%. 손절 -1.5%면 20%. 그리고 손절 -1%인 거래에서는 비용이 리스크의 30%다.
다시 말해, 손절을 -1%에 걸고 시드 전부를 넣는 순간 나는 “틀리면 1%를 잃는” 게임이 아니라 “틀리면 1.3%를 잃고, 맞아도 0.3%를 먼저 떼이는” 게임을 하고 있었다. 1% 룰의 수학이 비용 때문에 뒤에서 조용히 무너지는 지점이다.
그래서 추가한 룰: 손절 거리 하한
로직을 다시 최적화하지는 않았다. 과최적화의 독은 지난 기록에서 충분히 이야기했다. 대신 포지션 사이징 노드에 조건 하나를 추가했다.
손절 거리가 진입가 대비 2% 미만이면 진입하지 않는다.
이유는 위 표 그대로다. 2% 하한을 두면 비용이 리스크 예산의 15%를 넘지 않는다. 그 이하로 타이트한 손절은 백테스트에서는 훌륭해 보이지만(투입이 커서 이겼을 때 크게 먹는다), 실전에서는 비용이 그 이점을 거의 다 가져간다. ATR이 극단적으로 쪼그라든 횡보 구간에서 주로 나오는 시그널이라 어차피 승률도 낮은 편이었다.
n8n 코드 노드는 이제 이렇게 생겼다.
const stopPct = Math.abs($json.price - $json.stop) / $json.price;
if (stopPct < 0.02) {
return [{ json: { ...$json, skip: true, reason: 'stop_too_tight' } }];
}
const seed = $json.balance;
const size = Math.min((seed * 0.01) / stopPct, seed);
return [{ json: { ...$json, size: Math.floor(size) } }];
건너뛴 시그널도 기록은 남긴다. 나중에 “하한 때문에 놓친 거래”가 실제로 비용을 빼고도 이익이었는지 검증하려면, 안 들어간 거래의 기록이 들어간 거래의 기록만큼 중요하다.
백테스트에 비용을 넣는 법
전략 테스터 설정에서 수수료를 0.05%, 슬리피지를 몇 틱으로 넣는 것은 누구나 한다. 내가 이번에 바꾼 건 그 숫자를 “낙관 / 현실 / 최악” 세 벌로 돌려서 세 개의 자본 곡선을 나란히 보는 습관이다. 하나의 곡선은 약속이고, 세 개의 곡선은 범위다. 앞으로 이 시리즈에서 백테스트 수치를 말할 때는 현실 시나리오(수수료 0.05 + 슬리피지 0.10 + 지연 10bp)를 기본값으로 쓴다.
그리고 실전 로그는 계속 쌓는다. n8n 기록 노드에 컬럼 두 개를 추가했다. 시그널 가격과 실제 체결 가격, 그리고 그 차이를 bp로. 44번이 채워지면 이 글의 가정치를 실측치로 갈아 끼우고, 어느 시간대에 슬리피지가 터지는지까지 뜯어볼 것이다.

다음 기록을 위한 메모: 한 종목에서 여러 종목으로
여기까지가 한 종목, 한 시스템의 이야기다. 시그널 하나, 리스크 1%, 손절 하나. 그런데 시스템이 안정되자 당연한 욕심이 생겼다. 같은 로직을 두세 종목에 동시에 돌리면?
문제는 리스크 1%가 종목별로 각각 1%가 되는 순간, 셋이 같은 날 같은 방향으로 손절을 맞으면 하루에 3%가 빠진다는 것이다. 그리고 암호화폐는 폭락장에서 종목 구분 없이 같이 떨어진다. 다음 기록에서는 포트폴리오 전체의 리스크 상한을 어떻게 걸었는지, “상관관계가 1로 수렴하는 날”을 어떻게 다뤘는지 정리해 두어야겠다.
[법적 고지 및 면책 조항]
이 블로그의 모든 글은 개인적인 퀀트 트레이딩 알고리즘 백테스트 및 매매 일지 기록용이다.
특정 종목의 매수/매도를 추천하거나 투자 타이밍을 알려주는 리딩(Reading) 목적이 절대 아님을 명시한다.
블러드하운드 퀀트 랩은 타인의 투자금을 대리 운용하거나 금전을 요구하는 유사수신행위, 1:1 투자 자문 및 유료 리딩방 운영을 일절 하지 않는다.
이곳의 개인적인 데이터를 참고하여 발생한 모든 투자의 최종 판단과 손실에 대한 법적 책임은 버튼을 누른 본인에게 있다.
- 이 블로그의 모든 글은 개인적인 퀀트 트레이딩 알고리즘 백테스트 및 매매 일지 기록용이다.
- 특정 종목의 매수/매도를 추천하거나 투자 타이밍을 알려주는 리딩(Reading) 목적이 절대 아님을 명시한다.
- 블러드하운드 퀀트 랩은 타인의 투자금을 대리 운용하거나 금전을 요구하는 유사수신행위, 1:1 투자 자문 및 유료 리딩방 운영을 일절 하지 않는다.
- 이곳의 개인적인 데이터를 참고하여 발생한 모든 투자의 최종 판단과 손실에 대한 법적 책임은 버튼을 누른 본인에게 있다.


