[알고리즘 트레이딩] ETF 초단기 페어 트레이딩 전략: KODEX 200 vs KODEX 인버스

🔍 페어 트레이딩(Pairs Trading)이란?

페어 트레이딩은 통계적 차익거래 전략으로, 서로 높은 상관관계를 갖는 두 자산(쌍)을 선택해 가격 차이(스프레드)가 평균에서 벗어날 때 진입하고, 평균으로 회귀할 때 청산하는 구조입니다.


국내 ETF를 활용한 실전 예시

  • Long 대상: KODEX 200 (069500.KS)

  • Short 대상: KODEX 인버스 (114800.KS)

  • 두 ETF는 거의 정확히 반대 방향으로 움직이며, 이 관계를 활용한 초단기 전략 설계가 가능합니다.


📐 전략 설계 개요

요소내용
자산 페어KODEX 200 vs KODEX 인버스
데이터 소스야후 파이낸스 (yfinance 라이브러리)
데이터 타입5분봉
스프레드 정의log(KODEX 200) - log(KODEX 인버스)
Z-score 윈도우20
진입 조건Z-score > +2 또는 < -2
청산 조건Z-score가 0 근처로 회귀할 때
시뮬 수익 계산Long 수익 + Short 수익

🧠 전략 진입과 청산 논리

  • Z-score가 +2 이상일 경우:

    • KODEX 200 매수 (Long)

    • KODEX 인버스 매도 (Short) → 실전에서는 매도가 아닌 ETF 특성상 반대 방향 투자로 대응

  • Z-score가 0 이하로 수렴:

    • 포지션 청산

이 방식은 반드시 처음부터 두 ETF를 동시에 보유하거나, 스프레드가 벌어지는 순간 동시에 진입해야 합니다.


💡 실전과의 차이

  • 실전에서는 공매도 어려움, 슬리피지, 실시간 체결가 반영 문제 등 복잡한 요소가 존재합니다.

  • 본 전략은 시뮬레이션/연구 목적이며, 실제 자동매매를 위해선 증권사 API 연동이 필요합니다.


💻 참조용 코드: 야후 파이낸스 기반 초단기 시뮬레이터

import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt # Ticker 설정 ticker_long = yf.Ticker("069500.KS") # KODEX 200 ticker_short = yf.Ticker("114800.KS") # KODEX 인버스 # 5분봉, 1일치 데이터 df_long = ticker_long.history(period="1d", interval="5m") df_short = ticker_short.history(period="1d", interval="5m") # 종가 기준으로 정렬 df = pd.DataFrame({ "long": df_long["Close"], "short": df_short["Close"] }).dropna() # 로그 스프레드 계산 df["spread"] = np.log(df["long"]) - np.log(df["short"]) # 이동평균, 표준편차 lookback = 20 # Z-score 계산 윈도우 df["mean"] = df["spread"].rolling(lookback).mean() df["std"] = df["spread"].rolling(lookback).std() df["zscore"] = (df["spread"] - df["mean"]) / df["std"] # 진입/청산 신호 entry_threshold = 2 exit_threshold = 0 df["signal"] = 0 df.loc[df["zscore"] > entry_threshold, "signal"] = 1 # 진입 Long df.loc[df["zscore"] < -entry_threshold, "signal"] = -1 # 진입 Short df.loc[abs(df["zscore"]) < exit_threshold, "signal"] = 0 # 청산 # 시그널 변화 감지 df["position"] = df["signal"].replace(to_replace=0, method="ffill").fillna(0) # 수익률 계산 df["long_return"] = df["long"].pct_change() df["short_return"] = -df["short"].pct_change() df["strategy_return"] = df["position"].shift() * (df["long_return"] + df["short_return"]) df["cumulative_return"] = (1 + df["strategy_return"].fillna(0)).cumprod() # 그래프 출력 plt.figure(figsize=(12, 6)) plt.plot(df["zscore"], label="Z-score") plt.axhline(entry_threshold, color='red', linestyle='--') plt.axhline(-entry_threshold, color='blue', linestyle='--') plt.axhline(0, color='black', linestyle='-') plt.title("Z-score + Entry/Exit Thresholds") plt.legend() plt.grid() plt.show() plt.figure(figsize=(12, 5)) plt.plot(df["cumulative_return"], label="Strategy Return") plt.title("Cumulative Return (Simulated)") plt.grid() plt.legend() plt.show()

🛠️ 시뮬레이션 & 자동화 도구 제안

  • 자동 시그널 감지 웹 앱: FastAPI + JavaScript를 활용해 시각화 및 알림 시스템 구성

  • 백테스트 시나리오: 다양한 ETF 쌍에 적용해 성능 비교

  • 실전 대응 확장: 키움 API, 미래에셋 API 등으로 확장 시 진입/청산 트리거 자동 실행

  • 리스크 관리 시뮬레이션: 최대 낙폭(MDD), 평균 복구 시간 분석 기능 추가

댓글

이 블로그의 인기 게시물

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

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

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

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

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

NPN, PNP 트랜지스터 차이점

PLC 출력 형태

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

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

[자동화] 안쓰는 안드로이드폰을 활용한 식물 성장 타임랩스 촬영