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