퀀트투자 - 통계적인 관점에서 백테스트 수익률 분석
본문 바로가기
파이썬으로 만든 것들/퀀트투자

퀀트투자 - 통계적인 관점에서 백테스트 수익률 분석

by Squat Lee 2022. 9. 21.

 

예전에 저PBR 과 저DPS 조합으로 퀀트투자 백테스트를 한 적이 있다.

 

저PBR X 저DBS 조합 퀀트투자 백테스트

 

그 당시에는 모든 것이 완벽할 것이라고 생각하고 바로 실행해 보았다. 막상 실제로 투자를 해 보니 여러가지 문제에 부딪히게 되었다.

 

가장 큰 부분이 멘탈관리 적인 부분이다. 원래 11월 초에 사서 1년 보유 후 수익여부와 관계없이 되파는 것이었는데, 주가의 등락이 심해서 마음을 계속 졸였다.

 

특히나 손실이 발생하고 있는 시점이나, 어느정도 수익이 발생했을 때 어떻게 할 것인지에 대한 대비가 전혀 없었다.

 

다행인지 모르겠지만, 조금의 수익만 보고 전량 매도해 버렸다. 그리고 다시 백테스트를 하고 있다.

 

내 짧은 지식으로 어떻게 해야할지 몰라서 일단 생각나는 아이디어로 코드를 짜 보았다.

 

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from pykrx import stock
import pandas as pd
import time
 
