[알고리즘 트레이딩] 코스피 ETF를 활용한 페어 트레이딩 + 인버스 헷지 전략

📈 **페어 트레이딩(Pairs Trading)**은 두 자산의 상대적인 가격 차이가 일정 범위를 벗어날 때, 그 차이가 다시 수렴한다는 전제 하에 롱/숏 포지션을 동시에 취해 수익을 노리는 전략입니다. 이 전략은 시장이 상승하든 하락하든 비교적 시장 방향성에 영향을 덜 받는 시장 중립적 전략이라는 점에서 매력적입니다.

그런데, 실제 시장에서는 전체 지수가 급락할 때 페어 간의 스프레드도 영향을 받을 수 있습니다. 이때 유용한 것이 인버스 ETF를 활용한 헷지 전략입니다.


🎯 전략 요약

  1. 페어 선정: KODEX 200 vs TIGER 200 (코스피200 지수 추종 ETF)

  2. 진입 조건: 두 ETF 간의 z-score가 특정 임계값(예: -2 이하)을 하회할 때

  3. 포지션 구성:

    • KODEX 200 매수

    • TIGER 200 매도

    • 시장 방향성 헷지를 위해 KODEX 인버스 ETF 매수

  4. 청산 조건: z-score가 평균 근처로 복귀 (예: -0.5 이상)

  5. 청산 시점: 모든 포지션을 동시에 종료 (롱/숏/헷지 포함)


🧪 예제 코드 (Python + pandas)

import yfinance as yf import pandas as pd import matplotlib.pyplot as plt from scipy import stats # 1. 데이터 불러오기 start = "2023-01-01" end = "2024-12-31" # 코스피200 추종 ETF (KODEX200, TIGER200), 인버스 ETF (KODEX 인버스) symbols = { "KODEX200": "069500.KQ", "TIGER200": "102110.KQ", "KODEX_INVERSE": "114800.KQ" } data = yf.download(list(symbols.values()), start=start, end=end)['Adj Close'] data.columns = list(symbols.keys()) # 2. 스프레드와 z-score 계산 def calculate_zscore(series1, series2): beta, _, _, _, _ = stats.linregress(series2, series1) spread = series1 - beta * series2 zscore = (spread - spread.mean()) / spread.std() return zscore, spread, beta zscore, spread, beta = calculate_zscore(data["KODEX200"], data["TIGER200"]) # 3. 매매 시그널 생성 entry_z = -2 exit_z = -0.5 signals = pd.DataFrame(index=data.index) signals['zscore'] = zscore signals['long'] = (zscore < entry_z).astype(int) signals['exit'] = (zscore > exit_z).astype(int) # 4. 포지션 시뮬레이션 (간단한 수익 누적) position = 0 profits = [] for i in range(len(signals)): if signals['long'].iloc[i] and position == 0: buy_price = data["KODEX200"].iloc[i] sell_price = data["TIGER200"].iloc[i] hedge_price = data["KODEX_INVERSE"].iloc[i] position = 1 elif signals['exit'].iloc[i] and position == 1: p_kodex = data["KODEX200"].iloc[i] - buy_price p_tiger = sell_price - data["TIGER200"].iloc[i] p_hedge = data["KODEX_INVERSE"].iloc[i] - hedge_price profits.append(p_kodex + p_tiger + p_hedge) position = 0 cumulative_profit = pd.Series(profits).cumsum() # 5. 결과 시각화 plt.figure(figsize=(12, 6)) plt.plot(zscore, label="Z-score") plt.axhline(entry_z, color='red', linestyle='--', label='Entry') plt.axhline(exit_z, color='green', linestyle='--', label='Exit') plt.title("Z-score of KODEX200 vs TIGER200") plt.legend() plt.show() plt.figure(figsize=(12, 4)) plt.plot(cumulative_profit, label="Cumulative Profit") plt.title("Cumulative Profit (with Inverse ETF Hedge)") plt.legend() plt.show()

🤔 전략 운용 시 고려할 점

항목설명
📊 페어 선택 신중공적분 관계나 상관계수 분석 필요
🧮 z-score 기준과거 분포 기반, 너무 짧거나 길면 오류 가능
💰 거래 수수료/슬리피지ETF도 매수/매도 비용 고려해야 함
🔄 롤링 베타 활용 가능시장 변화 반영 위해 동적으로 beta 갱신 고려

✅ 마무리: 왜 인버스 ETF를 함께 청산해야 할까?

헷지용 인버스 ETF는 시장 급락에 대비한 보조적인 수단일 뿐, 독립적인 수익을 목표로 한 자산이 아닙니다.
따라서 주 포지션(롱/숏) 청산 시, 헷지 포지션도 함께 종료하는 것이 전략의 일관성과 안정성을 유지하는 핵심입니다.


