미국주식(ETF) 적립식 투자가 정답일까?
본문 바로가기
파이썬(Python)/파이썬으로 투자실험

미국주식(ETF) 적립식 투자가 정답일까?

by Squat Lee 2024. 4. 1.

보통 주식(ETF) 투자를  적립식으로 적금처럼 하라고 얘기를 많이 듣습니다.

 

이렇게 하면 분할매수를 하니깐 평단가는 낮아져서 위험을 줄일 수 있다고 합니다. 또한, 장기적으로 투자를 한다면 수익을 본다고 얘기 하곤합니다.

 

과연 사실일까요? 지금부터 백테스트를 해 보겠습니다.

 

매월 적립식 투자

백테스트 기간은 2005년 1월 2일부터 2024년 2월 29일까지 진행했습니다. 세금, 배당금, 수수료는 고려하지 않았습니다.

 

 S&P 500을 추종하는 SPY를 투자한다고 가정하고 백테스틀 진행하였습니다.

 

적립식 투자니깐 월별리밸런싱은 없고, 초기 $1,000로 SPY를 매수하고 매월 말일마다 $1,000를 추가 매수하는 방법으로 테스트를 했습니다.

매월 적립식 투자 백테스트 결과

 

백분율로 표현하였으며, 파란색이 SPY의 주가지수고 빨간색이 매월 투자한 총자산의 변화입니다.

 

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
SPY 지수 10% -51.5% 516%
SPY에 적립식투자 42.4% -38.7% 257%

 

20년 투자기간 동안 총 투자금액 대비 2.57배 정도 기대할 수 있습니다. MDD는 -51.5% 대비 -38.7%로 위험이 줄인것을 확인할 수 있습니다.

 

CAGR은 초기자산 대비 복리수익률이라서 추가금액이 투입되니 상당히 높아 졌습니다.

 

20년 동안 꾸준히 투자해서 2억을 5억 정도로 만들었다고 생각하면 될 것 같습니다.

 

사실 나쁜 결과는 아니지만 좀 더 좋은 방법이 없을까 고민을 하였습니다. 20년 동안 투자해서 고작 2배를 번다면 너무나 아쉬울 것 같습니다.

 

이번에는 나스닥100지수를 추종하는 QQQ로 백테스트를 진행해 보았습니다. 백테스트 진행 조건은 이전 SPY와 동일합니다.

매월 적립식 투자 백테스트 결과

 

구  분 CAGR MDD 총수익률
(수익률/총투입금액)
QQQ 지수 14.7% -51.2% 1249%
QQQ에 적립식투자 46.8% -43% 540%

 

SPY 보다는 결과가 더 좋네요.

 

CAGR은 46.8%이며, 투자원금대비 5.4배의 자산을 벌었네요. 2억을 투자하면 약 11억정도의 수익을 기대할 수 있습니다. 당연히 CAGR은 추가금을 수익으로 반영한 수치라서 높게 나오며, 추가금을 고려하지 않고 CAGR을 구하면 약 10%정도가 나옵니다.

 

수익률은 좋은데 MDD가 너무 낮아서 제가 감내가 될지 모르겠네요.

 

더 좋은 방법이 없을까요? 그래서 이번에는 월별로 리밸런싱을 해 보면 어떻게 될지 백테스트를 해 보았습니다.

 

 

매월 적립식 투자 + 월별리밸런싱(정적자산분배)

투자금은 동일하게 첫 달 $1,000이며, 매월 $1,000 씩 추가 투입됩니다.

 

SPY와 함께 미국 장기채권 ETF인 TLT를 5:5 비율로 투자하며, 매월 리밸런싱을 하며 5:5 비율을 유지합니다.

 

2005년 ~2024년 2월 29일까지 투자하며, 수수료, 세금, 배당금은 고려하지 않았습니다. 

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

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

 

 

이번에는 추가금을 고려하지 않고 수익률을 구해 봤습니다. CAGR이 은행이자 만큼 나왔네요.

 

