파이썬으로 나스닥100 분봉 거래량 불균형 지표 만드는 방법

파이썬을 활용하면 QQQ 같은 나스닥100 추종 상품의 분봉 OHLCV 데이터로 거래량 불균형 프록시를 만들 수 있습니다. 종가가 해당 분봉의 고가와 저가 사이 어디에 위치하는지를 이용하면 거래량에 방향성을 부여할 수 있습니다. 이 값을 일정 구간으로 누적하고 Z-Score로 표준화하면 평소보다 강한 매수·매도 압력 구간을 찾는 데 도움이 됩니다. 다만 OHLCV 기반 거래량 불균형은 실제 호가창의 Order Flow Imbalance와는 다른 추정 지표라는 점을 구분해야 합니다. 최종적으로는 반전 후보 신호를 만든 뒤 미래 수익률, 거래비용, 슬리피지 등을 포함한 백테스트로 검증하는 과정이 필요합니다.
앞선 글에서는 나스닥100 분봉에서 거래량 불균형이 단기 가격 반전을 찾는 하나의 조건이 될 수 있는지 살펴봤습니다.
그렇다면 다음 질문은 자연스럽습니다.
“거래량 불균형이라는 것을 실제 데이터에서는 어떻게 계산할까?”
차트 프로그램에서 누군가 만들어놓은 지표를 보는 것은 어렵지 않습니다. 하지만 직접 데이터를 내려받고 계산식을 만들어보면 해당 지표가 무엇을 측정하는지, 어떤 부분을 놓치고 있는지가 훨씬 명확하게 보입니다.
이번 글에서는 파이썬을 이용해 분봉 데이터를 불러온 뒤 OHLCV 데이터만으로 계산할 수 있는 거래량 불균형 프록시를 만들어보겠습니다.
중요한 점은 이 글에서 만드는 값이 실제 호가창의 Order Flow Imbalance와 동일한 것은 아니라는 것입니다. 체결 방향과 Bid·Ask 잔량까지 확인하려면 보다 세밀한 시장 미시구조 데이터가 필요합니다.
이번 목표는 일반 투자자가 비교적 쉽게 구할 수 있는 분봉 OHLCV 데이터에서 분석 가능한 지표를 만드는 것입니다.
나스닥100 거래량을 분석할 때 데이터부터 구분해야 한다
우선 나스닥100은 개별 주식이 아니라 지수입니다.
Nasdaq은 Nasdaq-100 Index, 즉 NDX를 Nasdaq에 상장된 대형 비금융 기업 100개를 기반으로 구성되는 지수로 설명하고 있습니다.
따라서 NDX 가격을 내려받았다고 해서 일반 주식처럼 해당 지수 자체가 사고팔리면서 발생한 거래량이 존재한다고 생각하면 곤란합니다.
거래량을 연구하려면 실제로 거래되는 상품의 데이터를 이용하는 방법이 현실적입니다.
이번 예제에서는 이해하기 쉽도록 QQQ를 사용하겠습니다. Invesco에 따르면 QQQ는 Nasdaq-100 Index를 추종하도록 설계된 ETF입니다. 따라서 나스닥100 관련 단기 움직임과 실제 거래량을 함께 연구하려는 경우 하나의 분석 대상으로 활용할 수 있습니다. 다만 QQQ와 NDX의 움직임이 모든 순간 완전히 동일하다는 뜻은 아닙니다.
선물 데이터를 이용할 수 있다면 Nasdaq-100 선물 데이터를 연구하는 방법도 있습니다.
어떤 데이터를 선택하든 중요한 것은 분석 결과에 “나스닥100 거래량”이라고 뭉뚱그려 표현하지 않고 실제 사용한 데이터가 QQQ인지, 선물인지, 구성 종목 집계 데이터인지 명시하는 것입니다.
이것만 지켜도 데이터 분석 글의 신뢰도가 상당히 높아집니다.
파이썬으로 QQQ 분봉 데이터 가져오기
간단한 연구에서는 Python의 yfinance 라이브러리를 사용할 수 있습니다.
현재 yfinance 문서에서는 1분, 2분, 5분, 15분, 30분, 60분 등의 분봉 interval을 지원하고 있으며, intraday 데이터에는 조회 기간 제한이 있다고 안내하고 있습니다. 현재 문서 기준으로 intraday 데이터는 최근 60일 범위 내에서 제공됩니다. 따라서 장기간 분봉 백테스트를 하려면 별도의 데이터 공급원을 고려할 필요가 있습니다.
먼저 필요한 패키지를 설치합니다.
pip install yfinance pandas numpy matplotlib
그다음 QQQ의 5분봉 데이터를 가져옵니다.
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
df = yf.download(
"QQQ",
period="5d",
interval="5m",
auto_adjust=False,
progress=False
)
# yfinance 버전에 따라 MultiIndex가 반환되는 경우 처리
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
df.columns = [col.lower() for col in df.columns]
print(df.head())
데이터에는 일반적으로 다음과 같은 값이 들어옵니다.
Open은 해당 5분봉의 시가, High는 고가, Low는 저가, Close는 종가, Volume은 해당 구간의 거래량입니다.
이제 이 다섯 개 값으로 거래량에 방향성을 부여해보겠습니다.

