분산 투자의 필요성 - 해리 마코위츠의 포트폴리오 이론
자산을 분산투자하여 포트폴리오를 만들게 되면 분산투자 전보다 위험을 감소시킬 수 있다는 이론이다.
포트폴리오 이론의 가정에 따르면 투자자들은 투자안의 의사결정과정에서 고려하는 수익과 위험은 각각 평균과 분산으로 표현할 수 있으며, 포트폴리오를 구성할 경우 자산 간의 상관계수가 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
'퀀트' 카테고리의 다른 글
28. 이평선 투자 전략 : 이평선의 위 또는 아래에서 매수하는 경우 (0) | 2023.01.20 |
---|---|
27. 모든 주식에 대해 보유 기간별 수익률 검증하기 (1) | 2023.01.19 |
25. 레이 달리오의 올웨더 전략 (0) | 2023.01.15 |
24. 게리 안토나치의 듀얼 모멘텀 전략 (0) | 2023.01.14 |
23. 래리 윌리엄스의 변동성 돌파 전략 (0) | 2023.01.13 |