퀀트

26. 해리 마코위츠의 포트폴리오 이론(분산투자의 필요성)

만 기 2023. 1. 16. 10:51

 

분산 투자의 필요성 - 해리 마코위츠의 포트폴리오 이론

자산을 분산투자하여 포트폴리오를 만들게 되면 분산투자 전보다 위험을 감소시킬 수 있다는 이론이다.

포트폴리오 이론의 가정에 따르면 투자자들은 투자안의 의사결정과정에서 고려하는 수익과 위험은 각각 평균과 분산으로 표현할 수 있으며, 포트폴리오를 구성할 경우 자산 간의 상관계수가 1인 경우가 아니라면 분산이 감소함을 통해 이득을 얻을 수 있다고 본다.

 

 

효율적 투자선(Efficient Frontier)

표준편차(변동성)을 최소로 하면서 최대의 기대 수익률을 얻을 수 있도록 포트폴리오 비율을 구성하는 기준

 

평균-분산 최적화(Mean-Variance Optimization)

최적의 비중을 계산하기 위해서는 평균-분산 최적화를 수행해야한다.

 

 

PyPortfolioOpt

Portfolio Optimization을 위한 Python 라이브러리.

Optimization을 라이브러리 자체적으로 실행해준다.

보유한 자산군 내에서 효율적 투자선에 기반한 최적의 종목 구성 비율을 구할 수 있다.

documents :

User Guide - PyPortfolioOpt 1.5.2 documentation

 

User Guide — PyPortfolioOpt 1.5.2 documentation

Mean-variance optimization is based on Harry Markowitz’s 1952 classic paper , which spearheaded the transformation of portfolio management from an art into a science. The key insight is that by combining assets with different expected returns and volatil

pyportfolioopt.readthedocs.io

 

 

PyPortfolioOpt 사용해서 올웨더 전략의 최적 비중 구해보기 (김단테)

데이터 준비

# 종목 리스트
stock_list = [
  ["VT", "VT"],
  ["EDV", "EDV"],
  ["VCLT", "VCLT"],
  ["EMLC", "EMLC"],
  ["LTPZ", "LTPZ"],
  ["IAU", "IAU"],
  ["DBC", "DBC"],
]


# 종가데이터 불러와서 데이터프레임 만들기
df_list = [fdr.DataReader(code, '2015-07-07	')['Close'] for name, code in stock_list]

df = pd.concat(df_list, axis=1)
df.columns = [name for name, code in stock_list]

 

평균분산값

from pypfopt.expected_returns import mean_historical_return
from pypfopt.risk_models import CovarianceShrinkage

# 평균 기대 수익률
mu = mean_historical_return(df)
# 공분산 추정치
S = CovarianceShrinkage(df).ledoit_wolf()

 

효율적 투자선(efficient frontier) 적용

from pypfopt.efficient_frontier import EfficientFrontier

# 평균 분산 값 넣어서 효율적 투자선 적용
ef = EfficientFrontier(mu, S)
# sharpe ratio 최댓값
weights = ef.max_sharpe()


# 최적 비중(가중치)
cleaned_weights = ef.clean_weights()
ef.save_weights_to_file("weights.txt")  # saves to file
print(cleaned_weights)
# [('VT', 0.3815), ('EDV', 0.0), ('VCLT', 0.0), ('EMLC', 0.0), ('LTPZ', 0.0), ('IAU', 0.29016), ('DBC', 0.32835)]


# 예상 수익률
ef.portfolio_performance(verbose=True)
# Expected annual return: 7.2%
# Annual volatility: 9.7%
# Sharpe Ratio: 0.54
# (0.07238239319677445, 0.09742134031129751, 0.5376891041469268)
  • 최적 비중에서 특정종목의 비중이 0이 되는 문제가 있다.
    • L2 레귤라이제이션 적용 필요

 

L2 Regularization 적용

from pypfopt import objective_functions

ef = EfficientFrontier(mu, S)
ef.add_objective(objective_functions.L2_reg, gamma=0.1)
w = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
ef.save_weights_to_file("weights.txt")  # saves to file
print(cleaned_weights)
  • gamma 값을 조정하여 특정종목 비중이 0이 되는 것을 방지할 수 있다.

 

올웨더 비중 대입해서 비교

df['올웨더_김단테'] = 0.35 * df['VT'] + \
                   0.20 * df['EDV'] + \
                   0.075 * df['VCLT'] + \
                   0.075 * df['EMLC'] + \
                   0.20 * df['LTPZ'] + \
                   0.05 * df['IAU'] + \
                   0.05 * df['DBC']
