[PortfolioOptimization] Maximum Diversification


개요

Maximum Diversification 방식의 포트폴리오 최적화를 구현해 봅니다.

import pandas as pd
import pandas_datareader.data as web
import datetime
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import math
import pyfolio as pf
import quantstats
plt.rcParams["figure.figsize"] = (10, 6) # (w, h)
import sys
from scipy.stats import rankdata
from scipy.stats import stats
from scipy.optimize import minimize

Maximum Diversification 방식의 포트폴리오 최적화를 구현해 보겠습니다. 분산효과 DR을 최대화하는 것은 -DR을 최소화하는 것과 같습니다.

def MaximumDiversification(rets, lb, ub):
    
    covmat = pd.DataFrame.cov(rets)
    
    def MaxDivObjective(x):
        # average weighted vol
        #x_vol = np.dot(np.sqrt(np.diag(covmat), x.T))
        x_vol = x.T @ np.sqrt(np.diag(covmat))
        # portfolio vol. @: matrix multiplication
        variance = x.T @ covmat @ x
        port_vol = variance ** 0.5
        
        diversification_ratio = x_vol / port_vol
        
        return -diversification_ratio
    
    def minimum_weight(x):
        return x
    
    def sum_weight(x):
        return (sum(x)-1)
    
    x0 = np.repeat(1/covmat.shape[1], covmat.shape[1])
    lbound  = np.repeat(lb, covmat.shape[1])
    ubound  = np.repeat(ub, covmat.shape[1])
    bnds = tuple(zip(lbound, ubound))
    constraints = ({'type': 'ineq', 'fun': minimum_weight},
                   {'type': 'eq', 'fun': sum_weight})
    options = {'ftol': 1e-20, 'maxiter': 1000}
    
    result = minimize(fun = MaxDivObjective,
                      x0 = x0,
                      method = 'SLSQP',
                      constraints = constraints,
                      options = options,
                      bounds = bnds)
    return(result.x)

미국 주식, 미국 장기 국채, 금, 원자재에 대해서 실험해 보겠습니다. 비중 합은 1로 하고, 모두 0 이상으로 가져가게 해서 레버리지는 안 하는 것으로 가정합니다. Rolling period는 12개월로(252거래일) 해 보겠습니다.

start = '2006-07-21'
end = '2021-05-19'

vti = web.DataReader("VTI", 'yahoo', start, end)['Adj Close'].to_frame("vti")
tlt = web.DataReader("TLT", 'yahoo', start, end)['Adj Close'].to_frame("tlt")
iau = web.DataReader("IAU", 'yahoo', start, end)['Adj Close'].to_frame("iau")
gsg = web.DataReader("GSG", 'yahoo', start, end)['Adj Close'].to_frame("gsg")
price_df = pd.concat([vti, tlt, iau, gsg], axis=1)
return_df = price_df.pct_change().dropna(axis=0)
# 252 days(12 months) 기준으로 rolling 
weight_df = pd.DataFrame(columns=['vti','tlt','iau','gsg'])

for i in range(len(return_df)-252):
    
    print(i)
    
    # 253일 분량 데이터 잘라오기 (for문 돌 때마다 index 0~252, 1~253, 2~254, ...)
    temp_return_df = return_df.iloc[i:(i+253), :]
    
    # 253일 중 252일치를 계산에 사용하고 253번째 행은 날짜를 추출
    date_index = str(temp_return_df.iloc[252, :].name)[0:10]
    # 최소/최대 보유 비중은 0.01과 0.5 (최소 1%는 편입, 최대 50%까지만 편입. 0 ~ 100% 사이에서 자유 조정 가능)
    temp = pd.DataFrame(MaximumDiversification(temp_return_df.iloc[0:252, :], 0.01, 0.5), index=['vti','tlt','iau','gsg'], columns=[date_index]).T
    
    weight_df = pd.concat([weight_df, temp])

weight_df
vtitltiaugsg
2007-07-250.2789860.5000000.0855330.135481
2007-07-260.2819840.4994640.0816600.136892
2007-07-270.2832410.5000000.0787520.138007
2007-07-300.2830820.5000000.0775080.139410
2007-07-310.2830980.5000000.0770260.139876
...............
2021-05-130.2068850.4593830.1437990.189933
2021-05-140.2071870.4584330.1431070.191273
2021-05-170.2017110.4581920.1430130.197085
2021-05-180.2023910.4579230.1428730.196814
2021-05-190.2027110.4533250.1467200.197245

3480 rows × 4 columns

