본문 바로가기
문송충의 코딩하기/퀀트 투자 스터디

[동퀀트] VAA(Vigilant Asset Allocation) 동적자산배분 파이썬으로 백테스팅

by 동장군님 2022. 2. 2.
728x90
반응형

강환국 유튜버께서 소개한 복리 18% 전략 VAA에 흥미가 생겨서 한번 백테스트를 해보고자 한다. 파이썬 실력이 너무 부족해서 백테스트를 하는데 굉장히 힘들었다.(이게 정말로 이렇게 하는 것인지도 잘 모르겠다.)

 


 

VAA 전략 소개

공격 자산: SPY, VEA, VWO, AGG

수비 자산: SHV, GOVT(원래는 IEF이지만 출시한 지 얼마 되지 않아서 교체), LQD

 

1. 위 7개 자산의 모멘텀 스코어 계산

(스코어 계산 공식: 최근 1개월 수익률 * 12 + 최근 3개월 수익률 * 4 + 최근 6개월 수익률 *2 + 최근 12개월 수익률 * 1)

 

2. 자산 매수

공격 자산 4개 모멘텀 스코어가 0 이상일 경우 4개 자산 중 가장 모멘텀 스코어가 높은 자산 매수

공격 자산 4개 자산 중 하나라도 모멘텀 스코어가 0 이하일 경우 수비 자산 중 모멘텀 스코어가 높은 자산 매수

 

3. 매월말 리밸런싱

 


 

7개 자산별 모멘텀 스코어 계산

 

우선 아래 코드를 통해서 자산별 가격, 기간별 수익률 그리고 모멘텀 스코어를 계산해보도록 하겠다.

import FinanceDataReader as fdr
import pandas as pd

spy = fdr.DataReader('SPY', '2013-01-01', '2022-01-31')
spy['close_1m']=(spy['Close']/spy['Close'].shift(20)-1)*12
spy['close_3m']=(spy['Close']/spy['Close'].shift(60)-1)*4
spy['close_6m']=(spy['Close']/spy['Close'].shift(120)-1)*2
spy['close_12m']=spy['Close']/spy['Close'].shift(240)-1
spy['score']=spy['close_1m']+spy['close_3m']+spy['close_6m']+spy['close_12m']
spy=spy[['Close','score']]
spy.columns=['Close_spy','Score_spy']

vea = fdr.DataReader('VEA', '2013-01-01', '2022-01-31')
vea['close_1m']=(vea['Close']/vea['Close'].shift(20)-1)*12
vea['close_3m']=(vea['Close']/vea['Close'].shift(60)-1)*4
vea['close_6m']=(vea['Close']/vea['Close'].shift(120)-1)*2
vea['close_12m']=vea['Close']/vea['Close'].shift(240)-1
vea['score']=vea['close_1m']+vea['close_3m']+vea['close_6m']+vea['close_12m']
vea=vea[['Close','score']]
vea.columns=['Close_vea','Score_vea']

vwo = fdr.DataReader('VWO', '2013-01-01', '2022-01-31')
vwo['close_1m']=(vwo['Close']/vwo['Close'].shift(20)-1)*12
vwo['close_3m']=(vwo['Close']/vwo['Close'].shift(60)-1)*4
vwo['close_6m']=(vwo['Close']/vwo['Close'].shift(120)-1)*2
vwo['close_12m']=vwo['Close']/vwo['Close'].shift(240)-1
vwo['score']=vwo['close_1m']+vwo['close_3m']+vwo['close_6m']+vwo['close_12m']
vwo=vwo[['Close','score']]
vwo.columns=['Close_vwo','Score_vwo']

