볼린저 밴드 압축은 정말 신호일까 — 가설을 미리 적어두고 4주 뒤 기각한 기록
주가 차트에 볼린저 밴드를 띄워 본 적이 있다면 “밴드가 좁아지면 곧 큰 움직임이 나온다”는 설명을 들어봤을 겁니다. 밴드폭 압축(squeeze)이라고 부르는 현상입니다.
저는 이 아이디어를 조금 다르게 물었습니다. “공시가 나온 시점에 그 종목의 밴드폭이 좁아져 있었다면, 이후 며칠간의 초과수익이 더 클까?”
결론부터 쓰면 기각했습니다. 그것도 예상과 반대 방향으로요.
이 글은 그 결론 자체보다 결론에 도달한 절차에 관한 것입니다. 데이터를 보기 4주 전에 판정 기준을 문서로 고정해두지 않았다면, 저는 같은 데이터에서 정반대의 글을 쓸 수 있었습니다. 그 이야기를 하려고 합니다.
밴드폭이란 무엇인가
볼린저 밴드는 이동평균선 위아래로 표준편차의 2배만큼 띠를 그린 것입니다. 밴드폭(bandwidth)은 그 띠가 얼마나 벌어져 있는지를 중심선 대비 비율로 나타냅니다.
밴드폭 = (상단 − 하단) / 중심선 = 4 × σ20 / SMA20
σ20은 최근 20거래일 종가의 표준편차, SMA20은 같은 기간 단순이동평균입니다. 값이 작을수록 최근 가격이 평균 근처에 몰려 있다는 뜻입니다.
여기서 문제가 하나 있습니다. 밴드폭의 절대값은 종목 간 비교가 되지 않습니다. 원래 변동성이 큰 종목은 조용할 때조차 밴드폭이 넓습니다. 그래서 저는 절대값 대신 백분위를 썼습니다.
BW_pct = 직전 120거래일 밴드폭 분포에서 오늘 밴드폭의 백분위
BW_pct가 20이라면 “이 종목 기준으로 최근 반년 중 하위 20% 수준으로 조용하다”는 뜻입니다. 종목 자기 자신과 비교하므로 대형주와 소형주를 같은 자로 잴 수 있습니다.
가설과 그 근거
가설: 수주·계약 공시 이벤트의 시장 대비 5일 초과수익은, 밴드폭 압축 국면에서 확장 국면보다 크다.
방향까지 미리 못박았다는 점이 중요합니다. “차이가 있다”가 아니라 “압축 쪽이 크다”입니다. 이유는 두 가지였습니다.
- 압축 국면은 아직 정보가 반영되지 않은 상태일 가능성이 있습니다. 조용하던 종목에 새 정보가 들어오면 반영에 시간이 걸리고, 그 지연이 며칠에 걸친 드리프트로 나타납니다.
- 반대로 이미 변동성이 큰 국면이라면 이미 여러 참여자가 붙어 있다는 뜻이므로, 새 정보는 더 빨리 소화될 겁니다.
그럴듯하게 들리시나요? 저도 그랬습니다. 그럴듯하다는 감각이 바로 이 절차가 필요한 이유입니다.
데이터를 보기 전에 무엇을 고정했는가
검증에 쓸 데이터는 이 시점에 이미 존재했습니다. 마음만 먹으면 바로 조회할 수 있었고, 그게 정확히 위험한 지점입니다. 그래서 조회하기 전에 다음을 문서에 적고 커밋했습니다.
① 변수 정의를 숫자 단위까지 동결
- 창 20거래일, 표준편차 배수 2, 백분위 창 120거래일, 압축/확장 컷 50
- 표본표준편차(ddof=1) 사용
- 신호일 전일까지의 종가만 사용 — 이벤트 당일 종가가 들어가면 결과를 알고 지표를 만든 셈이 됩니다
- 이력 요건: 신호 전 종가 140거래일 이상. 미달 표본은 제외하고 제외 건수를 보고
여기서 파라미터를 하나라도 바꾸면 재등록 대상입니다. 즉 “20일 말고 25일로 해보니 되네”는 금지입니다.
② 판정 기준 3개를 미리 정의 — 셋 다 충족해야 통과
- 압축군과 확장군의 Welch t가 1.96 이상(양측)이고 방향이 사전 지정과 일치
- 3분위(하위/중간/상위)로 나눴을 때 방향이 단조
- 등록 이후에 새로 쌓인 표본만으로도 같은 방향
세 번째 조건이 핵심입니다. 등록 시점에 이미 존재하던 데이터는 제가 그동안 이런저런 집계를 보며 간접적으로 노출됐을 수 있습니다. 등록 이후 발생한 이벤트는 그럴 수 없습니다. 표본이 작아 검정력이 부족하다는 것도 미리 적었고, 그래서 유의성이 아니라 부호만 요구했습니다.
③ 조회 금지
판정 시점까지 밴드폭과 수익률의 관계를 어떤 형태로도 조회하지 않기로 했습니다. 등록 당시 확인한 건 “이력이 충분한 종목이 몇 개인가” 뿐입니다.
④ 판정 시점을 사건이 아니라 표본 수로 정의
“등록 후 신규 이벤트 50건이 쌓이면 판정한다”로 적었습니다. 날짜로 정하면 결과가 마음에 안 들 때 “조금 더 모아보자”가 가능해집니다.
4주 뒤 — 결과
신규 이벤트가 54건이 되어 트리거가 충족됐고, 등록 문서에 적힌 그대로 실행했습니다.
전체 모집단은 2026년 3월 25일부터 8월 16일까지 589개 종목에서 발생한 9,401건의 이벤트입니다. 이 중 규약에 맞는 수주·계약 이벤트는 299건이었고, 이력 요건 미달 2건을 제외해 297건이 판정 표본이 됐습니다.
요건 ① — 미통과, 그리고 방향이 반대
| 구분 | 표본 수 |
|---|---|
| 압축군 (BW_pct ≤ 50) | 69건 |
| 확장군 (BW_pct > 50) | 228건 |
Welch t = −1.15 (df 111). 유의수준에 한참 못 미칩니다. 그리고 부호가 음수라는 건 확장군 쪽이 높았다는 뜻입니다. 제가 미리 적어둔 방향의 정반대입니다.
요건 ② — 미통과
3분위로 나눈 결과는 단조가 아니라 역U자였습니다. 중간 구간이 가장 높고 양 끝이 낮았습니다.
| 구간 | 표본 수 | 순위 |
|---|---|---|
| 하위 (≤33) | 38건 | 가장 낮음 |
| 중간 (33~67) | 99건 | 가장 높음 |
| 상위 (>67) | 160건 | 중간 |
요건 ③ — 미통과
등록 이후 신규 54건만으로도 t = −1.06, 전체와 같은 방향이었습니다. 즉 전체와 신규가 일관되게 가설의 반대를 가리켰습니다.
보조 지표로 함께 본 20일 실현변동성 백분위도 t = −1.57로 같은 방향이었습니다. 서로 다른 두 변동성 척도가 같은 결론을 냈습니다.
기각이 컷 선택 탓은 아닌가
여기서 멈추면 “50이라는 컷이 나빴던 것 아니냐”는 반론이 남습니다. 등록 문서에 강건성 검사를 적어뒀기 때문에 그것도 실행했습니다.
- 이상치 제외 — 창 안에 하루 만에 50% 넘게 움직인 종목(액면분할 등이 의심되는 경우) 8건을 빼도 t는 −1.15에서 −1.17로 사실상 불변
- 컷 민감도 — 압축/확장 경계를 25, 33, 40, 50, 60, 67, 75로 바꿔가며 전부 계산한 결과 모든 구간에서 |t| < 1.2. 사전 지정 방향으로 유의한 지점은 어디에도 없었습니다
- 수동 재계산 — 표본 한 건의 밴드폭을 손으로 다시 계산해 스크립트 값과 대조(오차 1e-12 이내)
즉 기각은 컷 선택에도, 이상치에도, 변동성 척도 선택에도 의존하지 않습니다.
왜 틀렸는가 — 표본 자체가 프라이어를 부정했다
가장 흥미로운 건 결과 자체가 아니라 왜 틀렸는지였습니다.
BW_pct는 종목 자기 자신의 백분위입니다. 그러면 이벤트가 변동성과 무관하게 발생한다면, 이벤트들의 BW_pct는 0~100에 고르게 퍼져야 합니다. 압축군과 확장군이 대략 반반이어야 한다는 뜻입니다.
실제로는 이랬습니다.
| 구간 | 비율 | 기대치(무관할 경우) |
|---|---|---|
| 압축 (≤50) | 23.2% (69/297) | 50% |
| 상위 (>67) | 53.9% (160/297) | 33% |
수주·계약 공시는 해당 종목이 이미 변동성 확장 국면에 있을 때 몰려서 발생합니다.
돌아보면 당연합니다. 큰 계약이 임박한 회사는 이미 관련 뉴스, 업황 변화, 거래량 증가를 겪고 있을 가능성이 높습니다. 공시는 그 국면의 결과이지 조용한 하늘에서 떨어지는 사건이 아닙니다.
제 가설에는 **“이벤트 발생과 변동성 국면은 서로 독립”**이라는 가정이 명시되지 않은 채 깔려 있었습니다. 그 가정이 깨졌기 때문에 “압축 국면의 수주 공시”는 중립적인 절반이 아니라 표본의 4분의 1짜리 특수 상태였습니다.
이건 검정된 명제가 아니라 관측입니다. 단독 가설로 쓰려면 처음부터 다시 등록해야 합니다. 기각된 표본을 다시 뒤져서 나온 이야기를 새로운 발견인 것처럼 쓰는 게 정확히 이 절차가 막으려는 행동이니까요.
사전 등록이 없었다면 무슨 일이 벌어졌을까
이 부분이 이 글의 핵심입니다. 같은 데이터를 손에 쥐고 판정 기준이 없었다면 저에게는 이런 선택지들이 있었습니다.
- 컷을 바꾼다. 50이 아니라 30이나 70에서 다시 본다. 7개 컷 중 하나쯤은 더 예쁜 숫자가 나옵니다.
- 부분집합을 본다. 다른 조건을 통과한 표본만 따로 보면 표본이 줄어 우연히 유의해질 수 있습니다.
- 방향을 갈아끼운다. 결과가 반대로 나왔으니 “확장 국면이 유리하다”로 가설을 바꿔 쓰면 됩니다. 원래 그렇게 생각했던 것처럼 서술할 수 있습니다.
- 더 기다린다. 표본이 더 쌓이면 달라질지 모른다며 판정을 미룹니다.
- 보조 지표로 갈아탄다. 실현변동성 쪽이 t = −1.57로 조금 더 컸으니 그쪽을 주 변수로 승격합니다.
이 다섯 가지 전부, 사후에는 합리적인 판단처럼 보입니다. 각각에 붙일 그럴듯한 설명이 있고, 실제로 그렇게 쓰인 글을 인터넷에서 흔히 볼 수 있습니다.
미리 적어둔 문서가 하는 일은 딱 하나입니다. 이 선택지들을 결과를 보기 전에 닫아버리는 것. 판정 기준 3개, 파라미터 동결, 표본 수 기반 트리거, 조회 금지는 전부 같은 목적을 위한 장치입니다.
특히 세 번째 요건 — 등록 이후 신규 표본 — 이 없었다면 저는 제가 얼마나 이 데이터에 노출됐는지 스스로 증명할 방법이 없었습니다.
부수적으로 배운 것
판정을 실행하면서 등록 문서 자체의 결함도 하나 발견했습니다.
등록 당시 각주에 “전체 이벤트 289건 중 287건이 이력 요건을 충족한다”고 적어뒀는데, 판정 시점에 289라는 분모가 재현되지 않았습니다. 동결된 규약으로 다시 세면 246건이었습니다. 정의를 12가지로 바꿔가며 시험해도 289는 나오지 않았습니다.
데이터가 사라진 건 아니었습니다(레코드 번호가 연속이라는 것까지 확인했습니다). 원인은 각주를 쓸 때 본문의 규약과 다른 쿼리를 썼다는 것이었습니다.
판정 결과에는 영향이 없었습니다. 충족률 자체는 원 단위까지 재현됐고(243/245), 세 번째 요건은 등록일 이후 데이터만 쓰기 때문입니다. 그래도 남겨둘 가치가 있는 교훈입니다.
등록 문서에 적는 숫자는, 그게 각주라도 판정에 쓸 쿼리와 같은 쿼리로 산출해야 한다.
수치를 내기 전에 재현부터 검증했기 때문에 걸렀습니다. 순서가 반대였다면 판정 결과에 이 불일치가 섞여 들어갔을 겁니다.
정리
- 밴드폭 압축 국면에서 이벤트 초과수익이 더 크다는 가설은 기각됐습니다. 3개 요건 전부 미통과이고, 방향은 사전 지정과 반대였습니다.
- 기각은 컷 선택·이상치 처리·변동성 척도 어느 것에도 의존하지 않았습니다.
- 실패의 원인은 계산이 아니라 명시되지 않은 가정이었습니다. “이벤트와 변동성 국면은 독립”이라는 가정이 데이터에서 깨졌습니다.
- 그 가정을 확인하지 않은 채 결과만 봤다면, 저는 다섯 가지 방법 중 아무거나 골라 “발견”을 쓸 수 있었습니다.
가설이 틀렸다는 걸 4주 만에 알아낸 게 이번 작업의 성과입니다. 틀린 걸 틀렸다고 판정할 수 있는 상태가, 맞는 걸 찾는 것보다 먼저입니다.
수치 공개 범위에 대하여
이 글에는 t값, 표본 수, 분포 비율, 파라미터는 그대로 적었지만 초과수익의 절대 크기는 적지 않았습니다. 실제로 운용 중인 전략의 성과 크기라서 공개 대상이 아닙니다.
다만 이 글의 논증은 그 값에 의존하지 않습니다. 기각의 근거는 t값이 임계에 못 미쳤다는 것과 부호가 사전 지정과 반대였다는 것 두 가지이고, 둘 다 위에 그대로 적혀 있습니다. 3분위 결과도 순위만으로 역U자가 확인됩니다.
무엇을 공개하고 무엇을 감출지는 이 블로그를 시작하며 미리 정해뒀습니다. 방법론·기각된 가설·검증 절차는 공개하고, 현행 진입 조건과 실시간 보유 종목, 성과 크기는 공개하지 않습니다. 판단이 개입할 여지를 줄이려고 자동 검사 스크립트로 강제하고 있습니다.
본문 수치는 발행 전 판정 스크립트를 재실행해 재현했고, 모집단 수치는 원본 데이터베이스와 자동 대조했습니다.