# 영업일을 List로 가져오기
def make_date_list(start, end):
    bdate_list = [] #주식거래일 리스트
    dates = pd.date_range(start=start, end=end, freq='BM'#한달 간격으로 (첫째일) 날짜 만들기. Type은 DatetimeIndex
    date_list = dates.strftime('%Y%m%d').to_list() #DatetimeIndex를 리스트로 만들기
    for date in date_list:#주식거래일 리스트로 만들기
        b_day = stock.get_nearest_business_day_in_a_week(date=date)
        bdate_list.append(b_day)
    return bdate_list
 
 
# 투자 종목 고르기
def row_pbr_dps(date):
    codes = stock.get_market_ticker_list(date, market='ALL'# code list 만들기
    corp = [] #Code와 Name을 저장할 List
    for code in codes:
        name = stock.get_market_ticker_name(code) #종목 이름 가져오기
        corp.append([code, name]) #Code와 이름으로 리스트를 만들기
    df1 = pd.DataFrame(data=corp, columns=['code''종목명'])#code와 종목명을 데이터프레임으로 만들기
    df1 = df1.set_index('code')#code를 Index로 설정하기
 
    df_f = stock.get_market_fundamental_by_ticker(date=date, market='ALL')#BPS, PER, PBR, EPS, DIV, DPS 가져와서 데이터 프레임 만들기
    df_c = stock.get_market_cap_by_ticker(date=date, market='ALL')#종가, 시가총액, 거래량, 거래대금, 상장주식수 가져오기
 
 
    time.sleep(0.1)
 
    df = pd.merge(df1, df_c, left_index=True, right_index=True#종목명, 종가, 시가총액, 거래량, 거래대금, 상장주식수
    df = pd.merge(df, df_f, left_index=True, right_index=True#위에 df + PER, PBR...
    #column은 '종목명', '종가', '시가총액', '거래량', '거래대금', '상장주식수', 'BPS', 'PER', 'PBR', 'EPS', 'DIV', 'DPS'
    df = df[df['PBR'> 0#PBR이 0이상만 구하기
    df['pbr_rank'= df['PBR'].rank()
 
    df = df[df['DPS'> 0#DPS 0이상만 구하기
    df['dps_rank'= df['DPS'].rank()
 
    df['pbr_dps'= df['pbr_rank'+ df['dps_rank']#pbr 순위와 dps 순위를 더하기
    df['pbr_dps_rank'= df['pbr_dps'].rank()#더한 PBR, DPS 순위의 순위 매기기
    df = df.sort_values(by='pbr_dps_rank'#pbr_dps_rank의 숫자가 낮은 순으로 정렬하기
 
    df = df[df['거래량'> 0#거래량이 0 이상인 종목만 구하기
    df = df.iloc[:20#20개 종목만 구하기
    df = df[['종목명''종가''상장주식수']] #컬럼이 많으면 복잡하니깐, 3개만 표시
 
    return df
 
# 리스트를 12개로 나눠서 저장하기
def devide_list(li, n):
    for i in range(0len(li), n):
        yield li[i:i + n]
 
# 테스트 하기
def pbr_dps_test(start, end):
    pro_stat = [] #년도별 수익률 리스트를 담을 변수
    date_list = make_date_list(start, end) #날짜 리스트 가져오기
    print(date_list)
    total_list = list(devide_list(date_list, 12)) #12개월씩 리스트를 나누기
 
    for i, yearly in enumerate(total_list):
        pro_ratio = [] #날짜와 수익률을 담을 변수
        for n, date in enumerate(yearly):
 
            if n == 0#첫번째 날짜 리스트는 매수
                df = row_pbr_dps(yearly[0])
                df['매수수량'= 1000000 // df['종가']
                df['매수금액'= df['매수수량'* df['종가']
                df.rename(columns={'상장주식수' : '주식수'+date}, inplace=True)
                df.rename(columns={'종가' : '주가'+date}, inplace=True)
 
 
            else# 나머지 날짜 리스트는 수익률
                dfn = stock.get_market_cap_by_ticker(date)
                dfn = dfn[['종가''상장주식수']]
                df = pd.merge(df, dfn, left_index=True, right_index=True)
                df.rename(columns={'상장주식수''주식수' + date}, inplace=True)
                df['주식수변동' + date] = df['주식수' + yearly[n]] - df['주식수' + yearly[0]]
                df.rename(columns={'종가' : '주가'+date}, inplace=True)
                df['수익' + date] = df['주가'+date]*df['매수수량'- df['매수금액']
                # 주식수가 줄어들면 주가 보정, 늘어나면 희석되니깐 보정 안함
                df['수익' + date].loc[df['주식수변동'+date] < 0= df['매수수량']*(df['주가'+date]*(1+df['주식수변동'+date]/
                                                                              df['주식수'+yearly[0]]))
 
                pro = df['수익' + date].sum()/df['매수금액'].sum() #날짜별 수익률 구하기
                pro_ratio.append([date, pro]) #날짜와 수익률을 리스트로 만들기
                df.to_excel(yearly[0][:4+ '.xlsx')  # 엑셀로 추출
        dfp = pd.DataFrame(data=pro_ratio, columns=['Date''수익률'])  # 수익률을 데이터프레임으로
        pro_min = dfp['수익률'].min() #수익률 최소값
        pro_med = dfp['수익률'].median() #수익률 중간값
        pro_max = dfp['수익률'].max() #수익률 최대값
        pro_stat.append([yearly[0][:4], pro_min, pro_med, pro_max])#년도별 최소, 중간, 최대값을 리스트로 만들기
 
    #년도별, 최소, 중간, 최대값을 데이터프레임으로 만들기
    dfps = pd.DataFrame(data=pro_stat, columns=['년도''최소수익(손실)률''중간수익(손실)률''최대수익(손실)률'])
    print(dfps)
    dfps.to_excel('연도별 수익(손실)률.xlsx')
 
 
 
 
cs

 

매월 초에 투자를 하고나서 1년동안의 수익률을 분석해 보았다. 최소값, 중간값, 최대값을 구했다.

 

 년도별로 최소, 중간, 최대수익률을 구해봤다.

 

구한 값의 최소값, 중간값, 최대값, 평균값, 25%값, 75%값을 구했다.

발생할 확률도 구해보았다.

 

5% 수익에서 만족한다면 승률이 약 90% 정도된다. 하지만, 5% 수익을 생각하고 이렇게 투자하기에는 품이 너무 많이 든다. 차라리 배당주 투자가 더 나을 수도 있다.(배당주 투자는 매수하고 아무것도 안 해도 매년 배당이 들어온다.)

 

10% 이상 수익은 84% 확률이다. 최대값 기준이지만, 10% 이상 되었을때 판다고 가정하면 10번 중 8번은 가능 하다는 것이다. 그럼 이제 고민할 부분은 투자를 하고 나서 10% 수익에 달성하지 못 했을때 어떻게 해야 하는지이다. 1년동안 기다렸지만 10% 수익은 달성하지 못하고 결국 손실로 매도할 경우도 있다. 그러면 나 스스로 5% 정도 되었을때 팔걸 하는 아쉬움이 클 것이다.

 

그리고 손실이 날 경우도 생각해야 된다. 손실 후 이익으로 회복되는 경우도 있지만, 계속 시장상황이 좋지 않은 경우 추가 매수를 하는 방법이 있다. 하지만, 손실이 얼마나 났을때 해야 하는가? 다행히 손실이 날 확률은 이익이 날 확률보다 낮아보인다. -20%와 -30%에 각각 분할로 추가 매수를 하면 될 것같다. 하지만, 이건 단순히 감이다.

 

손실로 인한 추가매수를 할 때 매수금액 비중은 어떻게 해야 하는지도 고민해 봐야 하고, 실제로 그렇게 하면 수익률이 어떻게 되는지도 확인해 볼 필요가 있다. 최초 매수때 전체 투자금액의 70%를 매수하고, -20% 때 추가매수 20%, -30%때 추가매수 10% 하는 방법도 있겠다. 

 

할 일이 많다. 그런데 데이터를 가져오는 krx 사이트에서 차단이 되어 버렸다.

 

진작부터 데이터를 로컬PC에 저장해야지 하는 생각이 들었지만 여태껏 귀찮아서 하지 않았다. 나중에 아예 막아버리기 전에 미리 데이터를 DB에 저장해야겠다. 

 

728x90
반응형

댓글