[5] 파이썬(Python)으로 BAA 전략 검증하기
파이썬(Python)으로 BAA 전략 검증하기
BAA Strategy
BAA 전략은 W. J Keller가 발표한 무수히 많은 전략들 중 가장 최신의 전략이며, 높은 기대 수익률을 보이는 동적 자산 분재 전략이다. 이름인 Bold에서 알 수 있듯이 'Impressive Returns'를 나타낸다고 한다.
자세한 내용은 아래 서지 정보와 SSRN링크를 참고하면 많은 정보를 얻을 수 있을 것이다.
Title : Relative and Absolute Momentum in Times of Rising/Low Yields: Bold Asset Allocation (BAA)
Author : Wouter J. Keller,
Date : July 18, 2022,
Abstract : Our aim is to develop a very offensive (‘aggressive’) tactical asset allocation strategy, by combining some of our previous models like Protected- (PAA), Vigilant- (VAA) and Defensive (DAA) Asset Allocation. We will call this new strategy the ‘Bold Asset Allocation’ (BAA). BAA combines a slow relative momentum with a fast absolute momentum and crash protection, based on the concept of the ‘canary’ universe, where we switch from our offensive to the defensive universe when any of the assets in the canary universe has negative absolute momentum. As a result, BAA spends ca 60% in the defensive universe. By enhancing this defensive universe beyond cash, we find very impressive returns (>=20%) with low monthly max drawdowns (<=15%) over Dec 1970 – Jun 2022.
원문 링크 : https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4166845
BAA 전략은 DAA의 카나리아 전략의 아이디어를 그대로 가져와서 변형시켰다. DAA 전략은 이전 논문에서도 확인할 수 있고, 필자의 자산관리 도서에서도 다루었다.
BAA전략을 분석하기 전에 DAA 전략에 대해서 먼저 다루어보려고 한다.
책의 원문을 바탕으로 DAA전략에 대해 소개하자면 다음과 같다.
대가의 자산분배 전략 : DAA 포트폴리오 운용하기
DAA전략은 Defensive Asset Allocation의 약자로 말 그대로 방어적인 전략이다. 기존의 듀얼 모멘텀 기반 VAA(Vigilant Asset Allocation) 전략을 개선하여 카나리아 자산군이라는 특이한 조건을 도입했다. 1980년대 영국에서 광산에서 일하는 광부가 카나리아 새를 가져가서 일산화탄소에 질식되는 사망사고를 방지하였다. 카나리아가 대신 죽어서 사람을 살린 것이다. 마찬가지로 DAA 전략에서는 카나리아 자산군이라는 일종의 선행 지표를 사용하여, 카나리아 지표의 모멘텀이 하락하면 자산을 안전자산으로 옮기고 지표의 모멘텀이 살아있으면 공격 자산에 투자하였다. 일종의 카나리아 자산을 이용한 마켓타이밍 전략이라고도 볼 수 있다. DAA 전략은 아래와 같이 카나리아 자산, 공격 자산 그리고 방어 자산으로 이루어져 있다.
- 카나리아 자산
VWO (Emerging Markets Stock Index Fund ETF)
BND (Total Bond Market ETF)
- 공격 자산
SPY (S&P 500)
IWM (Russell 2000)
QQQ (NASDAQ 100)
VGK (Europe)
EWJ (Japan)
VWO (Emerging)
VNQ (US REITs)
GSG (Commodities)
GLD (Gold)
TLT (US 20+ Year Treasury)
HYG (High Yield)
LQD (Investment Grade Corporate Bond)
- 방어 자산
SHV (미국 단기 국채)
IEF (미국 중기국채)
UST (미국 중기채 레버리지)
DAA 전략의 개발자인 Keller는 카나리아 및 각 공격/방어 자산의 12개월 환산 모멘텀을 구해 포트폴리오 운영에 활용하였다. 모멘텀을 구하는 공식은 아래와 같다.
환산 모멘텀=12 ×(종가−1개월 전 종가)+4 ×(종가−3개월 전 종가)+2 ×(종가−6개월 전 종가)+(종가−12개월 전 종가)
카나리아 자산인 VWO, BND의 환산 모멘텀이 모두 양수이면 카나리아가 살아있으므로 공격형 자산 중 환산 모멘텀이 가장 큰 2개의 자산을 구매하고 VWO, BND가 하나라도 음수 값이 되어 죽었다면 카나리아가 죽었다고 판단하고 방어 자산 중 환산 모멘텀이 가장 큰 1개의 자산을 구매한다.
상기 전략을 매월 리밸런싱 하며 운영하는데, 이 로직은 각 자산의 절대적인 세기(절대 모멘텀)와 자산군 간의 모멘텀의 세기 비교(상대 모멘텀)를 모두 고려하는 듀얼 모멘텀 전략이다. 이 로직대로 운영한다면 CAGR 22%, MDD 13%가량의 안정적인 수익률을 얻을 수 있다. 낮은 MDD에 연 복리 수익률이 20% 이상인 검증된 포트폴리오를 사용하지 않을 이유가 없다고 생각한다.
그렇다면 BAA전략은 무엇인가?
최근에 발표된 BAA 전략은 동적 자산배분 중 가장 우수한 전략 중 하나라고 생각된다. 아래 본문에서는 이를 실제로 운영한다면 어떨지, 다른 Factor들을 변형해서 이용한다면 어떠한 다른 결과가 있을지 파이썬을 이용하여 비교해보고자 한다.
먼저 DAA전략과 유사하게, 각 자산군을 리스트로 설정하였다.
attack = ['QQQ', 'VWO', 'VEA', 'BND']
defense = ['TIP', 'DBC', 'BIL', 'IEF', 'TLT', 'LQD', 'BND']
canary = ['SPY', 'VWO', 'VEA', 'BND']
논문에서는 3, 6, 9 12월의 환산 모멘텀과, 12개월 이동평균선을 이용한 상대 모멘텀을 사용했다.
아래 함수를 이용하여 find_mom_by('abs') / find_mom_by('rel') 자산군 내의 값을 계산할 수 있다.
def find_mom_by(detail:str, day:str, asset:list):
'''param
detail = abs(olute) or rel(ate)\n
day = '2020-01-01' \n
asset = attack or defense or canary
'''
def cal_momentum(day, ticker):
'''param
day = '2020-01-01' \n
ticker = 'SPY'
'''
day_datetime = datetime.strptime(day, '%Y-%m-%d')
day_before = day_datetime - timedelta(days=400)
ohlc = yf.download(ticker, start=datetime.strftime(day_before, '%Y-%m-%d') , end=datetime.strftime(day_datetime, '%Y-%m-%d'), progress=False)
def adj_price(day_before):
'''
param = day_before >= 1
'''
a = ohlc['Adj Close'][-day_before] # 최근전 종가
return a
moment = 12*(adj_price(1)/adj_price(20)-1) + 4*(adj_price(1)/adj_price(60)-1) + 2*(adj_price(1)/adj_price(120)-1) + (adj_price(1)/adj_price(250)-1)
return round(moment, 2)
def cal_rel_mom(day, ticker):
'''param
day = '2020-01-01' \n
ticker = 'SPY'
'''
def cal_SMA(day, ticker, N):
'''param
day = '2020-01-01' \n
ticker = 'SPY' \n
N = N일 이동평균
'''
day_datetime = datetime.strptime(day, '%Y-%m-%d')
day_before = day_datetime - timedelta(days=400)
ohlc = yf.download(ticker, start=datetime.strftime(day_before, '%Y-%m-%d') , end=datetime.strftime(day_datetime, '%Y-%m-%d'), progress=False)
ohlc[f'SMA{N}'] = ohlc['Adj Close'].rolling(N).mean()
sma = ohlc[f'SMA{N}'][-1]
return sma
rel = round((cal_SMA(day, ticker, 1) / cal_SMA(day, ticker, 240) - 1), 3)
return rel
df = pd.DataFrame(columns=['Value'])
if detail == 'abs':
for i in asset:
df.loc[i] = [cal_momentum(day, i)]
elif detail == 'rel':
for i in asset:
df.loc[i] = [cal_rel_mom(day, i)]
else:
pass
return df['Value'].sort_values(ascending=False)
ABAA 로직은 카나리아 자산의 모멘텀 값이 한 개라도 음수이면 DEFENSE 자산군에서 0, BIL보다 큰 자산을 33%씩 매수하는 것이고, 모두 양수이면 ATTACK 자산군 중 모멘텀이 가장 큰 1개의 자산을 매수하는 것이다.
따라서, ABAA 로직을 실행할 코드를 아래와 같이 설정하였다.
def BAA(day:str, cash:float)->list:
day = datetime.strftime(datetime.strptime(day, '%Y-%m-%d') + timedelta(days=1), '%Y-%m-%d')
def BAA_logic(day:str)->str:
canary = ['SPY', 'VWO', 'VEA', 'BND']
try:
if (find_mom_by('abs', day, canary) >0).value_counts()[False] > 0:
BAA = 'DEF'
else:
pass
except:
BAA = 'ATT'
return BAA
attack = ['QQQ', 'VWO', 'VEA', 'BND']
defense = ['TIP', 'DBC', 'BIL', 'IEF', 'TLT', 'LQD', 'BND']
#
stock_C = {} # 매수종목 초기화
remain_cash = cash
if BAA_logic(day) == 'DEF':
BIL = find_mom_by('rel', day, ['BIL'])[0]
ticker_buy = find_mom_by('rel', day, defense)
df = ticker_buy.to_frame()
df = df[df['Value'] > BIL]
amount_buy = len(df.index)
if amount_buy > 3:
for i in range(0, 3):
price_i = yf.download(df.index[i], start=day, end=day, progress=False)['Adj Close'][0]
num_i = int(0.33 * cash / price_i)
stock_C[df.index[i]] = num_i
remain_cash = remain_cash - num_i * price_i * (1+0.0025)
else:
for i in range(0, amount_buy):
price_i = yf.download(df.index[i], start=day, end=day, progress=False)['Adj Close'][0]
num_i = int(0.33 * cash / price_i)
stock_C[df.index[i]] = num_i
remain_cash = remain_cash - num_i * price_i * (1+0.0025)
elif BAA_logic(day) == 'ATT':
stock = find_mom_by('rel', day, attack).index[0]
price_i = yf.download(stock, start=day, end=day, progress=False)['Adj Close'][0]
num_i = int(cash / price_i)
remain_cash = remain_cash - num_i * price_i * (1+0.0025)
stock_C[stock] = num_i
else:
print('error)')
return [round(remain_cash,2), str(stock_C)]
이를 이용하여 얻어진 df를 backtest함수를 이용하여 백테스트하면 마지막과 같은 결과가 생성된다.
def backtest(df_init, init_cash:float, add_money:float, rebalance_day:int):
'''param
df init = df type, SPY, ^IXIC as standard
init_cash = 1000 USD
add_money = 1000 USD by MONTH
'''
list_day = []
list_cash = []
list_close_s = []
list_exp = []
num = 0 #거래 횟수 초기화
for i in range(0, len(df_init.index)):
day = df_init.index[i]
if (i != 0) and ((i+1) % rebalance_day != 0):
open_s = close_s
cash = remain_cash
close_s = open_s
remain_cash = cash
exp = cal_exp(day, remain_cash, close_s, 'Adj Close')
elif (i+1) % rebalance_day == 0: #일(영업일) 단위 추가금 및 리밸런싱
open_s = close_s
cash = remain_cash
cash = sell_everything(day, cash, open_s)[0] # 보유 모두 매도
cash = cash + add_money # 월 추가금액
remain_cash = BAA(day, cash)[0]
close_s = BAA(day, cash)[1]
exp = cal_exp(day, remain_cash, close_s, 'Adj Close')
num += 1
elif i == 0: #1일차
cash = init_cash
open_s = '{}'
remain_cash = BAA(day, cash)[0]
close_s = BAA(day, cash)[1]
exp = cal_exp(day, remain_cash, close_s, 'Adj Close')
else:
print('Backtest Error')
#list_day.append(df_init.index[i])
list_cash.append(cash)
list_close_s.append(close_s)
list_exp.append(exp)
df_init['Cash'] = list_cash
df_init['Close_S'] = list_close_s
df_init['Exp'] = list_exp
return df_init, num
Results
본 논문에서는 월 말 단위 리밸런싱을 통한 전략을 수립했다고 쓰여 있다. 이를 차용하여 20 영업일마다 매도 후 매수하는 전략으로 리밸런싱 하였다. 최근 10년 결과는 CAGR 7.12%, MDD -13.65%를 기록하였다. 논문과 일부 차이가 있는 이유는 수수료를 0.1%보다 보수적으로 설정했기 때문이다. (매수 : 0.25%, 매도 : 0.08%) 아무래도 매월 리밸런싱 전략에서는 수수료로 인한 수익이 낮게 나타날 수밖에 없다.
Keller 논문의 로직은 위의 결과처럼, 초기 자본 10000 USD, 0 USD를 매월 추가하였고, 20일 리밸런싱을 실정 하였다.
그렇다면 리밸런싱 기간을 조정하여 포트폴리오를 운영한다면 어떨까? 리밸런싱 변수를 가지고 pytorch 등을 이용하여 최적의 값을 딥 러닝을 이용하여 얻을 수 있지만, 본 포스트에서는 일단 간단하게 수치 변경을 통해서 로직을 검증해 보았다.
먼저 아래와 같이 10일 단위로 리밸런싱 주기를 짧게 설정하여 보았다.
CAGR이 대폭 감소하는 것을 알 수 있다. 이는 설정한 포트폴리오 전략(ATTACK, DEFENSE)이 충분하게 이익을 실현하기 전에 매도하는 것으로 생각된다.
15, 30, 40일 단위로 리밸런싱 했을 때, 위와 같은 결과가 나타난다. 40일 단위로 리밸런싱 했을 때는(2달 주기 리밸런싱) 특이하게 매우 높은 MDD와 낮은 CAGR를 보였다. 이는 경제위기 때 충분하게 이겨낼 수 없는다는 결과처럼 보인다.
그렇다면 LAA, 영구 포트폴리오, 올웨더 포트폴리오 등 리밸런싱 주기가 보다 긴 포트폴리오들처럼 60 영업일(3달), 120 영업일(6개월), 240 영업일(1년)로 리밸런싱 한다면 어떨까?
이처럼 전략을 길게 가져간다면 MDD는 -28% 정도로, 리스크가 있는 전략이지만 15% 정도로 상대적으로 높은 CAGR을 나타냄을 알 수 있었다. ABAA전략의 단점은 최근 10년에서 60/40 전략 혹은 SPY보다 수익률이 낮다는 것이다. 리밸런싱 주기를 논문에서 제시한 1달이 아닌 1년으로 하게 되면 복잡한 ABAA전략을 사용하면서도 높은 CAGR을 가져갈 수 있다는 장점이 있다. 10%의 CAGR과 10%의 MDD를 가져가는 것과 15%의 CAGR과 28%의 MDD를 가져가는 것을 비교해 보았을 때, 전자가 훨씬 나은 전략인 것처럼 보이지만, 다른 알고리즘이나 로직의 합성을 이용한다면 ABAA전략을 효과적으로 활용할 수 있을 것으로 보인다.
'일상 이야기 > Daily Quant' 카테고리의 다른 글
[7] 하락장을 대처하는 법 (0) | 2022.10.11 |
---|---|
[6] 평균 보유 일수에 관해서 (0) | 2022.09.26 |
[4] 전략을 설정 할 때 생각해야 할 것 (0) | 2022.09.02 |
[3] 퀀트전략 : 수급, 가격, 모멘텀 점수 (0) | 2022.08.30 |
[2] 퀀트전략 : PBR < 1 + 마켓 타이밍 전략 (0) | 2022.08.29 |
댓글
이 글 공유하기
다른 글
-
[7] 하락장을 대처하는 법
[7] 하락장을 대처하는 법
2022.10.11 -
[6] 평균 보유 일수에 관해서
[6] 평균 보유 일수에 관해서
2022.09.26 -
[4] 전략을 설정 할 때 생각해야 할 것
[4] 전략을 설정 할 때 생각해야 할 것
2022.09.02 -
[3] 퀀트전략 : 수급, 가격, 모멘텀 점수
[3] 퀀트전략 : 수급, 가격, 모멘텀 점수
2022.08.30