평균모멘텀스코어 개월 수에 따른 CAGR과 MDD 분석
본문 바로가기
파이썬(Python)/파이썬으로 투자실험

평균모멘텀스코어 개월 수에 따른 CAGR과 MDD 분석

by Squat Lee 2024. 3. 13.

평균모멘텀스코어는 일종의 추세추종 투자방법입니다.

 

지난번 포스트까지는 매월 주가를 비교해서 주가가 오르면 1을 부여하고, 반대로 주가가 떨어지면 0을 부여했습니다. 그리고 12개월치의 평균을 내서 그 비율만큼 주식에 투자를 하는 방법으로 백테스트를 하였습니다.

 

그런데 궁금증이 들더라구요. 12개월 평균이 최선일까 하는 의구심이 들었습니다.

 

그래서 개월수(1개월 ~ 12개월)에 따른 CAGR과 MDD 를 분석하였습니다.

 

리밸런싱 주기는 1개월이고, 세금, 수수료, 배당금은 고려하지 않았습니다.

 

SPY vs TLT 와 QQQ vs GLD를 분석해 보았습니다.

(위와같이 종목을 선정한 특별한 이유는 없습니다. 무난한 종목을 선택하였습니다.)

 

 

SPY vs TLT

개월수 1 2 3 4 5 6 7 8 9 10 11 12
 전략
CAGR
9.2 9.3 9.9 10.1 10.4 10.1 10.3 10.2 9.9 10.0 9.9 9.9
전략
MDD
-44.2 -40.4 -39.1 -38.9 -37.7 -37.8 -37.0 -36.4 -36.0 -35.6 -35.3 -34.9

※ SPY단독의 CAGR은 10.2%, MDD는 -51.5%

 

전체적으로 대동소이하게 결과값이 나오지만 1개월보다 5개월이 여러모로 유리하다는 점을 확인할 수 있습니다.

 

SPY 단독으로 MDD가 -51.5%인데 반해서 SPY와 TLT를 배분해서 투자하니 MDD가 -30%대로 떨어진 것을 확인할 수 있습니다. 서로다른 자산에 분산해서 리밸런싱을 하면 효과가 있다는 사실을 확인할 수 있습니다.

1개월 모멘텀 평균에 따른 SPY 단독 투자 vs SPY와 TLT의 전략투자

 

5개월 모멘텀 평균에 따른 SPY 단독 투자 vs SPY와 TLT의 전략투자

 

다른 개월수에 비해 5개월 모멘텀은 전략곡선이 SPY 단독으로 투자할 때 보다 항상 위에 위치해 있습니다. 즉, 수익률이 SPY 단독으로 할 때보다 더 높다는 사실을 확인할 수 있습니다.

12개월 모멘텀 평균에 따른 SPY 단독 투자 vs SPY와 TLT의 전략투자

 

 

 

이번에는 QQQ와 GLD를 가지고 백테스트 해 보겠습니다.

QQQ vs GLD

개월수 1 2 3 4 5 6 7 8 9 10 11 12
 전략
CAGR
11.8 12.7 14.7 14.0 13.9 13.5 13.3 13.3 13.1 13.1 13.1 13.1
전략
MDD
-37.9 41.5 -38.9 -36.7 -36.1 -36.0 -36.9 -35.9 -36.7 -38.1 -39.3 -39.4

※ QQQ단독의 CAGR은 13.8%, MDD는 -51.2%

 

QQQ는 SPY와 비교해서 MDD는 비슷하지만 CAGR은 더 높으다는 사실을 확인할 수 있습니다.

 

MDD 는 QQQ 단독투자와 비교해서 확실히 낮아졌고, 3~5개월은 CAGR이  QQQ 단독투자보다 더 높으다는 사실을 확인할 수 있습니다.

1개월 모멘텀 평균에 따른 QQQ 단독 투자 vs QQQ와 GLD의 전략투자

 

3개월 모멘텀 평균에 따른 QQQ 단독 투자 vs QQQ와 GLD의 전략투자

 

12개월 모멘텀 평균에 따른 QQQ 단독 투자 vs QQQ와 GLD의 전략투자

 

결론

하나의 자산만 투자하는 것 보다 반대되는 자산을 함께 투자하는 것이 MDD가 낮아진다는 사실을 확인할 수 있었습니다. 또한 추세추종 개월수를 자산별로 조절하면 단독투자 보다 더 높은 수익률(CAGR)을 기대할 수 있습니다.

 

이번 포스트는 여기까지고 전체 코드는 아래와 같습니다.

import pandas as pd
from pandas_datareader import data as pdr
import yfinance as yf
yf.pdr_override()
import matplotlib.pyplot as plt

pd.options.display.float_format = '{:,.2f}'.format

p1 = 'QQQ'
p2 = 'GLD'
현금비율 = 0 #현금비율

#초기 투입자금
주식자금 = 500
채권자금 = 500
현금자금 = 0

#평균모멘텀스코어 기간
moment_months = 12

df1 = pdr.get_data_yahoo(p1)
print(df1)
df2 = pdr.get_data_yahoo(p2)
print(df2)

df_o = pd.DataFrame({'p1': df1['Adj Close'], 'p2' : df2['Adj Close']})
df_o.set_index(df1.index)
df_o.dropna(inplace=True)

# 월말 날짜만 가져오기
def get_monthly_end(df_d):
    df_d['Odate'] = df_d.index
    df = df_d.groupby(by=[df_d.index.year, df_d.index.month]).last()
    df.set_index('Odate', inplace=True)
    return df

