지난번 포스트에서 일정한 배분으로 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억원으로 늘어나서 염려할 금액은 아니었음을 알 수 있었습니다.
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 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) 양도세 고려 |
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 단독투자 | 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')
'파이썬(Python) > 파이썬으로 투자실험' 카테고리의 다른 글
금리와 채권가격 비교 그리고 채권금리를 이용한 투자 백테스트 (2) | 2024.04.24 |
---|---|
미국ETF SPY와 TLT의 자산배분 백테스트 (0) | 2024.04.19 |
미국ETF 양도소득세는 복리에 어떤 영향을 미칠까? (0) | 2024.04.15 |
SCHD를 QQQ와 함께 자산분배해서 투자하면 수익률이 괜찮을까? (1) | 2024.04.12 |
미국 배당성장ETF SCHD로 적립식 투자를 하면 10년 후에 수익률이 어떻게 될까?(SCHD 적립식 투자 백테스트 배당금 포함) (2) | 2024.04.10 |
댓글