퀀트투자(PBR x DPS) 백테스트 업그레이드230928
본문 바로가기
파이썬으로 만든 것들/퀀트투자 업그레이드

퀀트투자(PBR x DPS) 백테스트 업그레이드230928

by Squat Lee 2023. 9. 28.

예전에 백테스트 한 퀀트투자를 다시 해 보았다.

 

1년이 넘어서 그런지 그 동안 프로그래밍 실력도 나아지고, 보는 눈도 좀 더 좋아진 것 같다.

 

조건은 KRX Data를 가져와서 저PBR x 저DPS 조합으로 20개씩 동일가중으로 매수해서 1년 후 매도하는 전략이다.

 

어차피 현실에서는 많은 변수가 있으니 날짜 부분은 매월 첫째일에 투자해서 1년 후 대충 11번째 날에 매도하는 방법으로 했다.

 

우선 결과는 아래와 같다.

 

 

지난번 테스트에는 매년 2월과 11월 초에 투자하는 것이 유리하다고 생각했다. 하지만, 손실이 나는 해의 개수를 비교해보니 2월과 12월이 2번으로 가장 적었다. 

 

초기에 1천만원 투자금으로 추가 불입없이 19년간 투자를 진행하는 시물레이션이다. 19년 후에는 2월 12월 각각 원금대비 187배, 131배로 늘어난다. 

 

이런 투자를 하게되면 손실이 나는 해가 가장 문제이다. 이 문제를 해결하기 위해 여러 고민을 해 봤지만,  2개의 계좌를 분리해서 시기를 다르게 투자하는 방법이 내 입장에서는 꾸준히 할 수 있고 현실적인 방법이라고 생각이 된다.

 

그래서 2월과 12월 각각 1천만원씩 투자를 했을때는 CAGR이 기존(31.6%, 29.3%)에서 33.7%로 높아지고, 기존 원금대비  늘어난 자산 (187배, 131배) 이  254배로 늘어나게 된다. 이 투자 방법은 분산투자가 오히려 수익률을 높이고 손실을 줄인다는 사실을 확인할 수 있었다.

 

당연히 앞으로는 어떻게 될 지 모른다. 과거는 과거의 일이고 미래는 아무도 모르는 것이 당연하다.

 

이렇게 백테스트를 하고도 당장 2년 또는 3년 연속으로 손실이 날 수도 있다. 이런것이 퀀트투자의 가장 어려운 부분이다. 그렇기 때문에 사람들이 이 투자 방법을 지속하지 못하는 것이다.

 

예전에는 잘 몰랐다. 확실한 백테스트가 미래를 보장한다고 생각했었다. 지금은 다르다. 세상에 100% 확실한 투자법은 없다고 생각한다. 하지만, 다른 투자 방법보다 조금이라도 장점이 더 있다면 해 보는것이 맞다고 생각한다. 그리고 손실도 과정의 일부로 받아들여야 한다고 생각한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import sqlite3
import pandas as pd
 
con = sqlite3.connect('krx_data.db')
 
li_m = ['01''02''03''04''05''06''07''08''09''10''11''12']
li_result = []
 