#평균모멘텀스코어 구하기
def score(df_d, moment_months):
    mm = int(moment_months)
    df = get_monthly_end(df_d)
    li_score = []
    for i in range(len(df)):
        if i < 11:
            li_score.append(0)
        else:
            val = 0
            for m in range(1, mm+1):
                if df['p1'].iloc[i] - df['p1'].iloc[i - m] > 0:
                    val += 1
            li_score.append(val / mm)

    df['평균모멘텀스코어'] = li_score
    return df

#MDD 구하기
def get_mdd(col):
    window = 252 #1년간 영업일을 252일로 가정
    peak = col.rolling(window, min_periods=1).max() #기간 동안에 최고 주가(결과값은 시리즈로 나옴)
    drawdown = col/peak - 1 #최고치 대비 현재 주가가 얼마나 하락했는지 구함
    연도별mdd = drawdown.rolling(window, min_periods=1).min()#"최고치-현재주가" 중 가장 작은값을 구함
    mdd = 연도별mdd.min()
    return mdd

#백테스트
def back_test(df_d, moment_months, 현금비율, 주식자금, 채권자금, 현금자금):
    df = score(df_d, moment_months)
    주식 = []
    주식수 = []
    채권 = []
    채권수 = []
    현금 = []
    합계 = []
    for i in range(len(df)):
        if i == 0:
            주식.append(주식자금)
            주식수.append(주식자금/df['p1'].iloc[i])
            채권.append(채권자금)
            채권수.append(채권자금/df['p2'].iloc[i])
            현금.append(현금자금)
            합계.append(주식[i]+채권[i]+현금[i])
        else:
            합계.append(주식수[i-1]*df['p1'].iloc[i] + 채권수[i-1]*df['p2'].iloc[i] + 현금[i-1])
            주식.append(합계[i]*(1-현금비율)*df['평균모멘텀스코어'].iloc[i])
            현금.append(합계[i]*현금비율)
            채권.append(합계[i]-주식[i]-현금[i])
            주식수.append(주식[i]/df['p1'].iloc[i])
            채권수.append(채권[i]/df['p2'].iloc[i])

    df['주식'] = 주식
    df['채권'] = 채권
    df['현금'] = 현금
    df['합계'] = 합계
    df['주식수'] = 주식수
    df['채권수'] = 채권수

    # 월말 데이터를 일별 데이터로 만들기
    df = pd.merge(df_o[['p1', 'p2']], df[['평균모멘텀스코어', '주식', '채권', '현금', '합계', '주식수', '채권수']],
                  left_index=True, right_index=True, how='outer')
    df.fillna(0, inplace=True)

    for s in range(1, len(df)):
        if df['합계'].iloc[s] == 0:
            df['주식수'].iloc[s] = df['주식수'].iloc[s-1]
            df['채권수'].iloc[s] = df['채권수'].iloc[s-1]
            df['주식'].iloc[s] = df['p1'].iloc[s] * df['주식수'].iloc[s]
            df['채권'].iloc[s] = df['p2'].iloc[s] * df['채권수'].iloc[s]
            df['현금'].iloc[s] = df['현금'].iloc[s-1]
            df['합계'].iloc[s] = df['주식'].iloc[s] + df['채권'].iloc[s] + df['현금'].iloc[s]
            df['평균모멘텀스코어'].iloc[s] = df['평균모멘텀스코어'].iloc[s-1]

    # 합계열에서 0이 아닌 숫자가 나오면 행삭제
    df.drop(df[df['합계']==0].index, inplace=True)

    # 2가지 자산의 누적수익률을 비교하기 위해 백분율로 표현
    df['주가백분율'] = df['p1'] / df['p1'].iloc[0]
    df['전략백분율'] = df['합계'] / df['합계'].iloc[0]

    #CAGR
    diff = df.index[-1].year - df.index[0].year
    cagr_etf = (df['주가백분율'].iloc[-1] / df['주가백분율'].iloc[0]) ** (1 / diff) - 1
    cagr_전략 = (df['전략백분율'].iloc[-1] / df['전략백분율'].iloc[0]) ** (1 / diff) - 1
    cagr = f'CAGR(ETF) : {round(cagr_etf, 3)} / CAGR(전략) : {round(cagr_전략, 3)}'
    print(cagr)

    #MDD구하기
    mdd_etf = get_mdd(df['p1'])
    mdd_전략 = get_mdd(df['합계'])
    mdd_t = f'MDD(ETF) : {round(mdd_etf,3)} / MDD(전략) : {round(mdd_전략,3)}'
    print(mdd_t)

    #엑셀파일로 만들기
    # df.to_excel(f'평균모멘텀스코어 백테스트({p1}, {p2}, CAGR {round(cagr_전략*100,1)} '
    #             f'MDD {round(mdd_전략*100,1)}).xlsx')

    #그래프로 표현하기
    plt.rcParams['figure.figsize'] = (16, 9)

    plt.plot(df.index, df['주가백분율'], color='blue', label=p1)
    plt.plot(df.index, df['전략백분율'], color='red', label='Ave_momentum')
    plt.grid(True)
    plt.legend(loc='best')

    plt.title(f'{p1} vs Average_Momentum({p1}, {p2})_{moment_months} Months')

    plt.show()

    return df

back_test(df_o, moment_months, 현금비율, 주식자금, 채권자금, 현금자금)
728x90
반응형

댓글