미국ETF 동적자산분배로 투자하면 수익률이 올라갈까?
본문 바로가기
파이썬(Python)/파이썬으로 투자실험

미국ETF 동적자산분배로 투자하면 수익률이 올라갈까?

by Squat Lee 2024. 4. 17.

지난번 포스트에서 일정한 배분으로 2가지 자산을 투자하는 방법에 대하여 백테스트를 하였고, 복리에 대한 영향도 테스트를 통해 가늠할 수 있었습니다.

 

QQQ : SCHD(8:2조건) 양도세 적용전(%) 양도세 적용후(%)
CAGR 13.8 13.7
MDD -30.3 -30.3
총 수익률 369 369

초기투자금액 10만달러, 월1천달러 분할매수

 

초기투자금 10만달러(약 1억 3천만원)와 1천달러를 월별 적립식으로 투자하면 백테스트 상 양도세가 적용되지 않음을 확인하였습니다.

 

100만달러(약 13억원) 초기투자비와 월 1천달러씩 월별 매수를 한다면 양도세가 총 $12,788.0 (약 16,340,869.0원) 발생하지만, 자산은 86억원으로 늘어나서 염려할 금액은 아니었음을 알 수 있었습니다.

 

초기투자금 100만달러, 월별 1천달러 투자시 백테스트

 

QQQ : SCHD(8:2조건) 10만달러 + 월별1천달러(%) 양도세 적용후(%)
CAGR 13.8 13.7
MDD -30.3 -30.3
총 수익률 369 369

초기투자금액 100만달러, 월1천달러 분할매수


 

이번에는 동적자산분배로 CAGR은 더 높이고 MDD는 더 낮출 수 있는 방법이 있는지 살펴보겠습니다.

 

지난 포스트에서는 QQQ와 SCHD를 8:2 비율로 고정하고 매월 리밸런싱을 진행하였습니다.

 

이제는 주가가 올라가면 더 많이 사고, 떨어지면 비중을 줄이는 방법으로 "동적자산분배"를 통한 백테스트를 진행해 보겠습니다.

 

백테스트 조건

1. QQQ와 SCHD를 평균모멘텀스코어*를 통해 비중을 매월 리밸런싱을 합니다.

2. 초기자산은 $2,000이고, 매월 $1,000씩 추가금이 투입됩니다.(리밸런싱은 추가금이 투입된 기준입니다.)

3. 배당수익은 재투자를 한다고 가정했습니다.

4. 매매수수료는 0.25%, 매당수수료는 15.4%를 반영했습니다.

5. 기간은 QQQ와 SCHD 중 상장일이 늦은자산을 기준으로 2023년 12월 31일까지입니다.

6. 연도별 이익과 손실을 합쳐서 250만원이 넘는 금액에 대해서는 22% 세금을 제하고 마지막날 자산을 조정하였습니다.

※ 연간 운용수수료는 ETF가격에 포함되었기에 별도로 고려하지 않았습니다.

 

* 평균모멘텀스코어

  • 주식에도 관성이 있다고 가정하고 주식이 오를때는 비중을 증가하고, 떨어질때는 비중을 줄이는 방법입니다.
  • 과거 12개월동안 주가의 변화를 계산해서 올랐을때는 '1'을 떨어졌을 때는 '0'을 부여한 다음에 12로 나눠줍니다. 그 값이 자산의 비중이 됩니다.(12개월 말고도 6개월, 3개월 등 다양한 기간으로 해도 됩니다.)
  • 매월 평균모멘텀스코어를 계산하기에 매월 비중이 달라집니다.

 

QQQ와 SCHD를 평균모멘텀스코어로 투자한 결과

 

이렇게 비중을 조절하면서 투자한 결과를 그래프로 살펴보니 수익률이 증가 된 것을 확인할 수 있습니다.

 

