GIL's LAB

실험 9. 평균 회귀 전략 검증 (feat. 첫 성공?) 본문

퀀트 투자/실험 일지

실험 9. 평균 회귀 전략 검증 (feat. 첫 성공?)

GIL~ 2021. 11. 18. 23:40

개요

이번 실험에서는 낙폭이 심했던 주가는 다시 평균 수준으로 돌아온다는 전략인 평균 회귀 전략을 검증해보겠습니다.

 

이 그림에서 보듯이, 이 전략은 현 시점을 기준으로 n1 영업일 이전을 과거 시점, n2 영업일 이후를 미래 시점이라 했을 때, n1일 영업일동안 최대 m1(%) 하락한 주가는 n2일 영업일동안 최대 m2(%) 상승할 것이라는 가정에 기반한 전략입니다. 여기서 과거 시점과 미래 시점의 주가를 그대로 사용하는 것이 아니라, 그 기간 내에 최댓값을 사용하는 것에 주목해야 합니다. 다시 말해, 과거에 m1만큼 크게 하락했으면 비슷한 기간 내에 m1과 비슷한 수준으로 다시 오를 것이라 가정하는 것이고 만족할만큼 주가가 회복되면 바로 매도하는 것입니다.

 

데이터 준비 및 환경 설정

길이가 300이상인 주가 데이터를 모두 불러와 sp_data_dict라는 사전에 추가하겠습니다.

 1 import os
 2 import pandas as pd
 3 sp_data_dict = dict()
 4 for file_name in os.listdir("../../데이터/주가데이터"):
 5     stock_price_data = pd.read_csv("../../데이터/주가데이터/" + file_name, parse_dates = ['Date'])
 6     if len(stock_price_data) >= 300:
 7         sp_data_dict[file_name[:-4]] = stock_price_data
 8    
 9     if len(sp_data_dict) >= 2:
10         break

 

다음으로 그래프를 그리기 위한 환경 설정을 해주겠습니다.

1 import seaborn as sns
2 from matplotlib import pyplot as plt
3 from matplotlib import rcParams
4 
5 sns.set()
6 %matplotlib inline
7 
8 rcParams['font.family'] = 'Malgun Gothic'
9 rcParams['axes.unicode_minus'] = False

 

평균 회귀 전략 검증 함수 작성

최근 n1 영업일 이전동안 m1% 이상 하락한 종목을 매수한 뒤, n2 영업일 이내에 m2% 이상 상승하면 매도하고 그렇지 않으면 n2 영업일 이후에 매도하는 전략을 검증하는 함수를 작성하겠습니다.

 1 def validate_strategy(sp_data_dict, n1, n2, m1, m2):
 2     profit_list = []
 3     for stock_name in sp_data_dict.keys():
 4         stock_data = sp_data_dict[stock_name]
 5         for idx in stock_data.index:
 6             if n1 <= idx <= len(stock_data) - n2:
 7                 current = stock_data.loc[idx, "Close"]
 8                 n1_before_max = stock_data.loc[idx-n1:idx, "Close"].max()
 9                 loss_before = (n1_before_max - current) / n1_before_max * 100
10                 if loss_before >= m1:
11                     buy_price = current
12                     candidate_sell_price = stock_data.loc[idx:idx+n2, "Close"].values
13                     if candidate_sell_price.max() > current * (1+m2/100):
14                         sell_price = candi_sell_price[candidate_sell_price > current * (1+m2/100)][0]
15                         
16                     else:
17                         sell_price = candidate_sell_price[-1]
18                     profit = (sell_price - buy_price) / buy_price * 100
19                     profit_list.append(profit)
20     return profit_list
  • 라인 5 - 6: n1보다 크고 행 개수 - n2보다 작은 범위를 idx로 순회합니다.
  • 라인 7: 인덱스가 idx인 Close값을 가져와서 current에 저장합니다.
  • 라인 8: 인덱스가 idx-n1부터 idx까지인 행 가운데 Close의 최댓값을 n1_before_max에 저장합니다.
  • 라인 9: n1 영업일 이전부터 현재까지의 최댓값을 기준으로 손실을 계산합니다.
  • 라인 9: n1 영업일 이전 최대값 대비 주가가 얼마나 하락했는지를 loss_before에 저장합니다.
  • 라인 10: 하락율이 m1 이상이면 현재 가격으로 매수합니다.
  • 라인 12: idx부터 idx+n2까지의 종가 데이터를 candidate_sell_price에 저장합니다.
  • 라인 13: 만약 candidate_sell_price에서 current를 기준으로 m2% 이상 상승한 날이 있다면, m2%를 처음 넘는 날의 종가를 sell_price에 저장합니다.
  • 라인 16 – 17: n2 영업일 이내에 m2% 이상 상승한 날이 없다면 n2 영업일 되는 날의 종가를 sell_price에 저장합니다.
  • 라인 18 – 19: sell_price와 buy_price를 바탕으로 수익을 계산하여 profit_list에 추가합니다.

 

이제 n1, n2, m1, m2를 설정하여 전략 검증 결과를 확인하겠습니다. 먼저, n1 = 20, n2 = 20, m1 = 5, m2 = 5로 설정했습니다. 즉, 20 영업일 이전에 5% 이상 하락한 주식을 매수하여 20일 영업일 이내에 다시 5% 이상 상승할 때 매도하는 전략을 검증해보겠습니다.

1 n1 = 20; n2 = 20; m1 = 5; m2 = 5
2 profit_list = validate_strategy(sp_data_dict, n1, n2, m1, m2)
3 display(pd.Series(profit_list).describe())

기간이 너무 짧아서 그런지 평균 수익률이 음수긴 하지만 0에 가깝습니다. 중위수가 5.03%인데, 실험을 하면서 처음으로 양수인 중위수를 보는듯합니다.

이젠 기간을 조금 더 늘려보겠습니다.

1 n1 = 60; n2 = 120; m1 = 20; m2 = 10
2 profit_list = validate_strategy(sp_data_dict, n1, n2, m1, m2)
3 display(pd.Series(profit_list).describe())

물론 손실을 보는 경우도 있지만, 이익을 보는 경우가 더 늘어났음을 알 수 있습니다.

이제 조금 더 장기적으로 살펴보겠습니다. 240 영업일 전후로 보는 것이므로, 1년 전에 30% 이상 하락했던 주식을 매수했다가 1년 내에 매도하는 것이라 볼 수 있습니다.

1 n1 = 240; n2 = 240; m1 = 30; m2 = 30
2 profit_list = validate_strategy(sp_data_dict, n1, n2, m1, m2)
3 display(pd.Series(profit_list).describe())

손해를 보는 경우도 당연히 있지만, 중위수가 30까지 올라왔습니다. 여기서는 모든 종목을 대상으로 했지만, 우량주를 대상으로 하면 더 큰 수익률이 나올 것이라 예상할 수 있습니다.

 

 

수집하고 싶은 금융 데이터나 실험하고 싶은 퀀트 관련 아이디어가 있으면 댓글로 남겨주세요! 
관련 포스팅을 준비하도록 하겠습니다!

 

Comments