agg = fdr.DataReader('AGG', '2013-01-01', '2022-01-31')
agg['close_1m']=(agg['Close']/agg['Close'].shift(20)-1)*12
agg['close_3m']=(agg['Close']/agg['Close'].shift(60)-1)*4
agg['close_6m']=(agg['Close']/agg['Close'].shift(120)-1)*2
agg['close_12m']=agg['Close']/agg['Close'].shift(240)-1
agg['score']=agg['close_1m']+agg['close_3m']+agg['close_6m']+agg['close_12m']
agg=agg[['Close','score']]
agg.columns=['Close_agg','Score_agg']

shv = fdr.DataReader('SHV', '2013-01-01', '2022-01-31')
shv['close_1m']=(shv['Close']/shv['Close'].shift(20)-1)*12
shv['close_3m']=(shv['Close']/shv['Close'].shift(60)-1)*4
shv['close_6m']=(shv['Close']/shv['Close'].shift(120)-1)*2
shv['close_12m']=shv['Close']/shv['Close'].shift(240)-1
shv['score']=shv['close_1m']+shv['close_3m']+shv['close_6m']+shv['close_12m']
shv=shv[['Close','score']]
shv.columns=['Close_shv','Score_shv']

govt = fdr.DataReader('GOVT', '2013-01-01', '2022-01-31')
govt['close_1m']=(govt['Close']/govt['Close'].shift(20)-1)*12
govt['close_3m']=(govt['Close']/govt['Close'].shift(60)-1)*4
govt['close_6m']=(govt['Close']/govt['Close'].shift(120)-1)*2
govt['close_12m']=govt['Close']/govt['Close'].shift(240)-1
govt['score']=govt['close_1m']+govt['close_3m']+govt['close_6m']+govt['close_12m']
govt=govt[['Close','score']]
govt.columns=['Close_govt','Score_govt']

lqd = fdr.DataReader('LQD', '2013-01-01', '2022-01-31')
lqd['close_1m']=(lqd['Close']/lqd['Close'].shift(20)-1)*12
lqd['close_3m']=(lqd['Close']/lqd['Close'].shift(60)-1)*4
lqd['close_6m']=(lqd['Close']/lqd['Close'].shift(120)-1)*2
lqd['close_12m']=lqd['Close']/lqd['Close'].shift(240)-1
lqd['score']=lqd['close_1m']+lqd['close_3m']+lqd['close_6m']+lqd['close_12m']
lqd=lqd[['Close','score']]
lqd.columns=['Close_lqd','Score_lqd']

df=pd.concat([spy,vea,vwo,agg,shv,govt,lqd],axis=1)

위 코드가 잘 돌아간다면 아래와 같이 2013년부터 현재까지 자산별 가격과 모멘텀 스코어를 확인할 수 있을 것이다. 

 

 

공격 vs 수비 자산 구매 여부 확인

 

다음으로 모멘텀 스코어을 통해 매달 공격과 수비 중 어느 자산을 매입해야 되는지 확인하도록 하겠다. 

 

모멘텀 스코어가 양수면 1점 음수면 0점으로 공격자산의 모멘텀 스코어를 더해서 공격자산 매수 여부를 결정하도록 만들었다. 즉 4점이면 공격자산 매수, 4점 미만이면 수비 자산을 매수하게 만들도록 하겠다. 

df['Score_spy_t']=df['Score_spy'].apply(lambda x: 1 if x>0 else 0)
df['Score_vea_t']=df['Score_vea'].apply(lambda x: 1 if x>0 else 0)
df['Score_vwo_t']=df['Score_vwo'].apply(lambda x: 1 if x>0 else 0)
df['Score_agg_t']=df['Score_agg'].apply(lambda x: 1 if x>0 else 0)

df['true_false']=df['Score_spy_t']+df['Score_vea_t']+df['Score_vwo_t']+df['Score_agg_t']
df=df.reset_index()
df['month']=df['Date'].apply(lambda x: str(x.year) + str(x.month) )
df['rank']=df.groupby('month')['Date'].rank(method='first')

dfa=df[df['rank']==1]