OHLCV로 거래량 불균형 프록시 만들기
가장 단순한 방법은 상승봉이면 거래량을 플러스, 하락봉이면 마이너스로 분류하는 것입니다.
df["simple_signed_volume"] = np.where(
df["close"] >= df["open"],
df["volume"],
-df["volume"]
)
간단하다는 장점은 있지만 문제가 있습니다.
시가 100, 종가 100.01인 거의 움직이지 않은 상승봉과 시가 100, 종가 102인 강한 상승봉을 같은 방식으로 처리합니다.
그래서 이번에는 종가가 해당 봉의 고가와 저가 사이 어디에서 끝났는지를 반영해보겠습니다.
다음과 같은 개념을 사용할 수 있습니다.
CLV = (2 × 종가 – 고가 – 저가) ÷ (고가 – 저가)
종가가 고가에 가까우면 CLV가 +1에 가까워지고, 저가에 가까우면 -1에 가까워집니다.
이를 거래량에 곱하면 방향성이 반영된 거래량 프록시를 만들 수 있습니다.
price_range = (df["high"] - df["low"]).replace(0, np.nan)
df["clv"] = (
(2 * df["close"] - df["high"] - df["low"])
/ price_range
)
df["signed_volume"] = df["volume"] * df["clv"]
하지만 한 개 분봉의 값만 보면 노이즈가 상당히 많습니다.
그래서 최근 일정 구간을 묶어 계산합니다.
window = 20
df["volume_imbalance"] = (
df["signed_volume"].rolling(window).sum()
/ df["volume"].rolling(window).sum()
)
여기서 만들어진 volume_imbalance는 대략 -1에서 +1 사이에서 움직입니다.
0보다 큰 방향으로 움직인다면 최근 구간에서 종가가 상대적으로 고가 쪽에서 형성된 거래량의 비중이 컸다는 의미로 해석할 수 있습니다.
반대로 0보다 크게 낮아진다면 저가 쪽에서 마감된 거래량의 영향이 상대적으로 컸다는 의미입니다.
중요한 점이 있습니다.
이 값은 실제로 “누가 시장가 매수를 몇 주 했고 누가 시장가 매도를 몇 주 했다”를 계산한 것이 아닙니다.
OHLCV 가격 위치에 거래량을 결합해 만든 추정 지표입니다.
따라서 이름 역시 실제 Order Flow Imbalance와 구분하기 위해 ‘Volume Imbalance Proxy’ 정도로 이해하는 편이 정확합니다.
극단적인 거래량 불균형을 Z-Score로 찾아보기
단순히 volume_imbalance가 -0.5 아래라고 해서 항상 강한 매도 압력이라고 판단하기도 어렵습니다.
시장 변동성은 시기마다 달라지기 때문입니다.
이럴 때 사용할 수 있는 방법 중 하나가 Z-Score입니다.
현재 값이 최근 평균에서 표준편차 기준으로 얼마나 떨어져 있는지를 계산하는 방식입니다.
pandas에서는 rolling 함수를 이용해 이동평균과 이동 표준편차를 계산할 수 있습니다.
z_window = 60
imb_mean = df["volume_imbalance"].rolling(z_window).mean()
imb_std = df["volume_imbalance"].rolling(z_window).std()
df["imbalance_z"] = (
(df["volume_imbalance"] - imb_mean)
/ imb_std
)
이번에는 가격과 거래량도 같은 방식으로 표준화해보겠습니다.
df["return_5"] = df["close"].pct_change(5)
ret_mean = df["return_5"].rolling(60).mean()
ret_std = df["return_5"].rolling(60).std()
df["return_z"] = (
(df["return_5"] - ret_mean)
/ ret_std
)
vol_mean = df["volume"].rolling(60).mean()
vol_std = df["volume"].rolling(60).std()
df["volume_z"] = (
(df["volume"] - vol_mean)
/ vol_std
)
이제 데이터에는 가격 움직임, 거래량 증가 정도, 거래량 불균형 정도라는 세 가지 정보가 들어있습니다.
여기부터가 재미있는 부분입니다.
단순히 매도 불균형이 큰 시점을 찾는 대신 “가격은 크게 하락했는데 매도 압력은 약해지기 시작하는 시점”을 찾아볼 수 있습니다.
예를 들어 연구용으로 다음과 같은 조건을 만들어볼 수 있습니다.
df["imbalance_recovering"] = (
df["volume_imbalance"]
> df["volume_imbalance"].shift(1)
)
df["bullish_reversal_candidate"] = (
(df["return_z"] < -1.5)
& (df["imbalance_z"].shift(1) < -1.0)
& (df["imbalance_recovering"])
& (df["volume_z"] > 0.5)
& (df["close"] > df["close"].shift(1))
)
여기에 사용한 -1.5, -1.0, 0.5라는 숫자는 정답이 아닙니다.
설명하기 위한 예시 임계값일 뿐입니다.
이 숫자를 과거 데이터에서 가장 수익이 좋도록 계속 조정하면 오히려 과최적화가 발생할 수 있습니다.
핵심 논리는 숫자가 아니라 순서입니다.
가격이 평소보다 크게 하락하고, 매도 방향의 불균형이 강하게 나타났다가, 그 압력이 더 이상 확대되지 않고, 거래량이 존재하는 상황에서 가격이 회복하기 시작하는지를 관찰하는 것입니다.
차트에도 표시해볼 수 있습니다.
signals = df[df["bullish_reversal_candidate"]]
plt.figure(figsize=(14, 7))
plt.plot(
df.index,
df["close"],
label="QQQ Close",
color="black"
)
plt.scatter(
signals.index,
signals["close"],
marker="^",
color="red",
s=80,
label="Reversal Candidate"
)
plt.legend()
plt.title("QQQ Volume Imbalance Reversal Candidates")
plt.show()
화살표가 많이 나온다고 좋은 지표는 아닙니다.
오히려 여기서부터 검증이 시작됩니다.
파이썬으로 나스닥100 분봉 거래량 불균형 지표 만드는 방법
신호를 만들었다면 반드시 다음 단계까지 검증해야 한다
차트에 반전 후보가 표시되면 가장 먼저 하고 싶은 것이 “몇 번이나 맞았는가?”를 계산하는 것입니다.
하지만 승률만 보면 전략의 실체를 놓칠 수 있습니다.
예를 들어 열 번 가운데 여섯 번 맞더라도 맞을 때 얻는 움직임보다 틀릴 때 손실이 훨씬 크다면 전략으로서 의미가 약할 수 있습니다.
반대로 승률은 낮아도 평균 이익이 평균 손실보다 충분히 큰 구조라면 결과가 달라질 수 있습니다.
또 하나 매우 중요한 것이 미래 데이터 사용 여부입니다.
현재 5분봉의 종가와 거래량을 이용해 신호를 계산했다면 해당 정보는 그 5분봉이 완료된 뒤에야 확정됩니다.
그런데 백테스트에서 같은 봉의 저가에 매수한 것으로 처리하면 실제로는 알 수 없었던 정보를 사용하게 됩니다.
따라서 신호가 발생한 다음 봉부터 진입 가능한 것으로 처리하는 등 현실적인 체결 규칙이 필요합니다.
수수료와 Bid·Ask 스프레드, 슬리피지도 빼놓을 수 없습니다.
특히 분봉 전략은 거래 횟수가 많아질 가능성이 있기 때문에 작은 거래비용도 누적되면 결과가 크게 달라질 수 있습니다.
그리고 이번 코드에서 가장 중요한 한계도 기억해야 합니다.
우리가 만든 거래량 불균형은 실제 호가 데이터를 이용한 Order Flow Imbalance가 아닙니다.
분봉의 고가·저가·종가 위치와 거래량을 조합한 프록시입니다.
그럼에도 이런 과정을 직접 만들어보는 이유가 있습니다.
단순히 “거래량이 터졌으니 반전한다”는 막연한 해석을 숫자로 바꿀 수 있기 때문입니다.
가격이 얼마나 이례적으로 움직였는지, 거래량이 평소보다 얼마나 증가했는지, 거래 압력이 어느 방향으로 기울어 있는지 각각 변수로 분리하면 이후 백테스트도 가능해집니다.
결국 좋은 지표를 만드는 출발점은 복잡한 공식이 아닙니다.
무엇을 측정하고 있는지 정확히 정의하는 것입니다.
이번 글에서는 QQQ 분봉 OHLCV를 이용해 거래량 불균형 프록시를 만들었습니다.
다음 단계에서는 이 신호가 실제로 의미가 있는지 확인해야 합니다.
예를 들어 신호 발생 이후 3개, 6개, 12개 분봉의 미래 수익률을 계산하고, 신호가 없는 일반 구간과 비교할 수 있습니다.
그 결과까지 확인해야 비로소 “차트에서 그럴듯해 보이는 지표”에서 “검증 가능한 전략 가설”로 넘어가게 됩니다.
다음 글에서는 바로 이 데이터를 이용해 거래량 불균형 반전 전략을 백테스트하고 승률만이 아니라 평균 수익률, 손익비, 최대낙폭, 거래비용 적용 전후 결과까지 비교해보겠습니다.
참고 자료
Nasdaq, Nasdaq-100 Index Methodology 및 Nasdaq-100 안내 자료.
Invesco, Invesco QQQ 공식 상품 자료.
pandas 공식 문서, Rolling Window 관련 문서.
yfinance 공식 문서, download API 및 intraday interval 안내.
투자 유의사항
본 글은 파이썬을 활용한 금융 데이터 분석 방법을 설명하기 위한 교육·정보 제공 목적의 콘텐츠입니다. 특정 금융상품의 매수 또는 매도를 권유하거나 향후 가격 움직임을 보장하기 위한 내용이 아닙니다.
분봉 데이터와 과거 가격을 이용한 분석 결과는 데이터 출처, 거래비용, 슬리피지, 시장 상황, 분석 기간 및 변수 설정에 따라 달라질 수 있습니다. 과거 데이터에서 관찰된 패턴이 미래에도 동일하게 나타난다고 볼 수 없으므로 실제 투자에 활용할 경우 별도의 검증과 위험 관리가 필요합니다.
금알남
댓글 0
첫 댓글을 남겨보세요.