구  분 정적자산분배(QQQ : SCHD 8:2) (%) 동적자산분배(QQQ : SCHD)
CAGR 13.7 17.7
MDD -30.3 -28.7
총 수익률 369 606

초기투자금액 100만달러, 월1천달러 분할매수

 

결과는 상당히 고무적으로 나왔습니다. 

 

CAGR은 4%가량 올랐고, MDD는 줄었습니다. 총 수익률은 1.6배가 차이가 납니다.

 

구  분 QQQ 단독투자 SCHD 단독투자 QQQ+SCHD 동적자산분배
총 수익률(%) 692 335 606
MDD(%) -35.1 -33.4 -28.7

 

QQQ나 SCHD 단독으로 투자할 때 보다 위험(MDD)는 줄었고 SCHD와 비교해서 수익률은 거의 2배가 차이가 납니다.

 

하지만 위의 수치에는 양도소득세를 고려하지 않았습니다. 

 

연도별 손익합산 금액이 250만원까지는 비과세이지만 250만원을 초과하는 금액에 대해서는 22%의 양도소득세가 부과됩니다.

 

그러면 이번에는 양도소득세를 고려한 백테스트 결과를 보도록 하겠습니다.

 

양도소득세를 고려한 백테스트 결과

 

QQQ와 SCHD를 평균모멘텀스코어로 투자한 결과(양도세 고려)

 

구  분 동적자산분배(QQQ : SCHD)
양도세 고려하지 않음
동적자산분배(QQQ : SCHD)
양도세 고려
CAGR 17.7 17
MDD -28.7 -28.7
총 수익률 606 559

초기투자금액 100만달러, 월1천달러 분할매수

 

수익률이 다소 줄었지만 여전히 CAGR이 17%라는 상당히 높은 결과를 보여줍니다.

 

백테스트 기간동안에 총 양도세는 $61,460.0 (약 78,536,361.0원) 이 부과되었습니다.

 

구  분 실현손익(원) 양도세(원)
2011년 0 0
2012년 -6,648,730 0
2013년 28,902,216 5,808,488
2014년 -32,615,219 0
2015년 20,367,200 3,930,784
2016년 36,982,454 7,586,140
2017년 -4,590,128 0
2018년 -248,851,66 0
2019년 97,544,460 20,909,781
2020년 93,038,408 19,918,450
2021년 -34,858,052 0
2022년 -1,039,882,438 0
2023년 59,980,670 12,645,747

초기투자금 100만달러, 매월 1천달러 동적자산분배 투자시 실현손익 및 양도세

 

자산배분을 하는 투자법의 특성상 실현손익은 불가피하기에 그에따른 양도소득세도 발생하게 됩니다.

 

위의 백테스트는 초기투자금 100만달러(약 13억원) 정도로 테스트를 했기 때문에 실현손익도 크고 양도소득세도 많아 보입니다. 

 

하지만 좀 더 현실적으로 초기투자금 1억원에 매달 1백만원씩 투자한다고 가정했을 때 위의 백테스트 기간동안 수익률과 양도소득세는 어떻게 달라지는지 살펴 보도록 하겠습니다.

 

QQQ와 SCHD를 평균모멘텀스코어로 투자한 결과(양도세 고려) 초기투자금 1억, 매월 1백만원씩 투자

 

구  분 QQQ 단독투자 SCHD 단독투자 QQQ+SCHD 동적자산분배
총 수익률(%) 692 335 386
CAGR(%) 18.8 13 14.1
MDD(%) -35.1 -33.4 -28.6

초기투자금 1억 매월 1백만원씩 동적배분 투자

 

초기투자금이 매월투자하는 금액에 비해서 적어진 탓에 CAGR과 총 수익률은 줄었지만, 여전히 14.1%로 투자하기 괜찮은 수치로 나왔습니다.

 

MDD도 28.6%로 QQQ나 SCHD를 단독으로 투자할 때보다 낮습니다.

 