dfa['month']=dfa['Date'].apply(lambda x: str(x.year) + str(x.month) )
dfa['rank']=dfa.groupby('month')['Date'].rank(method='first')
dfaa=dfa[dfa['rank']==1]
dfaa=dfaa[~dfaa['Score_spy'].isnull()]

price_dict_=[]
for i in range(len(dfaa)):
    
    price_dict=dict()
    
    price_dict['SPY']=tb['Close_spy'].values[i]
    price_dict['VWO']=tb['Close_vwo'].values[i]
    price_dict['VEA']=tb['Close_vea'].values[i]
    price_dict['AGG']=tb['Close_agg'].values[i]
    price_dict['SHV']=tb['Close_shv'].values[i]
    price_dict['GOVT']=tb['Close_govt'].values[i]
    price_dict['LQD']=tb['Close_lqd'].values[i]
    
    price_dict_.append(price_dict)
    
dfaa['dict']=price_dict_

 

매월 매수 자산

 

매월 공격과 수비 중 한 자산을 매수하기로 결정했다면 다음 스텝으로는 그 중 어떤 자산을 매입해야 할지 결정하는 것이다. 아래 코드와 같이 공격자산으로 결정했다면 공격자산 4개 모두 모멘텀 스코어가 양수여야하고, 하나라도 음수면 수비자산을 매수하기로 코드를 작성했다. 

 

assets_=[]
close_=[]

def get_key(val,dict_):
    for key, value in dict_.items():
         if val == value:
             return key
 
    return "There is no such Key"

for i in range(len(dfaa)):
    if dfaa['true_false'].values[i]==4:
        score={
            'SPY': dfaa['Score_spy'].values[i],'VEA': dfaa['Score_vea'].values[i],
            'VWO': dfaa['Score_vwo'].values[i],'AGG': dfaa['Score_agg'].values[i]
        }
        key=get_key(max(score.values()),score)       
        buy_price=dfaa['dict'].values[i][key]

    else:
        score={
            'SHV': dfaa['Score_shv'].values[i],'GOVT': dfaa['Score_govt'].values[i],
            'LQD': dfaa['Score_lqd'].values[i]
        }
        key=get_key(max(score.values()),score)    
        buy_price=dfaa['dict'].values[i][key]
        
    assets_.append(key)
    close_.append(buy_price)
    
dfaa['assets']=assets_
dfaa['close']=close_

$1000  초기 투자 자금으로 매월 리밸런싱

초기 투하 자본을 1000 달러로 가정하고 2014년 ~ 2022년 CAGR를 계산해보도록 하겠다.

 

dfaa['assets_']=dfaa['assets'].shift(1)
dfaa['close_']=dfaa['close'].shift(1)

profit=[]
amount=1000

for u in range(len(dfaa)):
    if u ==0:
        profit.append(0)
        continue
    else:
        if dfaa['assets_'].values[u] == dfaa['assets'].values[u]:
            
            number=math.floor(amount/dfaa['close_'].values[u])
            temp=dfaa['close'].values[u] * number - dfaa['close_'].values[u] * number

            profit.append(temp)
            amount+=temp

        else:
            number=math.floor(amount/dfaa['close_'].values[u])
            temp=dfaa['dict'].values[u][dfaa['assets_'].values[u]] * number-dfaa['close_'].values[u] * number
            profit.append(temp)
            amount+=temp
            
  dfaa['profit']=profit
  (amount/1000)**(1/8)-1

 

최종적으로 받은 amount를 기준으로 CAGR을 계산하면 약 4.5%에 불과하다. 여기서 배당 수익을 더한더라도 매월 리밸런싱에 따른 거래 비용을 감안한다면 4% 안팍으로 수익률이 결정될 것으로 보인다. 분명히 강환국 유튜브에서는 두 자리 수익률이 나온다고 했는데 내 백테스팅은 다른 결과가 나타났다. 내 코드가 문제가 있는지 모르겠지만 여하튼 지금 결과만 보면 생각보다 너무 별로다.

 

728x90
반응형

댓글