장기 국채인 TLT는 비중 상한인 50%에 닿는 경우가 꽤 많습니다. Maximum Diversification이 꼭 최고의 성과를 준다기보다는 이런 방법도 테스트해볼 겸 한 것이라서 괜찮습니다.

weight_df.plot(title = 'Weights for each asset class')

output_8_1

max_div_return = (weight_df['vti'] * return_df.iloc[252:, :]['vti'] +
                  weight_df['tlt'] * return_df.iloc[252:, :]['tlt'] + 
                  weight_df['iau'] * return_df.iloc[252:, :]['iau'] +
                  weight_df['gsg'] * return_df.iloc[252:, :]['gsg'])
        
max_div_return
2007-07-25    0.002205
2007-07-26   -0.004458
2007-07-27   -0.002834
2007-07-30    0.003076
2007-07-31    0.001080
                ...   
2021-05-13   -0.001327
2021-05-14    0.010107
2021-05-17    0.003095
2021-05-18   -0.003733
2021-05-19   -0.005739
Length: 3480, dtype: float64
max_div_return.index
Index(['2007-07-25', '2007-07-26', '2007-07-27', '2007-07-30', '2007-07-31',
       '2007-08-01', '2007-08-02', '2007-08-03', '2007-08-06', '2007-08-07',
       ...
       '2021-05-06', '2021-05-07', '2021-05-10', '2021-05-11', '2021-05-12',
       '2021-05-13', '2021-05-14', '2021-05-17', '2021-05-18', '2021-05-19'],
      dtype='object', length=3480)

인덱스가 DatetimeIndex 형식이 아니므로 DatetimeIndex 형식으로 변환합니다.

max_div_return.index = pd.to_datetime(max_div_return.index)
max_div_return.index
DatetimeIndex(['2007-07-25', '2007-07-26', '2007-07-27', '2007-07-30',
               '2007-07-31', '2007-08-01', '2007-08-02', '2007-08-03',
               '2007-08-06', '2007-08-07',
               ...
               '2021-05-06', '2021-05-07', '2021-05-10', '2021-05-11',
               '2021-05-12', '2021-05-13', '2021-05-14', '2021-05-17',
               '2021-05-18', '2021-05-19'],
              dtype='datetime64[ns]', length=3480, freq=None)

위험 균형 전략에 목표 변동성을 더했던(risk parity with target vol) 테스트보다 샤프 비율이 잘 나옵니다. 레버리지를 사용하면 샤프 비율이 내려가는 경우가 많으니 감안해서 보아야 하지만, maximum diversification도 괜찮은 방법이라는 것 정도는 알 수 있습니다.

quantstats.reports.plots(max_div_return, mode='basic')

output_15_0

output_15_1

quantstats.reports.metrics(max_div_return, mode='full')
                           Strategy
-------------------------  ----------
Start Period               2007-07-25
End Period                 2021-05-19
Risk-Free Rate             0.0%
Time in Market             100.0%

Cumulative Return          163.93%
CAGR%                      7.27%
Sharpe                     0.87
Sortino                    1.23
Max Drawdown               -18.15%
Longest DD Days            553
Volatility (ann.)          8.54%
Calmar                     0.4
Skew                       -0.43
Kurtosis                   7.5

Expected Daily %           0.03%
Expected Monthly %         0.58%
Expected Yearly %          6.68%
Kelly Criterion            7.72%
Risk of Ruin               0.0%
Daily Value-at-Risk        -0.86%
Expected Shortfall (cVaR)  -0.86%

Payoff Ratio               0.98
Profit Factor              1.17
Common Sense Ratio         1.12
CPC Index                  0.62
Tail Ratio                 0.96
Outlier Win Ratio          3.8
Outlier Loss Ratio         3.71

MTD                        -0.46%
3M                         -1.04%
6M                         1.17%
YTD                        -1.71%
1Y                         9.88%
3Y (ann.)                  8.55%
5Y (ann.)                  8.02%
10Y (ann.)                 7.18%
All-time (ann.)            7.27%

Best Day                   3.46%
Worst Day                  -5.28%
Best Month                 7.78%
Worst Month                -10.93%
Best Year                  20.4%
Worst Year                 -6.22%

Avg. Drawdown              -1.37%
Avg. Drawdown Days         30
Recovery Factor            9.03
Ulcer Index                1.02

Avg. Up Month              1.86%
Avg. Down Month            -1.74%
Win Days %                 54.34%
Win Month %                65.27%
Win Quarter %              75.0%
Win Year %                 73.33%
quantstats.reports.html(max_div_return, output='Report_MAXDIV.html', title='Maximum Diversification')





© 2021.03. by JacobJinwonLee

Powered by theorydb