가치투자퀀트

[자산배분#4 - 코딩] 영구포트폴리오 (PP : Permanent Portfoilio) 본문

자산배분

[자산배분#4 - 코딩] 영구포트폴리오 (PP : Permanent Portfoilio)

곰발이 2022. 9. 12. 09:00

영구 포트폴리오 코딩 방법에 대해 글을 써 보겠습니다.

 

영구 포트폴리오 기본

# 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 로 계산한 값과 비교하였을때 영구포트폴리오의 약점이 확연히 드러 납니다.

2005- 2022

 최근 10년과 같이 금융위기와 같은 System Risk 가 없는 (큰폭의 하락이 없는) 구간에서는 MDD 방어가 잘 되지 않을 뿐더러 수익률을 많이 깎아 먹고 있습니다.

 

영구 포트폴리오 (레버리지)

 

레버리지를 사용하면 어떻게 될까요?

SPY 대신 SSO (S&P500 2배 레버리지) 를 사용해 보겠습니다.

 

2배 레버리지를 사용하여 리스크 테이큰 하였음에도 수익률은 S&P500 을 따라가지 못하였습니다.

더군다나 마켓타이밍 장치가 없기 때문에 MDD 는 32% 언더워터 기간은 두배로 증가하였습니다.

 

따라서 영구포트폴리오 내에서 레버리지 사용은 별로 좋지 않다는 결론입니다.

 

 

Comments