백테스트가 아니라 장부가 어긋난다 — 자동매매를 실제로 돌리며 배운 것
백테스트를 통과한 전략을 실제 돈으로 돌리기 시작하면, 문제는 예상한 곳(신호가 맞는가)이 아니라 예상하지 못한 곳에서 터진다. 장부가 어긋난다. 주문이 실패했다고 읽었는데 체결돼 있고, 엔진이 산 종목이 남의 것으로 분류되고, 청산 시각이 거래가 안 되는 구간에 걸려 있다.
소액 자동매매를 실제로 돌린 사흘 동안 그런 어긋남을 다섯 번 만났다. 전부 백테스트에는 존재하지 않는 종류였다. 이 글은 그 기록이고, 뒤에는 파라미터를 다시 재면서 발견한 것과 새로 검정해 기각한 축 둘을 붙였다.
첫째, 장부는 생각보다 쉽게 어긋난다
사고 1 — 실패했다고 읽은 주문이 체결돼 있었다
청산 주문을 냈는데 API 가 오류를 돌려줬다. 엔진은 "실패 — 포지션을 남겨 둔다"고 기록했다. 그런데 실제로는 팔려 있었다. 엔진은 자기 장부만 믿고 이틀간 하루 세 번씩 없는 주식을 팔려고 했다. 매번 "매도가능수량 0"으로 실패하면서.
교훈은 단순하다. 장부는 정본이 아니다. 계좌가 정본이다. 지금 엔진은 청산 전에 실계좌부터 확인하고, 장부에 있는 것이 계좌에 없으면 주문 내역에서 체결가를 찾아 스스로 기록을 맞춘다. 사람이 손으로 판 경우까지 같은 경로로 아문다 — 실제로 그 다음 날, 사람이 직접 판 것을 엔진이 알아채고 기록을 맞췄다.
사고 2 — 엔진이 자기 돈을 남의 돈으로 분류했다
이 시스템은 위임 한도가 있다. 엔진이 굴릴 수 있는 돈의 상한이다. 계좌에는 엔진이 산 것과 사람이 산 것이 섞여 있으므로, 엔진은 기동할 때 계좌를 보고 "내 것"과 "남의 것"을 가른다. 남의 것은 한도에서 빼 준다.
그런데 설정을 바꾸면서 장부의 식별 키가 갈렸고, 하필 그 사이에 스케줄러가 엔진을 잠깐 띄웠다. 새 키의 장부는 비어 있었다. 엔진은 계좌의 보유 전부를 — 자기가 산 것까지 — "남의 것"으로 분류했다. 한도가 통째로 되살아났고, 다음날 그 위에 또 샀다. 위임 한도를 24% 넘겨 투입된 것이다.
사고 3 — 청산 시각이 거래 불가 구간에 걸려 있었다
한국 주식시장에는 거래가 안 되는 틈이 있다. 정규장은 15:30에 끝나지만 15:20부터는 마감 동시호가라 일반 지정가가 그대로 체결되지 않고, 15:30~15:40 은 아예 공백이며, 시간외 단일가는 18:00에 끝난다.
종가 청산을 15:35 로 잡았다가 거절당했다. "고쳤다"며 15:25 로 옮겼는데 그것도 동시호가 구간이라 틀렸고, 재시도로 넣은 19:30 도 단일가가 끝난 뒤라 틀렸다. 추측으로 두 번 고쳤고 두 번 다 틀렸다. 거래소 시간표를 API 로 직접 받아 보고 나서야 맞는 시각을 골랐다.
덧붙여, 실패 로그에 "HTTP 422"만 남고 서버가 알려준 이유가 버려지고 있었다. 오류의 본문을 로그에 남기게 고치자 다음 실패부터는 추측이 필요 없어졌다. 서버가 이유를 말해주는데 안 듣고 있었던 것이다.
둘째, 파라미터를 정직한 잣대로 다시 재니 순위가 뒤집혔다
돌리고 있던 파라미터는 백테스트 격자에서 통계량이 가장 좋았던 조합이었다. 그런데 그 통계량(t 9.91)은 표본 겹침으로 4배 부풀어 있었고 — 하루 50종목을 20일씩 들면 같은 시장 국면을 수십 번 겹쳐 센다 — 절대 수익으로 순위를 매겨 시장이 준 몫과 신호의 몫이 섞여 있었다.
잣대를 바꿔 54개 조합을 다시 쟀다. 무작위로 고른 종목에 같은 규칙을 돌린 것을 같은 날짜끼리 짝지어 빼고(시장의 몫 소거), 통계량은 일자 묶음으로 냈다(겹침 소거).
| 쓰던 조합 | 다시 잰 뒤 고른 조합 | |
|---|---|---|
| 유니버스 폭 | 상위 50 | 상위 20 |
| 손절 | 2.0σ | 3.0σ |
| 무작위 대비 초과분 | +0.109%p (t 1.68) | +0.403%p (t 3.61) |
| 하락장 해(2022) | 음수 | 양수 |
| 호가 1칸 슬리피지 | 음수로 전락 | 생존 |
새 조합의 방향은 일관됐다 — 좁은 유니버스, 넓은 손절. 신호는 상위 20에서 진하고 50까지 가면 묽어지며, 거친 종목에 좁은 손절은 잡음에 털린다. 상위 5개 조합이 전부 이 방향이었고 여섯 해 전부 양수였다.
셋째, 새로 검정한 두 축도 기각됐다
수급 주체 — 기관을 따라 사면 유의하게 잃는다
"외국인·기관의 순매수는 정보를 가진 쪽의 행동이고, 큰 손은 나눠 사니까 따라 사도 남는 게 있다"는 가설을 쟀다. 251종목 5년 반의 일별 수급 32만 행이다.
| 관측 | 결과 | |
|---|---|---|
| 외국인 강매수 상위 십분위 이후 20일 | 29만 건 | 강매도 십분위와 차이 0.2%p 미만 |
| 외국인 추종 (최선 조합) | 1,299일 | +0.096%p (t 0.68) — 미달 |
| 기관 추종 (10일 강도) | 1,295일 | −0.368%p (t −2.51) — 유의하게 손해 |
일별 수급 확정치는 그날 저녁 공표되는 보이는 정보다. 공표될 때쯤이면 매집이 만든 가격 이동은 끝나 있다. 기관 추종이 유의한 마이너스인 것은 그 되돌림 구간을 사기 때문으로 보인다.
국면 판정 — 25년 동안 아무것도 말하지 않았다
"지금 시장이 어떤 유형인지 파악하고 그에 맞는 전략을 쓴다"는 접근이 성립하려면 판정기가 두 가지를 갖춰야 한다. 국면이 행동할 만큼 이어질 것, 그리고 감지된 뒤의 수익이 국면답게 갈릴 것.
가장 널리 쓰이는 판정기(지수의 200일 이동평균 위/아래)를 25년치 지수로 쟀다. 하락장 여섯 번이 들어 있는 표본이다.
| 그대로 쓰면 | 확인 20일을 붙이면 | |
|---|---|---|
| 하락 구간 중앙 길이 | 2~3일 (행동 불가) | 115일 (해결) |
| 감지 후 20일 수익 — 상승장 | +1.02% | +0.99% |
| 감지 후 20일 수익 — 하락장 | +0.94% | +0.99% |
넷째, 그래서 루틴은 이렇게 짰다
매주 백테스트를 다시 돌려 "최적" 파라미터로 자동 갱신하는 구조를 떠올리기 쉽다. 그건 과거에 맞추는 일을 매주 반복하는 것이다. 대신 주말 루틴은 한 가지만 묻는다 — 살아 있는 돈이 백테스트가 말한 대로 움직이고 있는가.
점검은 자동이지만 변경은 자동이 아니다. 바꿀 근거가 나오면 보고에 떠 있고, 사람이 본다. 이 경계가 무너지면 시스템이 매주 조금씩 과거에 최적화된다.
남는 것
사흘의 목록을 보면 신호 연구보다 운영 정비가 많다. 그게 정직한 비율이라고 생각한다. 백테스트의 세계에는 체결 실패도, 장부 어긋남도, 동시호가도, 사람이 손으로 판 주식도 없다. 실전의 세계에는 전부 있고, 하나하나가 백테스트 우위보다 큰 돈을 움직인다.
1. 장부는 정본이 아니다. 계좌가 정본이다 — 매 주기 대조하고, 어긋나면 스스로 아물게
2. 조용한 오분류가 크래시보다 위험하다 — 돈의 소속은 여러 겹으로 지켜라
3. 거래소 시간표를 추측하지 마라 — API 로 받아라. 오류 본문을 버리지 마라
4. 부풀려진 통계량은 파라미터 선택까지 오염시킨다 — 잣대부터 정직하게
5. 점검은 자동으로, 변경은 사람이 — 이 경계가 과최적화의 마지막 방어선
국내 상장 종목의 2021년 이후 일별 자료(약 132만 행), 1분 단위 자료(약 50만 행), 일별 수급 자료(약 32만 행), 지수 25년치로 검정했습니다. 과거 데이터에 대한 통계 분석이며 특정 종목이나 매매 시점에 대한 권유가 아닙니다. 투자 판단과 그 결과는 투자자 본인에게 귀속됩니다.
작성: 2026. 8. 이알렉산드로
댓글
댓글 쓰기