[자산배분#4 - 코딩] 영구포트폴리오 (PP : Permanent Portfoilio)
영구 포트폴리오 코딩 방법에 대해 글을 써 보겠습니다.
영구 포트폴리오 기본
# Makeup Universe
Universe = ['SPY','SHY','GLD','TLT']
df_PP = get_yahoo_price_data(Universe, start_day, end_day)
df_PP.index = df_PP.index.values.astype('<M8[m]')
# Duration of backtest
df_PP = df_PP[start_day:end_day]
우선 자산군을 선택하고 백테스트 기간을 입력합니다.
# calculate profit
for i in range(len(Universe)):
df_PP.rename(columns = {Universe[i]:'ASSET'+'{}'.format(i)}, inplace = True)
col_list_P = [col+'_P' for col in df_PP.columns]
df_PP[col_list_P] = df_PP.pct_change()
df_PP[col_list_P] = df_PP[col_list_P]*100
# calculate balance
df_PP = Cal_PP_Profit(df_PP,Universe)
return df_PP
우선 각 자산군의 이름을 ASSETx 로 변경합니다 (자산군을 변경하면서 테스트 할수 있도록)
각 자산군의 수익률을 계산합니다.
이전에 설명한 것과 같이 pct_change() 메서드를 사용하면 간단하게 계산이 가능합니다.
그리고 영구포트폴리오의 메인 함수를 호출하여 줍니다.
def Cal_PP_Profit(df_PP,Universe):
Universe_Cnt = len(Universe)
Initial_Balance = 100
for j in range(Universe_Cnt):
df_PP['ASSET'+'{}'.format(j)+'_BAL'] = 0
df_PP['ASSET'+'{}'.format(j)+'_MDD'] = 1
for i in range(len(df_PP)):
date = df_PP.index[i]
total_Bal = 0
# Check Rebalancing Date
if date.day == 30 and date.month == 12:
df_PP.loc[date,'REBAL_CHK'] = True
else:
df_PP.loc[date,'REBAL_CHK'] = False
1. 초기 자산은 100 으로 설정
2. 각각의 자산군의 Balance MDD 초기값 세팅
3. 리벨런싱은 12월 30일에 수행합니다.
# Calculate Balance
for j in range(Universe_Cnt):
if i==0:
temp = Initial_Balance/Universe_Cnt
df_PP.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
else:
temp = (df_PP['ASSET'+'{}'.format(j)+'_BAL'].iloc[i-1] * (1 + df_PP['ASSET'+'{}'.format(j)+'_P'].iloc[i]/100))
df_PP.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = temp
total_Bal = total_Bal + (df_PP['ASSET'+'{}'.format(j)+'_BAL'].iloc[i])
전체 자산을 구하는 코드입니다.
초기값 i = 0 인 경우 기초 자산 (Initial_Balance) 를 균등하게 분배합니다. (4개의 자산군이므로 25% 씩 할당 되겠습니다)
각 자산군의 이전 자산에 수익률을 곱하여 계산해 줍니다.
그리고 모든 자산군의 합을 Total_bal 이라는 전체 자산으로 계산합니다.
# Rebalancing..
for j in range(Universe_Cnt):
if ( df_PP['REBAL_CHK'].iloc[i] == True):
df_PP.loc[date,'ASSET'+'{}'.format(j)+'_BAL'] = total_Bal / Universe_Cnt
else:pass
df_PP.loc[date,'PP_BAL'] = total_Bal
return df_PP
리벨런싱 코드 입니다.
위에서 12월 30일을 리벨런싱 시점으로 지정하였고, 해당 일자에 전체 total_balance 를 균등 분배 해 줍니다.
그리고 전체 df_PP 를 반환 합니다.
*MDD 는 Quantstats 에서 자동 계산해 주어 따로 코드를 작성할 필요는 없으나,
matplot 으로 시각화 하고 싶은 경우 아래와 같이 작성 가능합니다.
df_PP = df_PP.assign(PP_DD=lambda x: -(df_PP['PP_BAL'].cummax() - df_PP['PP_BAL']) / df_PP['PP_BAL'].cummax())
df_PP['PP_DD'] = df_PP['PP_DD'] * 100
MDD 계산식은 아래와 같으며 cummax() 라는 메서드로 손쉽게 계산 가능합니다.
% MDD = - (고점 - 현재값) / 고점
qs.reports.html(df_AA['PP_BAL'],'SPY', mode='full', title='PP(ORI)', download_filename='PP.html')
자 이제 Quantstats 함수를 이용해 레포트를 추출하도록 하겠습니다.
정상적으로 리포트가 생성 되었습니다.
벤치마크 (SPY) 와 비교하였을때 CAGR 은 -7% 정도 하락하였지만, MDD 는 -4% 정도밖에 감소하지 않았습니다.
2005년 부터 Portfolio visualize 로 계산한 값과 비교하였을때 영구포트폴리오의 약점이 확연히 드러 납니다.
최근 10년과 같이 금융위기와 같은 System Risk 가 없는 (큰폭의 하락이 없는) 구간에서는 MDD 방어가 잘 되지 않을 뿐더러 수익률을 많이 깎아 먹고 있습니다.
영구 포트폴리오 (레버리지)
레버리지를 사용하면 어떻게 될까요?
SPY 대신 SSO (S&P500 2배 레버리지) 를 사용해 보겠습니다.
2배 레버리지를 사용하여 리스크 테이큰 하였음에도 수익률은 S&P500 을 따라가지 못하였습니다.
더군다나 마켓타이밍 장치가 없기 때문에 MDD 는 32% 언더워터 기간은 두배로 증가하였습니다.
따라서 영구포트폴리오 내에서 레버리지 사용은 별로 좋지 않다는 결론입니다.