매월적립식 미국주식(ETF) 투자, 수익률을 더 올릴 방법은 없을까?
본문 바로가기
파이썬(Python)/파이썬으로 투자실험

매월적립식 미국주식(ETF) 투자, 수익률을 더 올릴 방법은 없을까?

by Squat Lee 2024. 4. 3.

지난 포스트에서는 미국주식(ETF)의 매월적립식 투자 백테스트 결과를 보았습니다.

 

2024.04.01 - [파이썬(Python)/파이썬으로 투자실험] - 미국주식(ETF) 적립식 투자가 정답일까?

 

 

생각보다 수익률이 좋지 못했습니다. 그래서 정적자산분배로 백테스트를 해 보았지만 결과는 참담했습니다.

 

매월적립식 투자 + 정적자산분배(5:5)

 

SPY+TLT 월별적립식(정적자산분배)

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
SPY 지수 10% -51.5% 516%
SPY+TLT 적립식투자 4% -27.4% 112%

 

추가금을 수익으로 고려하지 않고 구한 CAGR 수치입니다.

QQQ+GLD 월별적립식(정적자산분배)

 

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
QQQ 지수 14.7% -51.2% 1249%
QQQ+ GLD 적립식투자 6.9% -27.3% 253%

※ 추가금을 수익으로 고려하지 않고 구한 CAGR 입니다.

 

매월 적립식 + 동적자산배분 투자

그래서 매월 적립식으로 투자하되 월별로 리밸런싱을 하고, 비율은 평균모멘텀스코어를 이용하면 어떨까 하는 생각이 들었습니다.

 

조건은 앞서 했던 백테스트와 동일하게 2005년부터 2024년 2월 29일까지 기간으로 수수료, 세금, 배당금은 고려하지 않고 수행했습니다. 초기자금 $1,000와 매월 $1,000를 추가 투자하고 월별로 리밸런싱을 수행합니다.

 

자산배분 비율은 평균모멘텀스코어를 이용하고, 주식성 ETF의 추세에 따라서 투자비율을 결정하게 됩니다.

 

우선 SPY+TLT를 테스트 해 보겠습니다.

SPY+TLT 월별적립식 동적자산배분 투자

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
SPY 지수 10% -51.5% 516%
SPY+TLT 적립식투자 5.3% -36.7% 164%

※ 추가금을 수익으로 고려하지 않고 구한 CAGR 입니다.

 

정적자산투자 보다 CAGR은 올랐으며(4 -> 5.3) MDD는 오히려 나빠졌습니다.( 27.4 -> 36.7)  그래도 총 투자자산 대비 수익률은 112 -> 164로 증가했네요.

 

이번에는 QQQ+GLD 조건으로 보겠습니다.

 

QQQ+GLD 월별적립식 동적자산배분 투자

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
QQQ 지수 14.7% -51.2% 1249%
QQQ 월별적립식 투자 10.3% -43% 540%
QQQ+ GLD 적립식투자
(정적자산분배)
6.9% -27.3% 253%
QQQ+ GLD 적립식투자
(동적자산분배)
9.3% -31.9% 445%

※ 추가금을 수익으로 고려하지 않고 구한 CAGR 입니다.

 

테스트 결과는 더 양호해졌습니다. 

 

당현히 정적자산분배에 비교해서 수익률이 올라가고, 월별적립식투자만 할때보다 MDD는 줄었습니다.

 

이정도 결과는 상당히 고무적이네요.

 

하지만 여기서 만족할 수는 없겠죠? 더 좋은 방법이 없을까요?

 

 

월적립식 투자 + 동적자산배분투자 + 동적투자금투입

 

평균모멘텀투자는 주식이 올라갈때는 계속 올라가고 떨어질때는 계속 떨어진다는 주가의 관성을 이용한 투자방법입니다.

 

그래서 추가투자금액도 매월 투자하지 말고 주가가 올라갈때 투자하면 어떻게 될까요? 

 

조건은 앞서 했던 백테스트와 동일하게 2005년부터 2024년 2월 29일까지 기간으로 수수료, 세금, 배당금은 고려하지 않고 수행했습니다. 초기자금 $1,000와 매월 $1,000를 추가 투자하고 월별로 리밸런싱을 수행합니다.

 

자산배분 비율은 평균모멘텀스코어를 이용하고, 주식성 ETF의 추세에 따라서 투자비율을 결정하게 됩니다.

 