백테스트 기간동안 총 양도세는 $4,146.0 (약 5,297,656.0원)가 나왔습니다.

 

 총 투자된 자금은 246,000,000원이고, 최종 자산은 1,195,888,743원이 되었습니다.(환율 1,318원/달러 기준)

 

12억에 비해서 양도소득세 5백만원은 감당할 수 있는 금액 아닐까 생각합니다.

 

백테스트를 진행한 파이썬 코드는 아래와 같습니다.

 

백테스트 파이썬 코드

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

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

#투자자산
p1 = 'QQQ'
p2 = 'SCHD'

moment_months = 12
p1비율 = 0.5 #초기 투자자산 배분비율

def get_div(p):
    ticker1 = yf.Ticker(p)
    div = ticker1.dividends.tz_localize(None)
    return div


매매수수료 = 0.0025
배당소득세 = 0.154

start = datetime(2005, 1, 1)
end = datetime(2023, 12, 31)

초기투자금 = 100000000/1318
매월투자금 = 1000000/1318

배당금적용여부 = 1 #1이면 적용, 0이면 미적용
양도소득세적용여부 = 1 #1이면 적용, 0이면 미적용

#데이터 가져오기
def get_data(p1, p2, start, end):
    df1 = pdr.get_data_yahoo(p1, start, end)
    df1.rename(columns={'Adj Close':'p1주가'}, inplace=True)

    df2 = pdr.get_data_yahoo(p2, start, end)
    df2.rename(columns={'Adj Close':'p2주가'}, inplace=True)

    df_ex = pdr.get_data_yahoo('USDKRW=X', start, end) #환율
    df_ex.rename(columns={'Adj Close': '환율'}, inplace=True)

    df = pd.merge(df1['p1주가'], df2['p2주가'], left_index=True, right_index=True, how='inner')
    df = pd.merge(df, df_ex['환율'], left_index=True, right_index=True)
    return df


# 세금계산을 위해서 년도별 마지막날 구하기
df_y = get_data(p1, p2, start, end)
df_y['date'] = df_y.index
df_y = df_y.groupby(by=df_y.index.year).last()
li_lastdays = df_y['date'].to_list()

# 배당데이터에 다른 데이터 넣기
def merge_div(p1, p2, start, end):
    df = get_data(p1, p2, start, end)
    div1 = get_div(p1)
    div2 = get_div(p2)
    if 배당금적용여부 == 1:
        df['배당금1'] = div1
        df['배당금2'] = div2
        df.fillna(0, inplace=True)
    else:
        df['배당금1'] = 0
        df['배당금2'] = 0
    return df

df = merge_div(p1, p2, start, end)

#월말투자금 넣기
df_m = df
df_m['date'] = df_m.index #멀티인덱스로 만들어지기에 별도의 날짜컬럼을 만들고
df_m = df_m.groupby(by=[df.index.year, df.index.month]).last() #매월 마지막날만 필터
li_monthlast = df_m['date'].to_list() #매월 마지막날을 리스트로

# 월말 날짜만 가져오기
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
    df = df[['투자모멘텀스코어', '평균모멘텀스코어']]
    return df

#투자금 컬럼 데이터프레임으로 만들기
df['투자금'] = 0
for i, m in enumerate(li_monthlast):
    if i == 0:
        df['투자금'].loc[m] = 초기투자금
    else:
        df['투자금'].loc[m] = 매월투자금

#MDD 구하기
def get_mdd(col):
    window = 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

# 적립식 투자하면서 자산변동 계산
df_score = score(df, moment_months)
df = pd.merge(df, df_score, left_index=True, right_index=True, how='outer')
df.fillna(0, inplace=True)

df[['p1주식수', 'p1금액', 'p2주식수', 'p2금액', '합계', '원금누계', '실현손익', '연간양도세']] = 0

