[Quant Strategy] Pair Trading with Long/Short
Pair Trading - Long-Short¶
A와 B의 가격사이에 높은 상관관계(절댓값 0.8이상)을 찾고, 만약 가격이 반대 방향으로 움직이는 관계를 발견한다면,
Long-Short 전략을 시도 할 수 있다.
이번 프로젝트에서는 가설매매로서의 Long-Short 전략 수립 과정을 세우고 Backtesting 한 후,
다음 프로젝트에서 Nasdaq 100 내의 상관계수를 고려한
평균회귀 전략을 시도해 보도록 하겠다.
가설 수립¶
최근 금리 인상과 우크라이나/러시아 전쟁우려에 따른 여파로 나스닥, 비트코인이 급락하고,
안전자산 수요로 금,채권, 인플레이션,전쟁여파에 따라 유가가 급등하는 양상을 보여주고 있다.
이러한 상황에서 기술주 포지션을 최대한 헷지 하려면,
숏 포지션으로 비트코인, 채권가격 , 롱 포지션으로 금, 유가을 잡는 전략을 세울 수 있다.
또한 전체 증시가 1월말 이후 횡보하는 양상을 보이는데,
낙폭이 큰 나스닥,러셀2000 숏 포지션과 변동성이 미미한 S&P500을 롱하는 전략도 세울 수 있다.
2022년 1월 1일부터 현재까지, 나스닥 롱 포지션과 비트코인 숏 포지션에 따른 수익률을 시각화해보도록 하겠다.
데이터 준비 과정¶
나스닥100 선물 데이터와, 비트코인 선물 데이터를 인베스팅 닷컴에서 크롤링 한 후,
DataFrame 형태로 바꾸어 준다.
import requests
from bs4 import BeautifulSoup
# simbol - nasdaq 100 F, WTI F
# resolution - hour (60min)
# from & to - time line // 2022-01-01
from urllib.request import Request, urlopen
# NASDAQ Future symbol : 8874
# WTI Future symbol : 8849
# S&P 500 F : 1175153
# Russell F : 1174944
# Gold F : 8830
# BTC : 1057391
# QLD : 14207
# TSLA : 13994
long_url = "https://tvc4.investing.com/f260fb7c8e75ae2ce49d13fec98539d7/1645104032/1/1/8/history?symbol=8874&resolution=60&from=1640996500&to=1650000000"
short_url = "https://tvc4.investing.com/4c48fe6a63b9b709b73e8ec996f2d66f/1645104213/1/1/8/history?symbol=1057391&resolution=60&from=1640996500&to=1650000000"
long_req = Request(long_url, headers={'User-Agent': 'Mozilla/5.0'})
long_webpage = urlopen(long_req).read()
short_req = Request(short_url, headers={'User-Agent': 'Mozilla/5.0'})
short_webpage = urlopen(short_req).read()
# HTML -> Str
long_str_data = long_webpage.decode('utf-8')
short_str_data = short_webpage.decode('utf-8')
long_data = eval(long_str_data)
short_data = eval(short_str_data)
# timestamp 형태의 시간 데이터를 "%Y-%m-%d-%H" 꼴로 변환
from datetime import datetime
import time
long_time = []
short_time = []
for l in long_data['t']:
ll = datetime.fromtimestamp(l)
long_time.append(ll.strftime("%Y-%m-%d-%H"))
for s in short_data['t']:
ss = datetime.fromtimestamp(s)
short_time.append(ss.strftime("%Y-%m-%d-%H"))
# dict형 OHLC 데이터 -> dataframe
import pandas as pd
long_z = zip(long_data['o'],long_data['h'],long_data['l'],long_data['c'])
long_z = list(long_z)
short_z = zip(short_data['o'],short_data['h'],short_data['l'],short_data['c'])
short_z = list(short_z)
long_date = pd.DataFrame(long_time, columns=['date'])
long_df = pd.DataFrame(long_z, columns=['Open','High','Low','Close'])
short_date = pd.DataFrame(short_time, columns=['date'])
short_df = pd.DataFrame(short_z, columns=['Open','High','Low','Close'])
# 날짜 데이터와 OHLC 데이터를 통합 시킨다.
long_df = pd.concat([long_date,long_df], axis=1, ignore_index=False)
short_df = pd.concat([short_date,short_df], axis=1, ignore_index=False)
# 시간대별 수익률
long_df['rate'] = (long_df['Close'] - long_df['Open']) / long_df['Open'] + 1
short_df['rate'] = (short_df['Close'] - short_df['Open']) / short_df['Open'] + 1
# long 포지션은 그대로, short 포지션은 1 - 수익률로 변화율을 만들어 준다.
long_df['cumulative'] = (long_df['Close'] - long_df['Open'].iloc[0]) / long_df['Open'].iloc[0] + 1
short_df['cumulative'] = 1 - (short_df['Close'] - short_df['Open'].iloc[0]) / short_df['Open'].iloc[0]
# 원래는 일일 수익률을 누적곱으로 표현해 누적 수익률을 구할 수 있지만
# 이전 행 close가 다음 행 open하고 같아야 하는데 그렇지 않아서 기준 값으로 구하였다.
# long_df['cumulative'] = long_df['rate'].cumprod()
# short_df['cumulative'] = short_df['rate'].cumprod()
long_df.set_index('date', inplace=True)
short_df.set_index('date', inplace=True)
long_df
Open | High | Low | Close | rate | cumulative | |
---|---|---|---|---|---|---|
date | ||||||
2022-01-02-23 | 16368.75 | 16400.25 | 16368.75 | 16384.25 | 1.000947 | 1.000947 |
2022-01-03-00 | 16389.75 | 16420.50 | 16384.75 | 16416.00 | 1.001602 | 1.002887 |
2022-01-03-01 | 16418.00 | 16427.50 | 16400.25 | 16403.25 | 0.999102 | 1.002108 |
2022-01-03-02 | 16401.75 | 16416.00 | 16393.50 | 16415.75 | 1.000854 | 1.002871 |
2022-01-03-03 | 16415.75 | 16418.00 | 16400.50 | 16407.00 | 0.999467 | 1.002337 |
... | ... | ... | ... | ... | ... | ... |
2022-02-18-17 | 13979.25 | 14056.25 | 13941.75 | 13942.75 | 0.997389 | 0.851791 |
2022-02-18-18 | 13945.25 | 14007.75 | 13906.50 | 13989.50 | 1.003173 | 0.854647 |
2022-02-18-19 | 13989.50 | 14125.00 | 13981.75 | 14106.75 | 1.008381 | 0.861810 |
2022-02-18-20 | 14111.00 | 14140.75 | 13962.00 | 13998.25 | 0.992010 | 0.855181 |
2022-02-18-21 | 14008.25 | 14038.00 | 13987.25 | 13993.75 | 0.998965 | 0.854906 |
802 rows × 6 columns
from scipy import stats
corr = pd.concat([long_df['Open'],short_df['Open']], axis=1, join='inner', ignore_index=False, keys=['L', 'S'])
r, p_val = stats.pearsonr(corr['L'], corr['S'])
print("Nasdaq 100 과 BTC의 상관계수는" , r , "입니다.")
Nasdaq 100 과 BTC의 상관계수는 0.6248682640190434 입니다.
# 합성 포지션 (Long-Short)을 구현한다.
# 둘의 데이터 포인트의 수가 다르므로, 교집합으로 묶어준다.
composite = pd.concat([long_df['cumulative'],short_df['cumulative']], axis=1, join='inner', ignore_index=False, keys=['L_cul', 'S_cul'])
composite['cumulative'] = ((composite['L_cul'] + composite['S_cul']) / 2 - 1) * 100
composite
L_cul | S_cul | cumulative | |
---|---|---|---|
date | |||
2022-01-02-23 | 1.000947 | 0.985933 | -0.656006 |
2022-01-03-00 | 1.002887 | 0.991382 | -0.286546 |
2022-01-03-01 | 1.002108 | 0.990111 | -0.389053 |
2022-01-03-02 | 1.002871 | 0.992726 | -0.220133 |
2022-01-03-03 | 1.002337 | 0.997057 | -0.030309 |
... | ... | ... | ... |
2022-02-18-17 | 0.851791 | 1.141622 | -0.329368 |
2022-02-18-18 | 0.854647 | 1.139098 | -0.312761 |
2022-02-18-19 | 0.861810 | 1.138145 | -0.002269 |
2022-02-18-20 | 0.855181 | 1.143344 | -0.073755 |
2022-02-18-21 | 0.854906 | 1.143006 | -0.104355 |
802 rows × 3 columns
시각화 결과¶
금리 인상에 따라 나스닥 롱 포지션이 손해를 입더라도,
BTC 숏 포지션으로 헷지함으로 추가 수익을 낼 수 있었다.
ROE 그래프는 Nasdaq / BTC Ratio로도 해석이 가능하다.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig = plt.figure(figsize=(18,10)) ## 캔버스 생성
fig.set_facecolor('white') ## 캔버스 색상 설정
ax = fig.add_subplot() ## 프레임 생성
ax.plot(composite['cumulative'], color='#000000') ## 선그래프 생성
plt.title("Nasdaq Long / BTC Short ROE")
plt.ylabel('ROE')
plt.xlabel('Date')
plt.grid(True, lw=0.4, ls='--')
plt.xticks(rotation=45) ## x축 눈금 라벨 설정 - 40도 회전
ax.xaxis.set_major_locator(ticker.MultipleLocator(56))
#plt.title('Sales for 10 days',fontsize=20) ## 타이틀 설정
plt.show()
평균 회귀를 노리고 프로젝트를 진행하지는 않았지만,
1월 20일 ROE가 증가한 시점에 나스닥 숏, BTC 롱 포지션을 취했으면 8%정도의 이익을 취할 수 있었다.
'Quant' 카테고리의 다른 글
[Quant Strategy] NCAV 전략 - 2022.03.16 기준 (12) | 2022.03.17 |
---|---|
[Project] 증권사 리포트에 따라 사면 어떻게 될까?? (35) | 2022.03.11 |
Project : Price and EPS NTM (1) | 2021.12.09 |
[금융상품] 채권 (18) | 2021.10.26 |
[금융상품] 원유 (20) | 2021.10.18 |