그리고 투자금액은 평균모멘텀스코어가 0 이상일때만 투자하겠습니다.  투자자산의 합계가 오를때 투자하는 조건을 추가하겠습니다. 마지막으로 투자하지 않고 대기하고 있는 자산은 RP에 투자한다고 가정하고 월 3%/12의 이자를 받는다고 가정하겠습니다.

 

어렵죠? 저도 어려워요. 안 좋은 머리 폭발할뻔 했습니다.

월정립식 투자 + 동적자산배분 + 동적추가금액 투입

 

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
QQQ 지수 14.7% -51.2% 1249%
QQQ 월별적립식 투자 10.3% -43% 540%
QQQ+ GLD 적립식투자
(정적자산분배)
6.9% -27.3% 253%
QQQ+ GLD 적립식투자
(동적자산분배)
9.3% -31.9% 445%
QQQ+ GLD 적립식투자
(동적자산분배+동적투자금액)
10.9% -37.3% 610%

※ 추가금을 수익으로 고려하지 않고 구한 CAGR 입니다.

 

상당한 노력끝에 고무적인 결과가 나왔어요.

 

QQQ 단독지수를 제외한 CAGR (10.9%) 이 가장 높게 나왔습니다. MDD는 월별적립식 투자보다 낮아졌습니다. 그래도 여전히 높은감은 있습니다. 그래도 달리 방법이 없으니 이정도에 만족하려고 합니다.

 

아무래도 투자하지 않는 자산을 연 3% RP라도 투자를 해서 이렇게 된 것 같습니다.

 

전체적인 코드는 아래와 같습니다.

 

그런데 말입니다. 동적자산배분 투자는 최초 투자금액에 따라서 수익률이 달라지는 것 알고 계신가요?

 

다음 포스트에서는 초기투자 비율을 조절해서 투자하는 백테스트를 진행해 보도록 하겠습니다.

 

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

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

p1 = 'QQQ'
p2 = 'GLD'

#초기 투입자금
p1자금 = 500
p2자금 = 500
현금분배여부 = 0 #1이면 현금분배 0이면 현금분배하지 않음
이율 = 0.03

#추가자금
추가자금 = 1000

#평균모멘텀스코어 기간
moment_months = 3
start = datetime(2005, 1, 1)
end = datetime(2024, 2, 29)

def get_etf_data(start, end, p):
    if p[-2:] == 'KS': #한국 ETF를 달러로 계산
        df_ex = pdr.get_data_yahoo('USDKRW=X', start, end)  # 환율데이터 가져오기
        df_ko = pdr.get_data_yahoo(p, start, end)
        df = pd.merge(df_ko['Adj Close'], df_ex['Adj Close'], left_index=True, right_index=True)
        df.columns = ['국내ETF', '환율']
        df['Adj Close'] = df['국내ETF'] / df['환율']
    else:
        df = pdr.get_data_yahoo(p, start, end)
    return df