for m in range(len(df)):
    if m == 0:
        df['합계'].iloc[m] = df['투자금'].iloc[m] * (1-매매수수료)
        df['p1금액'].iloc[m] = df['합계'].iloc[m] * p1비율
        df['p1주식수'].iloc[m] = df['p1금액'].iloc[m] / df['p1주가'].iloc[m]
        df['p2금액'].iloc[m] = df['합계'].iloc[m] - df['p1금액'].iloc[m]
        df['p2주식수'].iloc[m] = df['p2금액'].iloc[m] / df['p2주가'].iloc[m]
        df['원금누계'].iloc[m] = df['투자금'].iloc[m]
        df['실현손익'].iloc[m] = 0
    else:
        df['합계'].iloc[m] = df['투자금'].iloc[m] * (1-매매수수료) + \
                           (df['p1주가'].iloc[m] * df['p1주식수'].iloc[m-1]) + (df['p2주가'].iloc[m] * df['p2주식수'].iloc[m-1]) + \
                           (df['배당금1'].iloc[m] * df['p1주식수'].iloc[m-2] + df['배당금2'].iloc[m] * df['p2주식수'].iloc[m-2]) * (1 - 배당소득세)
        # 매월 리밸런싱 할 때는 주식수가 변하지만
        if df['투자금'].iloc[m] > 0:
            df['p1금액'].iloc[m] = df['합계'].iloc[m] * df['평균모멘텀스코어'].iloc[m]
            df['p1주식수'].iloc[m] = df['p1금액'].iloc[m] / df['p1주가'].iloc[m]
            df['p2금액'].iloc[m] = df['합계'].iloc[m] - df['p1금액'].iloc[m]
            df['p2주식수'].iloc[m] = df['p2금액'].iloc[m] / df['p2주가'].iloc[m]
        # 리밸런싱을 안 하는 날에는 주식수가 고정
        else:
            df['p1주식수'].iloc[m] = df['p1주식수'].iloc[m-1]
            df['p1금액'].iloc[m] = df['p1주식수'].iloc[m] * df['p1주가'].iloc[m]
            df['p2주식수'].iloc[m] = df['p2주식수'].iloc[m - 1]
            df['p2금액'].iloc[m] = df['p2주식수'].iloc[m] * df['p2주가'].iloc[m]
        df['원금누계'].iloc[m] = df['원금누계'].iloc[m - 1] + df['투자금'].iloc[m]
            # 매월 리밸런싱 할때 실현손익 구하기
        if df['투자금'].iloc[m] > 0 and m > 0:
            for j in range(1, 33):
                if df['투자금'].iloc[m-j] > 0:
                    if df['p1주식수'].iloc[m] < df['p1주식수'].iloc[m-j]:
                        df['실현손익'].iloc[m] = (df['p1주식수'].iloc[m-j]-df['p1주식수'].iloc[m]) * \
                                             (df['p1주가'].iloc[m] - df['p1주가'].iloc[m-j]) - \
                                             (df['p1주식수'].iloc[m-j]-df['p1주식수'].iloc[m]) * df['p1주가'].iloc[m] * 매매수수료
                    if df['p2주식수'].iloc[m] < df['p2주식수'].iloc[m - j]:
                        df['실현손익'].iloc[m] = (df['p2주식수'].iloc[m-j]-df['p2주식수'].iloc[m]) * \
                                             (df['p2주가'].iloc[m] - df['p2주가'].iloc[m-j]) -\
                                             (df['p2주식수'].iloc[m-j]-df['p2주식수'].iloc[m]) * df['p2주가'].iloc[m] * 매매수수료
                        break

        else:
            df['실현손익'].iloc[m] = 0

        # 실현손익으로 양도소득세 계산 및 합계, 주식금액을 양도소득세를 제외한 금액으로 정정
        if 양도소득세적용여부 == 1: #양도소득세 적용시 금액을 계산
            if df.index[m] in li_lastdays:
                연간실현손익 = df['실현손익'].loc[str(df.index[m])[:4]].sum() * df['환율'].iloc[m] #원화로 변환
                print(f'{str(df.index[m])[:4]}년 실현손익: {round(연간실현손익,0):,}')
                if 연간실현손익 >= 2500000:
                    df['연간양도세'].iloc[m] = ((연간실현손익 - 2500000) * 0.22)/df['환율'].iloc[m] #달러로 변환
                    print(f"{df['연간양도세'].index[m]}연간양도세 : {round(df['연간양도세'].iloc[m]*df['환율'].iloc[m],0):,}")
                    df['합계'].iloc[m] = df['투자금'].iloc[m] * (1 - 매매수수료) + \
                                       (df['p1주가'].iloc[m] * df['p1주식수'].iloc[m - 1]) + (
                                                   df['p2주가'].iloc[m] * df['p2주식수'].iloc[m - 1]) + \
                                       (df['배당금1'].iloc[m] * df['p1주식수'].iloc[m - 2] + df['배당금2'].iloc[m] *
                                        df['p2주식수'].iloc[m - 2]) * (1 - 배당소득세) - df['연간양도세'].iloc[m]
                    df['p1금액'].iloc[m] = df['합계'].iloc[m] * p1비율
                    df['p1주식수'].iloc[m] = df['p1금액'].iloc[m] / df['p1주가'].iloc[m]
                    df['p2금액'].iloc[m] = df['합계'].iloc[m] - df['p1금액'].iloc[m]
                    df['p2주식수'].iloc[m] = df['p2금액'].iloc[m] / df['p2주가'].iloc[m]