df['올웨더_ef최적화'] = 0.39582 * df['VT'] + \
                   0.0 * df['EDV'] + \
                   0.0 * df['VCLT'] + \
                   0.0 * df['EMLC'] + \
                   0.24676 * df['LTPZ'] + \
                   0.35742 * df['IAU'] + \
                   0.0 * df['DBC']
df['올웨더_ef최적화_L2_Regularization'] = 0.38482 * df['VT'] + \
                   0.05017 * df['EDV'] + \
                   0.03555 * df['VCLT'] + \
                   0.0 * df['EMLC'] + \
                   0.19087 * df['LTPZ'] + \
                   0.29412 * df['IAU'] + \
                   0.04448 * df['DBC']

 

수익률 그래프

# 첫 날을 기준으로 상대 수익률 그려보기
df_norm = df / df.iloc[0] - 1
df_norm.plot(figsize=(20,6));
plt.title('상대 수익률');


# 수익률 정렬
df_norm.iloc[-1].sort_values(ascending=False)

⇒ 전체 데이터를 바탕으로 optimization을 했으므로, 이 데이터 구간에서는 지금 구한 최적화 비중이 당연히 좋은 것이다.

⇒ 따라서 트레이닝 데이터와 테스트 데이터를 분리해서 작업해야한다.

 

Training

# training 구간 : 2015-07-07 ~ 2019-12-31
# test 구간 : 2020-01-01 ~ 현재
df_list = [fdr.DataReader(code, '2015-07-07', '2019-12-31')['Close'] for name, code in stock_list]

df = pd.concat(df_list, axis=1)
df.columns = [name for name, code in stock_list]


from pypfopt.expected_returns import mean_historical_return
from pypfopt.risk_models import CovarianceShrinkage

mu = mean_historical_return(df)
S = CovarianceShrinkage(df).ledoit_wolf()


from pypfopt.efficient_frontier import EfficientFrontier

ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()

cleaned_weights = ef.clean_weights()
ef.save_weights_to_file("weights.txt")  # saves to file
print(cleaned_weights)

ef.portfolio_performance(verbose=True)


from pypfopt import objective_functions

ef = EfficientFrontier(mu, S)
ef.add_objective(objective_functions.L2_reg, gamma=0.1)
w = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
ef.save_weights_to_file("weights.txt")  # saves to file
print(cleaned_weights)

 

Test

# training 구간 : 2015-07-07 ~ 2019-12-31
# test 구간 : 2020-01-01 ~ 현재
df_list = [fdr.DataReader(code, '2020-01-01')['Close'] for name, code in stock_list]

df = pd.concat(df_list, axis=1)
df.columns = [name for name, code in stock_list]


df['올웨더_김단테'] = 0.35 * df['VT'] + \
                   0.20 * df['EDV'] + \
                   0.075 * df['VCLT'] + \
                   0.075 * df['EMLC'] + \
                   0.20 * df['LTPZ'] + \
                   0.05 * df['IAU'] + \
                   0.05 * df['DBC']
df['올웨더_ef최적화'] = 0.40389 * df['VT'] + \
                   0.0 * df['EDV'] + \
                   0.2071 * df['VCLT'] + \
                   0.0 * df['EMLC'] + \
                   0.0 * df['LTPZ'] + \
                   0.38902 * df['IAU'] + \
                   0.0 * df['DBC']
df['올웨더_ef최적화_L2_Regularization'] = 0.39964 * df['VT'] + \
                   0.09202 * df['EDV'] + \
                   0.13605 * df['VCLT'] + \
                   0.0 * df['EMLC'] + \
                   0.02174 * df['LTPZ'] + \
                   0.35055 * df['IAU'] + \
                   0.0 * df['DBC']


# 첫 날을 기준으로 상대 수익률 그려보기
df_norm = df / df.iloc[0] - 1
df_norm.plot(figsize=(20,6));
plt.title('상대 수익률');


df_norm.iloc[-1].sort_values(ascending=False)

# DBC                            0.552286
# IAU                            0.247433
# VT                             0.113311
# 올웨더_ef최적화                      0.035841
# 올웨더_ef최적화_L2_Regularization   -0.007522
# 올웨더_김단테                       -0.105995
# LTPZ                          -0.166257
# VCLT                          -0.199961
# EMLC                          -0.257118
# EDV                           -0.308786
# Name: 2023-01-13 00:00:00, dtype: float64