df1 = get_etf_data(start, end, p1)
print(df1)
df2 = get_etf_data(start, end, p2)
print(df2)
df_o = pd.merge(df1['Adj Close'], df2['Adj Close'], left_index=True, right_index=True)
df_o.columns = ['p1', 'p2']
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 = []
    li_tscore = [] #현금을 제외한 자산의 합에대한 비율
    for i in range(len(df)):
        if i < 11:
            li_score.append(0)
            li_tscore.append(0)
        else:
            tval = 0
            val = 0
            for m in range(1, mm+1):
                if (df['p1'].iloc[i]+df['p2'].iloc[i]) - (df['p1'].iloc[i-1]+df['p2'].iloc[i-1]) > 0:
                    tval += 1
                if df['p1'].iloc[i] - df['p1'].iloc[i - m] > 0:
                    val += 1
            li_score.append(val / mm)
            li_tscore.append(tval / mm)

    df['투자모멘텀스코어'] = li_tscore
    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, p1자금, p2자금, 추가자금, 이율, 현금분배여부):
    df = score(df_d, moment_months)
    li_p1 = []
    li_p1c = [] #자산p1의 개수
    li_p2 = []
    li_p2c = [] #자산p2의 개수
    li_cash = []
    li_add = []
    원금누계 = []
    li_sum = []
    추가투입 = 0
    for i in range(len(df)):
        if i == 0:
            li_p1.append(p1자금)
            li_p1c.append(p1자금/df['p1'].iloc[i])
            li_p2.append(p2자금)
            li_p2c.append(p2자금/df['p2'].iloc[i])
            li_cash.append(0)
            li_add.append(0)
            원금누계.append(p1자금+p2자금)
            li_sum.append(li_p1[i]+li_p2[i])
        else:
            if df['평균모멘텀스코어'].iloc[i] != 1 or df['투자모멘텀스코어'].iloc[i] != 1:
                li_sum.append(li_p1c[i - 1] * df['p1'].iloc[i] + li_p2c[i - 1] * df['p2'].iloc[i] + li_cash[i - 1] * (
                            1 + 이율 / 12))
                원금누계.append(원금누계[i - 1])
                추가투입 += 추가자금
                li_add.append(0)
                print(f'{df.index[i]} 추가투입 : {추가투입}')
            else:
                print(f'{df.index[i]} 추가투입 : {추가투입+추가자금}')
                이율 = (1+0.03/12)*(추가투입/추가자금)
                li_sum.append(li_p1c[i-1]*df['p1'].iloc[i] + li_p2c[i-1]*df['p2'].iloc[i] + li_cash[i-1]*(1+이율/12)+
                              추가투입+추가자금*이율)
                원금누계.append(원금누계[i - 1] + 추가투입 + 추가자금)
                li_add.append(추가투입+추가자금)
                추가투입 = 0

            li_cash.append(li_sum[i]*(1-df['투자모멘텀스코어'].iloc[i])*현금분배여부)
            li_p1.append((li_sum[i]-li_cash[i])*df['평균모멘텀스코어'].iloc[i])
            li_p2.append(li_sum[i]-li_p1[i]-li_cash[i])
            li_p1c.append(li_p1[i]/df['p1'].iloc[i])
            li_p2c.append(li_p2[i]/df['p2'].iloc[i])

    df['자산1'] = li_p1
    df['자산2'] = li_p2
    df['현금'] = li_cash
    df['원금누계'] = 원금누계
    df['합계'] = li_sum
    df['자산1 개수'] = li_p1c
    df['자산2 개수'] = li_p2c
    df['추가투입자금'] = li_add

    # 월말 데이터를 일별 데이터로 만들기
    df = pd.merge(df_o[['p1', 'p2']], df[['투자모멘텀스코어', '평균모멘텀스코어', '자산1', '자산2', '합계', '자산1 개수', '자산2 개수', '현금', '원금누계', '추가투입자금']],
                  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['자산1 개수'].iloc[s] = df['자산1 개수'].iloc[s-1]
            df['자산2 개수'].iloc[s] = df['자산2 개수'].iloc[s-1]
            df['자산1'].iloc[s] = df['p1'].iloc[s] * df['자산1 개수'].iloc[s]
            df['자산2'].iloc[s] = df['p2'].iloc[s] * df['자산2 개수'].iloc[s]
            df['현금'].iloc[s] = df['현금'].iloc[s-1]
            df['원금누계'].iloc[s] = df['원금누계'].iloc[s-1]
            df['합계'].iloc[s] = df['자산1'].iloc[s] + df['자산2'].iloc[s] + df['현금'].iloc[s]
            df['투자모멘텀스코어'].iloc[s] = df['투자모멘텀스코어'].iloc[s-1]
            df['평균모멘텀스코어'].iloc[s] = df['평균모멘텀스코어'].iloc[s-1]
            df['추가투입자금'].iloc[s] = 0

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

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

    #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)

    ETF수익률 = (df['p1'].iloc[-1] / df['p1'].iloc[0] - 1)*100
    전략수익률 = (df['합계'].iloc[-1] / df['원금누계'].iloc[-1] - 1)*100

    print(f'ETF 수익률 : {round(ETF수익률, 2)}')
    print(f'전략 수익률 : {round(전략수익률, 2)}')

    #엑셀파일로 만들기
    df.to_excel(f'평균모멘텀스코어 백테스트({p1}, {p2}, 추가자금 {round(추가자금/(p1자금+p2자금)*100,2)}% CAGR '
                f'{round(cagr_전략*100,1)} 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, p1자금, p2자금, 추가자금, 이율, 현금분배여부)

 

 

728x90
반응형

댓글