df = df[['p1주가', 'p1주식수', 'p1금액', '배당금1', 'p2주가', 'p2주식수', 'p2금액', '배당금2',
         '투자금', '합계', '원금누계', '실현손익', '환율', '연간양도세', '평균모멘텀스코어', '투자모멘텀스코어']]

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

df.dropna(inplace=True)

#CAGR
diff = df.index[-1].year - df.index[0].year
cagr_etf1 = (df['p1주가'].iloc[-1] / df['p1주가'].iloc[0]) ** (1 / diff) - 1
cagr_etf2 = (df['p2주가'].iloc[-1] / df['p2주가'].iloc[0]) ** (1 / diff) - 1
cagr_전략 = (df['합계'].iloc[-1] / df['합계'].iloc[0]) ** (1 / diff) - 1
cagr_전략_실질 = (df['합계'].iloc[-1] / df['원금누계'].iloc[-1]) ** (1 / diff) - 1
cagr = f'CAGR(ETF1) : {round(cagr_etf1, 3)} / CAGR(ETF2) : {round(cagr_etf2, 3)} / ' \
       f'CAGR(전략) : {round(cagr_전략, 3)} CAGR(실질전략) : {round(cagr_전략_실질, 3)}'
print(cagr)

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

ETF1수익률 = df['p1주가'].iloc[-1]/df['p1주가'].iloc[0] - 1
ETF2수익률 = df['p2주가'].iloc[-1]/df['p2주가'].iloc[0] - 1
전략수익률 = df['합계'].iloc[-1]/df['투자금'].sum() - 1

print(f'{p1} 수익률 : {round(ETF1수익률,2)}')
print(f'{p2} 수익률 : {round(ETF2수익률,2)}')
print(f'전략 수익률 : {round(전략수익률,2)}')

print(f'양도소득세 총계 : ${round(df["연간양도세"].sum(),0):,} (약 {round(df["연간양도세"].sum()*df["환율"].iloc[-1],0):,}원)')

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

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

plt.title(f'{p1} {p2} Average Moment Strategy Dynamic Ratio Invest({moment_months}months)')

plt.show()

#엑셀파일로 만들기
df.to_excel(f'동적분배 백테스트(세금포함, {p1} {p2} 모멘텀기간 {moment_months}개월 CAGR {round(cagr_전략_실질*100,1)} '
            f'MDD {round(mdd_전략*100,1)}).xlsx')

 

728x90
반응형

댓글