📚 관련 키워드

  • Pairs Trading (페어 트레이딩)

  • Market Neutral Strategy

  • Z-score 기반 트레이딩

  • ETF 롱/숏 전략

  • 헷지(Hedge)와 인버스 ETF


🧠 페어 트레이딩, 이론은 완벽한데 수익이 안 나는 이유? 슬리피지를 고려하자

페어 트레이딩은 시장의 방향성과 관계없이 수익을 추구할 수 있는 전략입니다. 그런데 막상 실전에 적용해보면 “생각보다 수익이 안 나는 것 같다”는 느낌을 받을 수 있습니다. 그 이유 중 하나는 **슬리피지(slippage)**입니다.


❓ 슬리피지가 뭐길래?

슬리피지는 주문을 넣은 가격과 실제 체결된 가격 간의 차이를 의미합니다.
예를 들어, 매수 호가가 10,000원이었지만 실제 체결은 10,020원에서 이루어졌다면 20원의 슬리피지가 발생한 겁니다.

페어 트레이딩에서는 일반적으로 두 자산 간의 스프레드가 평균으로 회귀할 것을 기대하고 포지션을 잡습니다. 그런데 이 스프레드의 변화폭이 작다면, 슬리피지와 수수료만으로도 이익이 사라질 수 있습니다.


📉 현실적인 수익률 예시

항목수치 (가정)
평균 스프레드 회귀폭0.4%
슬리피지 (왕복)0.2%
거래 수수료 (왕복)0.15%
남는 순이익0.05% ← 거의 의미 없음 ❗

이런 상황에서는 수익이 날 가능성이 매우 낮아집니다.


🛠️ 어떻게 수익성을 높일 수 있을까?

1. 거래 횟수를 늘려 소수점 이익을 쌓는다

  • 소위 말하는 고빈도 페어 트레이딩 전략

  • 하지만 API 응답 속도, 체결 속도, 낮은 슬리피지 환경 등 조건이 까다롭습니다.

2. 스프레드가 충분히 벌어졌을 때만 진입

  • 진입 임계값(z-score)을 -2.0 → -2.5 등으로 조정

  • 거래 빈도는 줄지만, 수익 폭은 증가

3. 더 좋은 페어 찾기

  • 너무 유사한 ETF끼리는 스프레드가 거의 없음

  • 오히려 구조적으로 살짝 다른 자산 (예: 반도체 vs 디스플레이) 간의 조합이 더 유리할 수 있음

4. 슬리피지를 미리 추정해서 전략에 반영

  • 전략 설계 시 "예상 슬리피지"를 고려한 진입/청산 조건 설정이 필요

5. ETF보다 개별 종목 활용 고려

  • ETF는 동일 지수를 추종하기 때문에 스프레드가 크지 않음

  • 개별 종목 간 상관 관계를 분석하여 공적분 관계가 강한 종목군을 찾는 것이 포인트


📌 결론

페어 트레이딩은 강력한 전략이지만, 슬리피지와 수수료를 이길 만큼 충분한 회귀폭이 있어야만 실전 수익이 가능합니다.

따라서 단순히 통계적으로 z-score가 -2라고 무조건 매수하는 것이 아니라,
해당 거래에서 예상되는 **총 비용(슬리피지+수수료)**과 기대 수익폭을 항상 비교해야 합니다.


📈 관련 코드 예시 (슬리피지를 고려한 진입 신호)

calculate_entry_signal <- function(spread, entry_threshold, slippage_buffer) { adjusted_threshold <- entry_threshold - slippage_buffer entry_signal <- ifelse(spread < adjusted_threshold, 1, 0) return(entry_signal) } # 예시 사용 z_score <- -2.6 entry_threshold <- -2.5 slippage_buffer <- 0.15 # 슬리피지를 고려한 보정값 signal <- calculate_entry_signal(z_score, entry_threshold, slippage_buffer) print(signal) # 진입 조건을 만족하면 1 출력

끝.

댓글

이 블로그의 인기 게시물

전력(kW) 계산하기 (직류, 교류 단상, 교류 삼상)

[PLC] PLC 아날로그 입출력 기본

[아두이노] 가변저항(Potential Divider)과 전압분배(Voltage Divider)

공압 속도 제어: 미터인 vs 미터아웃

제너 다이오드에 저항을 연결하는 이유

PLC 출력 형태

[전기 기초] 저항의 정격전력(Watt) 표기의 의미

NPN, PNP 트랜지스터 차이점

[PLC] 래더 다이어그램과 PLC

회로 차단기 용량 선정하는 방법