for Y in range(20032022):
    for M in li_m:
        # 해당 년,월에 해당하는 데이터 가져와서 날짜만 뽑기
        df_date = pd.read_sql("SELECT 일자 FROM fundamental WHERE 일자 LIKE '" + str(Y) + M + "%'", con)
        li_date_before = df_date['일자'].tolist() #일자를 list형식으로 봅기
 
        #중복된 날짜를 제거
        li_date = []
        for d in li_date_before:
            if d not in li_date:
                li_date.append(d)
        for date in li_date:
            print(date)
            # 해당일자에 해당하는 데이터만 가져오기
            df = pd.read_sql("SELECT 일자, code, 종목명, 종가, 거래량, 상장주식수, PBR, DPS FROM fundamental "
                             "WHERE 일자= " + date, con)
            df = df[(df['거래량'> 0& (df['PBR'> 0& (df['DPS'> 0)] #거래량, PBR, DPS가 모두 0 이상인 종목만 가져오기
 
            df['PBR_RANK'= df['PBR'].rank() #낮은 순서대로 순위매기기
            df['DPS_RANK'= df['DPS'].rank() #낮은 순서대로 순위매기기
            df['PBR_DPS'= df['PBR_RANK'+ df['DPS_RANK'#순위매긴걸 더하기
            df['PBR_DPS_RANK'= df['PBR_DPS'].rank() #최종순위 구하기
            df.sort_values(by='PBR_DPS_RANK', inplace=True#낮은 순서대로 정렬하기
 
            df = df.iloc[:20#20개만 고르기
 
            # 대략적으로 1년 후에 날짜 구하기
            date_later = pd.read_sql("SELECT 일자 FROM fundamental WHERE 일자 LIKE '" + str(Y+1+ M + "%'", con)
            date_later = date_later['일자'].iloc[10#대략 11번째 날짜로 정하기
            # 정한 날짜로 데이터 가져오기
            df_later = pd.read_sql("SELECT 일자, code, 종가, 상장주식수 FROM fundamental WHERE 일자= " + date_later, con)
            df_later.columns = ['1년후_일자''code''1년후_종가''1년후_상장주식수']
 
            df = pd.merge(df, df_later, on='code')
 
            df['수익률'= ((df['1년후_종가'- df['종가']) / df['종가']) * (df['1년후_상장주식수']/df['상장주식수'])
 
            profit = df['수익률'].mean()
            li_result.append([date, profit])
 
            if date == '20030103'#첫번째 날짜는 만들어진 dataframe이 df_t로 저장되고, 나머지는 계속 concat로 붙여넣기
                df_t = df
            else:
                df_t = pd.concat([df_t, df])
 
df_result = pd.DataFrame(data=li_result, columns=['투자년도''수익률']) #투자년도와 수익률로 데이터프레임 만들기
print(df_result)
 
df_t.to_excel('PBR_DPS 백테스트기록(2003-2022)_모든일자.xlsx')
df_result.to_excel('PBR_DPS 백테스트 년도별 수익률(2003-2022)_모든일자.xlsx')
 
cs

위는 백테스트를 한 코드이다. 

 

예전에는 pykrx 모듈을 사용해서 일일이 웹에서 크롤링한 데이터로 백테스트를 했는데, 생각보다 시간이 오래 걸렸다. 지금은 KRX 자체에서 제한을 둔건지 2년 단위가 넘어서는 데이터는 가져올 수가 없다. 그래서 1년 단위로 반복적으로 데이터를 불러온 후 사용해야 한다.

 

그래서 이번에는 DB에 데이터를 미리 다운받은 후 테스트를 했다. DB를 다운로드 받는 시간은 생각보다 상당히 길다. 2003년부터 데이터를 받아보니 하루가 걸린 것 같다. 아마 내 컴퓨터 성능도 일정부분 작용했으리라 생각이 된다.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
from datetime import datetime
 
# 기존 백테스트 결과 가져오기
df = pd.read_excel('PBR_DPS 백테스트 년도별 수익률(2003-2022)_모든일자.xlsx')
 
# 가져오면 필요없는 Column이 생기는데 삭제
df = df.iloc[: ,1:]
 
# 투자년도 Column의 데이터가 이상해서 변경해 주어야 함
df['투자년도'= df['투자년도'].astype(str)
df['투자년도'= pd.to_datetime(df['투자년도'])
df['투자년도'= df['투자년도'].apply(lambda x: datetime.strftime(x, '%Y%m%d'))
print(df)
 
# 월별 첫째날의 데이터 가져오기
df = df.groupby(df['투자년도'].str[:6]).first()
 
# 엑셀에서 후속작업을 편하게 하기 위해 투자년과 투자월을 각각 컬럼으로 만들기
df['투자월'= df.index.str[4:6]
df['투자년'= df.index.str[:4]
 
print(df)
 
df.to_excel('월별수익률(PBRXDPS)230928.xlsx')
cs

일별로 투자한 기록이 엑셀에 저장이 되니 월별로 수익률을 나타내려면 추가 코딩작업이 필요하다.

 

월별로 그룹을 나눈 다음에 매월 첫째날의 데이터만 가져오도록 코드를 작성하고 엑셀로 추출하였다.

퀀트투자 수익률 vs KOSP PBR INDEX

최근 읽은 책에서 종목이 20~30개가 되면 각 개별의 종목 수익률 보다 시장 전체의 영향을 더 많이 받는다는 구절을 읽었다.

 

그래서 MDD를 조금더 낮추기 위해 KOSPI PBR INDEX와 수익률을 일별로 비교해 보았다.

 

언듯 보기에는 서로 반대로 움직인다고 생각이 된다.

 

KPSPI PBR INDEX 비교

KOSP PBR INDEX를 대입해보니 연속성은 다소 떨어지는 것 같다.

 

매년 2월 투자에서 손실이 난 해의 PBR은 각각 1.51, 0.94였지만, 이것 보다 더 높은 PBR에도 수익이 다수 발생했다.

 

단순히 PBR 수치만으로 투자 여부나 비중조절을 하게되면 오히려 전체 누적수익률이 줄어들게 될 수도 있겠다.

 

월별수익률(PBRXDPS)230928.xlsx
0.04MB

 

KOSPI PBR과 비교하는 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import pandas as pd
import sqlite3
from datetime import datetime
import matplotlib.pyplot as plt
 
con = sqlite3.connect('krx_data.db')
df = pd.read_excel('PBR_DPS 백테스트 년도별 수익률(2003-2022)_모든일자.xlsx')
df['투자년도'= df['투자년도'].astype(str)
 
df = df.set_index('투자년도')
df = df.iloc[:, 1:]
 
df_per = pd.read_sql('SELECT 날짜, PBR FROM kospi_index', con)
 
df_per.rename(columns={'날짜':'투자년도'}, inplace=True)
df_per['투자년도'= pd.to_datetime(df_per['투자년도'])
df_per['투자년도'= df_per['투자년도'].apply(lambda x: datetime.strftime(x, '%Y%m%d'))
 
df_per = df_per.set_index('투자년도')
 
df = pd.merge(df, df_per, left_index=True, right_index=True)
df.index = pd.to_datetime(df.index)
print(df)
 
plt.rcParams['figure.figsize'= (169)
 
fig, ax1 = plt.subplots()
ax1.set_xlabel('DATE')
ax1.set_ylabel('PBR')
ax1.plot(df.index, df['PBR'], color='blue', label='KOSPI PBR')
ax1.legend(loc='upper right')
 
ax2 = plt.twinx()
ax2.set_ylabel('Yield')
ax2.plot(df.index, df['수익률'], color='red', label='Yield')
ax2.legend(loc='lower right')
 
plt.grid(True)
 
plt.show()
 
df['일자'= df.index
df['YEAR'= df['일자'].dt.year
df['MONTH'= df['일자'].dt.month
df['DAY'= df['일자'].dt.day
 
# 월별 손실일수
df_minus = df[df['수익률']<0].groupby(by='MONTH').count() / df.groupby(by='MONTH').count()
df_minus = df_minus['수익률']
 
# 월별 최대수익
df_max = df.groupby(by='MONTH').max()
df_max = df_max['수익률']
 
# 월별 최대손실
df_min = df.groupby(by='MONTH').min()
df_min = df_min['수익률']
 
# 월별 수익평균
df_mean = df.groupby(by='MONTH').mean()
 
# 월별 수익 중앙값
df_mid = df.groupby(by='MONTH').median()
 
# 월별 손실일수, 최대수익, 최대손실
df_analy = pd.merge(df_minus, df_max, left_index=True, right_index=True)
df_analy = pd.merge(df_analy, df_min, left_index=True, right_index=True)
df_analy = pd.merge(df_analy, df_mean['수익률'], left_index=True, right_index=True)
df_analy = pd.merge(df_analy, df_mid['수익률'], left_index=True, right_index=True)
df_analy.columns = ['월별손실일수''월별최대수익''월별최대손실''월별수익평균''월별수익중앙값']
print(df_analy)
 
 
# print(df[df['수익률']<0].groupby(by='MONTH').count())
 
# df.to_excel('DPS_PBR_KOSPIPBR연관분석.xlsx')
 
df_analy.to_excel('월별 통계값230928.xlsx')
 
cs

 

위의 코드에서 월별 통계값도 구해보았다. 아래가 결과이다.

퀀트투자 월별 통계값

여기서도 월별 손실일수가 가장 낮은 달이 2월과 12월임을 알 수있다.

 

월별 최대수익은 2월과 10월에 나왔지만, 10월의 손실일수가 많아서 2월과 12월에 투자하는 것이 여러모로 마음이 편할 것 같다. 해 보지는 않았지만, 누적수익률도 아마 10월 보다는 12월이 더 높을 것이다.

 

평균수익률도 2월이 가장 높고 11월이 2번째로 높았다. 12월은 4번째다. 월별수익중앙값도 2월이 가장 높고, 11월은 4번째이다. 평균값과 중앙값은 2번째 ~ 4번째의 차이가 적다. 손실율을 따지면 12월에 투자하는 것이 합리적이다.

 

퀀트투자 수익류과 KPSPI PBR INDEX 회귀분석

내가 만든 퀀트투자 수익류과 KOSPI PBR 간에 분명한 상관관계가 존재할 것 같아서 회귀분석을 해 보았다.

 

위의 그래프에서 보는 것 처럼 R=-0.3으로 반대로 움직인다는 것을 확인할 수 있다.

 

하지만, 여기까지 내 한계인가 보다. 이것을 어떻게 활용할지가 생각나지 않는다.

 

단순히 PBR 수치만으로 투자 비중이나 투자여부를 결정하기에 PBR 수치와 수익률 간의 관계가 일정하지 않다.

 

하긴, 투자가 산수처럼 딱 떨어지는 공식이 있다면 누구나 부자가 될 것이다. 그렇지 않기에 어렵고 주식투자에서 돈을 버는 사람이 소수일 것이다.

 

일단, 여기까지 퀀트투자 백테스트 결과를 정리하고 PBR간의 관계는 차차 고민해야겠다.

 

아래는 위의 그래프를 그리는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import pandas as pd
import sqlite3
from datetime import datetime
import matplotlib.pyplot as plt
from scipy import stats
 
con = sqlite3.connect('krx_data.db')
df = pd.read_excel('PBR_DPS 백테스트 년도별 수익률(2003-2022)_모든일자.xlsx')
df['투자년도'= df['투자년도'].astype(str)
 
df = df.set_index('투자년도')
df = df.iloc[:, 1:]
 
df_per = pd.read_sql('SELECT 날짜, PBR FROM kospi_index', con)
 
df_per.rename(columns={'날짜':'투자년도'}, inplace=True)
df_per['투자년도'= pd.to_datetime(df_per['투자년도'])
df_per['투자년도'= df_per['투자년도'].apply(lambda x: datetime.strftime(x, '%Y%m%d'))
 
df_per = df_per.set_index('투자년도')
 
df = pd.merge(df, df_per, left_index=True, right_index=True)
df.index = pd.to_datetime(df.index)
df.dropna(inplace=True)
print(df)
 
df['X'= df['수익률']
df['Y'= df['PBR']
 
regr = stats.linregress(df.X, df.Y)
 
regr_line = f'Y = {regr.slope:.2f} * X + {regr.intercept:.2f}'
 
plt.figure(figsize=(169))
plt.plot(df.X, df.Y, '.')
plt.plot(df.X, regr.slope * df.X + regr.intercept, 'r')
plt.legend(['PBR_DPS Yield X KOSPI_PBR', regr_line])
plt.title(f'PBR_DPS Yield X KOSPI_PBR (R={regr.rvalue:.2f})')
plt.xlabel('PBR_DPS Yield')
plt.ylabel('KOSPI_PBR')
plt.show()
 
 
cs

 

기록을 하지 않으니 발전이 더딘 것 같다. 최근에 여러가지 백테스트를 많이 했지만, 기록이 없어서 뭘 만들었는지 도통 생각이 나지 않는다.

 

기록은 남을 위해 하는 것이 아니라 나를 위해 하는 것이다. 

 

나를 위해 조금만 더 부지런해지자.

728x90
반응형

댓글