SPY & TLT 5:5 투자와 평균모멘텀스코어로 투자 시 CAGR 및 MDD 비교(정적 배분 투자 vs 동적 배분 투자)
본문 바로가기
파이썬(Python)/파이썬으로 투자실험

SPY & TLT 5:5 투자와 평균모멘텀스코어로 투자 시 CAGR 및 MDD 비교(정적 배분 투자 vs 동적 배분 투자)

by Squat Lee 2024. 3. 11.

지난번에는 SPY와 현금을 평균모멘텀스코어를 통해 비중을 조절하여 투자를 해 보았습니다.

2024.03.08 - [파이썬(Python)/파이썬으로 투자실험] - 평균모멘텀스코어 백테스트(ETF & 현금 vs ETF)

 

이번 포스트에서는 SPY와 TLT를 5:5로 일정하게 투자하면서 월별 리밸런싱 하는 방법과 평균모멘텀스코어를 통해 비중을 변경하면서 투자하는 방법을 BackTest를 수행해 보겠습니다.

 

지난번 코드와 비교해서 아래 사항이 수정되었습니다.

  1. 월별 데이터에서 일별 데이터로 백테스트를 진행하였습니다.
  2. MDD를 Pandas의 rolling 함수를 써서 수정하였습니다.
  3. 주식과 채권을 투자하는 방법으로 코드를 수정하였습니다.

결론부터 보시죠

구  분 5:5 정적배분 투자 동적배분(평균모멘텀스코어) SPY단독투자
CAGR 7.97% 9.9% 10.2%
MDD -26.9% -34.9% -51.5%

 

포트폴리오 비주얼라이저 백테스트 결과 캡쳐

 

 

사실 조건이 모두 동일하지는 않습니다. 비용이나 배당금 그리고 세금에 대한 부분도 아직 반영하지 못해서 다르고 그 외에 자잘한 조건들이 차이가 날거라 생각합니다.

 

하지만, 대략적인 흐름을 보기에는 충분할 것 같습니다.

 

5:5 정적배분 투자는 포트폴리오 비주얼라이저에서 했고, 동적배분과 SPY 단독투자는 파이썬으로 백테스트 하였습니다.

 

MDD는 정적배분 투자가 가장 낮고, 단독 투자가 가장 높습니다. CAGR은 동적배분과 단독투자가 거의 비슷하고 단독투자가 가장 높습니다.

 

자산에 따라 다른데 만약 QQQ와 GLD를 섞어서 해 보면 다른 결과가 나옵니다.

 

구  분 QQQ QQQ + TLT(동적배분) QQQ + GLD(동적배분)
CAGR 15.1% 12.7% 13.1%
MDD -51.2% -41.2% -39.4%

 

개인적으로 QQQ + GLD 조합이 위 테이블 중에서 가장 좋은 것 같네요.

 SPY + TLT 조합에서 재미있는 점을 발견했습니다. 

 

2022년까지는 SPY 단독 투자보다 SPY + TLT 조합이 수익률이 더 높고 MDD는 더 낮았습니다. 그런데 코로나 이후에 채권과 주식이 같은 방향으로 움직이다 보니 지금 CAGR은 이렇게 된 것 같습니다.

 

 

QQQ+GLD 조합은 위의 그래프와 같이 나옵니다.

 

다음에는 변동성에 따른 투자비중을 조절해서 투자하는 방법과 변동성과 추세추종을 합쳐서 투자하는 앙상블 기법에 대해서 코드를 작성 후 포스트 해 보겠습니다.

 

그리고 점차 세금, 비용, 배당금 등도 반영할 방법을 궁리해 봐야 겠습니다.

 

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 = 'SPY'
p2 = 'TLT'
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):
    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, 13):
                if df['p1'].iloc[i] - df['p1'].iloc[i - m] > 0:
                    val += 1
            li_score.append(val / 12)

    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):
    df = score(df_d)
    주식 = []
    주식수 = []
    채권 = []
    채권수 = []
    합계 = []
    for i in range(len(df)):
        if i == 0:
            주식.append(500)
            주식수.append(500/df['p1'].iloc[i])
            채권.append(500)
            채권수.append(500/df['p2'].iloc[i])
            합계.append(1000)
        else:
            합계.append(주식수[i - 1] * df['p1'].iloc[i] + 채권수[i - 1] * df['p2'].iloc[i])
            주식.append(합계[i]*df['평균모멘텀스코어'].iloc[i])
            채권.append(합계[i]-주식[i])
            주식수.append(주식[i]/df['p1'].iloc[i])
            채권수.append(채권[i]/df['p2'].iloc[i])

    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] + df['채권'].iloc[s]

    # 합계열에서 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}).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})')

    plt.show()

    return df

back_test(df_o)

 

혼자 배운 코딩이라 어렵네요. 특히나 판다스를 이용해서 간결하게 코드를 짜고 싶은데 쉽지 않은 것 같습니다. 점점 나아지리라 생각합니다.

728x90
반응형

댓글