Johansen 검정으로 설계하는 공적분 기반 페어 트레이딩
Johansen 검정과 VECM을 활용해 공적분 페어를 선정하고, 헤지 비율·스프레드 신호·주문 원자성·리스크 관리까지 연결하는 통계적 차익거래 설계
2026-08-14 · 최초 발행 2024-04-29
상대가격이 아니라 장기 균형을 거래하는 방식
페어 트레이딩은 공통 요인을 공유하는 가격쌍에서 상대적 과대·과소평가를 찾아 롱과 숏을 동시에 구성하는 통계적 차익거래 기법이다. 방향성 노출을 줄이고 상대가치(relative value)에 집중하며, 평균회귀하는 스프레드에서 수익 기회를 찾는다.
이 전략의 근거는 공적분(Cointegration)이다. 각 시계열이 비정상(I(1))이더라도 선형 결합이 정상(I(0))이라면, 두 가격 사이에 장기 균형 관계가 존재한다고 볼 수 있다. 헤지 비율(β)은 이 관계를 표현하며, 스프레드는 s_t = y_t − βx_t로 구성한다.
Johansen Test는 벡터오차수정모형(VECM)에서 공적분 랭크(r)를 추정하는 절차다. 트레이스 통계량과 최대 고유값 통계량으로 랭크를 정하고, 고유벡터에서 공적분 조합과 헤지 비율을 얻는다. 2자산 페어에도 쓸 수 있고 다자산으로 확장하기 쉽다.
페어를 고르고 신호를 만들기까지
데이터는 거래 시간대, 결측치, 이상치, 기업행위인 분할·배당을 반영해 정제해야 한다. 두 자산의 관측 시점을 맞추기 위해 캔들 스냅샷 또는 이전 틱 보간(Last Observation Carried Forward)을 적용하고, 프리·포스트 마켓을 제외하는 식으로 실제 거래 가능 구간도 일치시킨다.
후보군은 산업, 시가총액, 스타일 같은 섹터·팩터 유사성으로 먼저 좁힌다. 이후 공분산·상관 필터, ADF/PP 단위근 검사, Johansen 또는 Engle–Granger 검정을 차례로 사용한다. 다자산에서는 PCA로 공통요인을 제거한 뒤 잔차 기반 근접성을 측정할 수 있다.
Johansen Test 전에 AIC/BIC로 적정 시차(p)를 고르고, 검정 결과에서 랭크와 β를 구한다. 롤링 윈도우 재추정과 구간 안정성 검사(Sup-F/구조적 변동 힌트)는 레짐 변동을 감시하는 데 사용한다. 여러 후보를 동시에 검정할 때는 Šidák 또는 Benjamini–Hochberg 보정으로 과적합을 억제한다.
스프레드는 z-score로 정규화해 밴드 전략의 진입·청산 임계치를 정한다. Hurst 지수와 ADF t-값은 평균회귀 강도를 거르는 보조 기준이 될 수 있다. 포지션 크기는 체결비용과 슬리피지를 반영해 동적으로 조정한다.
운영 단계에서는 주문 쌍 원자성이 필요하다. 모두 체결하거나 둘 다 취소하고, 한쪽이 먼저 체결되면 반대 자산을 즉시 헷지하는 주문 락을 둔다. 포지션 상태는 New→PartiallyFilled→Filled→Hedged→Closed 상태머신으로 관리한다.
시장 중립성을 운영 조건으로 만드는 일
동일 섹터 주식에서는 항공의 대형 A와 B, 국내 대형 은행 1과 2처럼 공적분 안정성과 유동성을 기준으로 일중~스윙 차익거래를 구성할 수 있다.
ETF와 상위 구성종목 바스켓 사이에서는 다자산 Johansen 확장으로 바스켓 β를 추정해 추적 오차를 줄일 수 있다. 동일 기초자산의 근월–원월 선물은 캘린더 스프레드나 현선 차익과 결합할 수 있지만, 거래시간 동조화와 베이시스 이벤트 리스크 관리가 필요하다.
마켓·베타·섹터 노출은 중립화하고, 스프레드 분산 급증이나 잔차 ADF 실패가 나타나면 회귀 관계 붕괴로 보고 해당 페어를 중지한다. 일중·총위험 한도, 손절과 이익실현, 변동성 타게팅, 자본 배분 규칙도 거래 로직에 포함한다.
변동성 타게팅에서 목표 연간 변동성 10%를 둘 경우 샤프지수는 0.6→1.0 이상 개선되고, 드로우다운은 20~35% 축소될 가능성이 있다. 체결비용을 포함한 뒤에도 알파의 절반 이상이 유지되는지가 기준이 되며, 수치는 자산군과 시장체결비용에 좌우된다. 방향성 노출 축소는 포트폴리오 방어력을 높이고, 다자산·다주기 분산은 전략 지속가능성을 보완한다. 모델의 투명성과 검증 용이성은 운영 컴플라이언스 적합성에도 연결된다.
검정 방법과 적응형 스프레드의 차이
| 방법 | 성능(알파 유지) | 확장성(다자산) | 일관성(통계적 타당) | 안정성(레짐 전환 대응) | 운영 편의 |
|---|---|---|---|---|---|
| Engle–Granger(2단계) | 중 | 낮음(2자산 한정) | 보통(공통요인 누락 민감) | 보통 | 높음(간단) |
| Johansen(VECM) | 중~상 | 높음(다자산 가능, r 추정) | 높음(공적분 랭크 추정) | 보통~상(다중 관계 포착) | 중(모수·시차 튜닝 필요) |
| 상태공간·칼만 스프레드 | 상(적응 추정) | 중 | 보통(모형 가정 영향) | 상(시간가변 β) | 중~낮음(구현 복잡) |
Johansen은 다변량 관계를 다루고 공적분 랭크를 추정한다는 점에서 강점이 있지만, 시차와 결정항 같은 파라미터에 민감하다. 칼만 필터는 시간가변 β에 적응할 수 있으나 구현과 튜닝이 복잡하고 모형위험이 커진다. 단순 z-score 전략은 운영하기 쉽지만 레짐 전환에서 성능이 저하될 수 있다.
statsmodels로 공적분 페어 신호 구성하기
전제조건: Python 3.11, pandas 2.x, numpy 1.26+, statsmodels 0.14+ (패키지 API 변경 가능성, 최신 정보 확인 필요)
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.vector_ar.vecm import coint_johansen
# 1) 데이터 로드(예시: CSV 두 종목 종가)
x = pd.read_csv("X.csv", parse_dates=["date"], index_col="date")["close"]
y = pd.read_csv("Y.csv", parse_dates=["date"], index_col="date")["close"]
# 2) 동조화 및 전처리
df = pd.concat([x, y], axis=1, join="inner").dropna()
df.columns = ["X", "Y"]
# 단위근 체크(둘 다 I(1) 가정)
for col in df:
adf_p = adfuller(df[col].pct_change().dropna())[1] # 수익률 정상성 점검
assert adf_p < 0.05, f"{col}: 비정상 수익률, 데이터 확인 필요"
# 3) Johansen Test (det_order=0: 상수 항, k_ar_diff=1: 차분 시차)
res = coint_johansen(df[["X","Y"]], det_order=0, k_ar_diff=1)
# 랭크 판정: trace 통계량 > 임계치
r = int((res.lr1 > res.cvt[:,1]).sum()) # 5% 기준
assert r >= 1, "공적분 관계 부재, 페어 제외"
# 4) 헤지 비율(β) 산출: 첫 번째 공적분 벡터 정규화
beta_vec = res.evec[:, 0]
beta = - beta_vec[0] / beta_vec[1] # 스프레드 s = Y - βX 형태
s = df["Y"] - beta * df["X"]
z = (s - s.rolling(252).mean()) / s.rolling(252).std()
z = z.dropna()
# 5) 시그널링 규칙
z_in, z_out = 1.5, 0.2
signals = pd.Series(index=z.index, dtype=int)
signals[(z > z_in)] = -1 # 스프레드 과대: Y 숏, X 롱
signals[(z < -z_in)] = 1 # 스프레드 과소: Y 롱, X 숏
signals[abs(z) < z_out] = 0
signals = signals.ffill().fillna(0)
# 6) 포지션 및 PnL (단순화, 체결비용 가정)
ret_x = df["X"].pct_change().reindex(z.index).fillna(0)
ret_y = df["Y"].pct_change().reindex(z.index).fillna(0)
# 노션 균형: |Y|:|X| = 1:|β|
w_y = 1.0
w_x = -beta
gross = abs(w_y) + abs(w_x)
w_y /= gross; w_x /= gross
pnl = signals.shift().fillna(0) * (w_y * ret_y + w_x * ret_x)
# 거래비용(왕복 10bps 가정) 적용
tcost = 0.001 * (signals.diff().abs().fillna(0))
pnl_net = pnl - tcost
print("연환산 수익률:", (1 + pnl_net).prod()**(252/len(pnl_net)) - 1)
print("연환산 변동성:", pnl_net.std() * np.sqrt(252))
print("샤프:", pnl_net.mean() / pnl_net.std() * np.sqrt(252))
롤링 재추정 주기는 예를 들어 60거래일로 두고 β와 랭크의 안정성을 확인한다. 다중 페어 포트폴리오의 자본은 균등 또는 위험균형(Vol/ES/VaR 기반) 방식으로 배분할 수 있다. 주문은 스프레드 주문 또는 OCO(One-Cancels-the-Other) 세트로 구성해 원자성을 보장한다.
백테스트에는 체결비용과 슬리피지 시뮬레이션을 포함하고, 실시간 모니터링 대시보드에서 롤링 윈도우, 레짐 변화, 다중검정 보정 결과를 지속적으로 확인해야 한다. 공적분 검정 결과만으로 거래를 확정하지 않고, 체결과 위험 관리까지 같은 파이프라인으로 묶는 것이 이 전략의 운영 조건이다.