이번 글에서는 가속듀얼모멘텀을 백테스트 해보겠다. 우선 가속듀얼모멘텀의 기본 구조는 다음과 같다.
- n개의 투자자산과 1개의 안전자산을 선정한다.
- n개 투자자산의 1,3,6개월 평균모멘텀을 산출한다.
- n개 투자자산의 평균모멘텀 상위 m개의 자산에 동일 비중으로 투자한다.
- 단, 선정된 m개의 투자자산 중에서 모멘텀이 음수인 자산은 안전자산으로 교체한다.
가속듀얼모멘텀의 원문은 아래 링크를 참조하도록 한다.
이글에서는 투자자산으로 TQQQ, TMF를 안전자산은 BIL을 선택하여 테스트를 수행하였으며 전략 및 백테스트 생성 코드는 아래와 같다.
# 가속듀얼모멘텀 백테스트
def WeighADM_BT(assets, rank, start_day, run_on_end_of_period=False, lag=1, name='Accelerating DM'):
s = bt.Strategy(name, [bt.algos.RunAfterDate(start_day),
bt.algos.RunMonthly(run_on_first_date=False, run_on_end_of_period=run_on_end_of_period, run_on_last_date=False),
bt.algos.SelectAll(),
WeighADM(rank=rank, lag=pd.DateOffset(days=lag)),
bt.algos.Rebalance()])
return bt.Backtest(s, assets, initial_capital=100000000.0)
# 가속 듀얼모멘텀 1~2종목
assets_adm = assets[tickers].copy()
assets_adm[dualtickers[1]] = assets[dualtickers[1]]
adms = {}
for i in range(0,2):
adms[i] = WeighADM_BT(assets_adm, i+1, start_day+pd.DateOffset(months=6), False, lag, name='ADM_{}R'.format(str(i+1)))
adm_report = bt.run(*adms.values())
WeighADM_BT()메소드의 두번째 파라미터인 rank는 선정되는 투자자산의 순위이며 rank가 1 인 경우 투자자산군에서 모멘텀이 양수인 1개 종목만 선정하고 2 인 경우에는 모멘텀이 양수인 2개 종목을 선정하는 것을 의미한다. WeighADM()은 가속듀얼모멘텀을 산출하여 비중을 할당하는 사용자 정의 클래스이다. 따라서 rank가 1 이고 투자자산군에서 모멘텀이 가장 좋은 자산의 수익률이 음수이면 안전자산인 BIL에 100% 투자되고 rank가 2 이고 투자자산군에서 TQQQ만 모멘텀이 양수인 경우에는 TQQQ와 BIL에 50:50으로 투자된다.
백테스트 결과는 아래와 같다.
모멘텀 상위 1종목, 2종목으로 백테스트를 수행한 결과는 연간 수익률은 모두 괜찮은 편이지만 상위 1종목만 선택하는 경우 최대 손실율은 -79%로 매우 높으며 상위2종목 선택시에는 -49%로 개선되지만 여전히 높다. 따라서 TQQQ, TMF, BIL로 구성시 가속듀얼모멘텀은 과거 백테스트 결과가 효과적이지 않다는 것을 확인할 수 있다.
WeighADM() 사용자 정의 클래스는 아래와 같다.
class WeighADM(bt.Algo):
"""
n개의 공격자산과 1개의 안전자산으로 구성된 투자자산(target.temp['selected'])에 가속듀얼모멘텀 방식으로 비중을 할당한다.
n개의 공격자산중에서 (1,3,6개월) 평균 수익률 순위(self.rank)내에 있는 공격자산에 동일 비중으로 투자한다.
단, 해당 공격자산의 평균 수익률이 0보다 작을 경우 안전자산으로 변경함.
Args:
rank : 투자할 공격자산의 개수
lag : 리밸런스 지연일
Returns:
target.temp['weights']에 가속듀얼모멘텀 방식에 따른 비중을 반환
"""
def __init__(self, rank=1, lag=1):
super(WeighADM, self).__init__()
self.rank = rank
self.lag = lag
def __call__(self, target):
selected = target.temp['selected'].copy()
t0 = target.now - self.lag
momentum1 = target.universe[selected].loc[t0 - pd.DateOffset(months=1):t0]
momentum3 = target.universe[selected].loc[t0 - pd.DateOffset(months=1):t0]
momentum6 = target.universe[selected].loc[t0 - pd.DateOffset(months=1):t0]
ret1 = momentum1.calc_total_return()
ret3 = momentum3.calc_total_return()
ret6 = momentum6.calc_total_return()
assetcount = len(selected) - 1
avg = (ret1 + ret3 + ret6) / 3
#print('avg \n', avg)
rank = avg[selected[0:assetcount]].rank(ascending=False)
weights = pd.Series([0]*len(selected), index=selected)
for i in range(0, assetcount):
if rank[selected[i]] <= self.rank:
if avg[selected[i]] > 0:
weights[selected[i]] = 1
else:
#print('selected[{}]={}'.format(assetcount, selected[assetcount]))
weights[selected[assetcount]] = 1
#print('weights \n', weights)
weights = weights / weights.sum()
#print('final weights \n', weights)
target.temp['weights'] = weights
return True
1편 : TQQQ, TMF 고정 비중 백테스트
2편 : TQQQ, TMF 상대 모멘텀 백테스트
3편 : TQQQ, TMF 절대 모멘텀 백테스트(1)
4편 : TQQQ, TMF 절대 모멘텀 백테스트(2)
5편: TQQQ,TMF 듀얼 모멘텀 백테스트
6편: TQQQ, TMF 평균 모멘텀 스코어 백테스트
7편: TQQQ, TMF 수익곡선 모멘텀 백테스트
8편: TQQQ, TMF 평균모멘텀 전략의 비중 계산 Algo 클래스
☞ 이 글은 순수하게 개인적인 의도로 테스트한 결과이며 내용상 오류가 있을 수도 있습니다.
'자산배분' 카테고리의 다른 글
TQQQ, TMF 평균모멘텀 전략의 비중 계산 Algo 클래스 (18) | 2023.01.19 |
---|---|
TQQQ, TMF 수익곡선 모멘텀 백테스트 (18) | 2023.01.18 |
TQQQ, TMF 평균 모멘텀 스코어 백테스트 (21) | 2023.01.12 |
TQQQ, TMF 듀얼 모멘텀 백테스트 (22) | 2023.01.09 |
TQQQ, TMF 절대 모멘텀 백테스트(2) (18) | 2022.12.26 |