이렇게 투자할바엔 차라리 은행에 예금하는게 속편할 것 같습니다. 그러면 MDD가 0%가 나오니 다리뻗고 잘 수라도 있겠죠.

 

이번엔 QQQ와 GLD로 투자를 해 보겠습니다. 조건은 위와 동일합니다.

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

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

 

QQQ+GLD 조합이 SPY+TLT 보다 결과는 더 낫습니다. 그래도 원금대비 2.5배의 수익률은 20년 투자기간을 감안하면 너무 가옥한 것이 아닌가 하는 생각이 드네요.

 

결론

매월 적립식 투자방법은 수익률이 저조한 것 같습니다. MDD가 좀 높더라도 SPY이나 QQQ에 단독으로 투자하는 것이 장기간으로 봤을 때 훨씬 유리할 것 같습니다.

 

다음 포스트에서는 동적자산분배로 적립식 투자를 했을 때 결과를 알려드리도록 하겠습니다.

 

위의 백테스트 코드는 아래와 같습니다.

 

 

매월 적립식 투자

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'

#초기 투입자금
초기자금 = 1000
매월추가투입 = 1000

start = datetime(2005, 1, 1)
end = datetime(2024, 2, 29)


df_o = pdr.get_data_yahoo(p1, start, end)
df_o['p1'] = df_o['Adj Close']

# 월말 날짜만 가져오기
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

#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 = get_monthly_end(df_d)
    li_p1 = []
    li_p1c = [] #자산p1의 개수
    li_sum = []
    원금누계 = []
    for i in range(len(df)):
        if i == 0:
            li_p1.append(초기자금)
            li_p1c.append(초기자금/df['p1'].iloc[i])
            li_sum.append(li_p1[i])
            원금누계.append(초기자금)
        else:
            li_sum.append(li_p1c[i-1]*df['p1'].iloc[i]+매월추가투입)
            li_p1.append(li_sum[i])
            li_p1c.append(li_p1[i]/df['p1'].iloc[i])
            원금누계.append(원금누계[i-1]+매월추가투입)

    df['자산1'] = li_p1
    df['합계'] = li_sum
    df['자산1 개수'] = li_p1c
    df['원금누계'] = 원금누계

    # 월말 데이터를 일별 데이터로 만들기
    df = pd.merge(df_o['p1'], df[['자산1', '합계', '자산1 개수', '원금누계']],
                  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['자산1'].iloc[s] = df['p1'].iloc[s] * df['자산1 개수'].iloc[s]
            df['합계'].iloc[s] = df['자산1'].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['원금누계']

    #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
    전략수익률 = df['합계'].iloc[-1]/df['원금누계'].iloc[-1] - 1

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

    #엑셀파일로 만들기
    # df.to_excel(f'평균모멘텀스코어 백테스트({p1}, 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} Accumulated Invest')

    plt.show()

    return df

back_test(df_o, 초기자금, 매월추가투입)

 

 

 

매월 적립식 투자 + 매월리밸런싱(정적자산분배)

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 = 'SPY'
p2 = 'TLT'

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

#추가자금
추가자금 = 1000

#평균모멘텀스코어 기간
moment_months = 6
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)
        else:
            li_score.append(0.5)

    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])
            원금누계.append(p1자금+p2자금)
            li_sum.append(li_p1[i]+li_p2[i])
        else:
            li_sum.append(li_p1c[i-1]*df['p1'].iloc[i] + li_p2c[i-1]*df['p2'].iloc[i] + 추가자금)
            원금누계.append(원금누계[i - 1] + 추가자금)
            li_p1.append(li_sum[i]*df['평균모멘텀스코어'].iloc[i])
            li_p2.append(li_sum[i]-li_p1[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['원금누계'] = 원금누계
    df['합계'] = li_sum
    df['자산1 개수'] = li_p1c
    df['자산2 개수'] = li_p2c

    # 월말 데이터를 일별 데이터로 만들기
    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['자산1'].iloc[s] + df['자산2'].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['원금누계']

    #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
반응형

댓글