News & Events
Python 및 SciPy를 사용하여 리스크를 최소화하는 포트폴리오 구축하기
구체적인 제약 조건을 충족하는 다양한 포트폴리오를 구축하는 방법에 대해 알아보자. 해당 포스팅에서는 위험을 최소화하는 포트폴리오를 구축할 것이다.
제일 먼저 해야 할 일은 파이썬을 사용하여 주가를 얻는 것이다.
Python을 사용하여 주가를 다운로드하는 방법
pip install yfinance –upgrade –no-cache-dir를 사용하여 설치할 수 있는 패키지로 작업할 것이다. 이 CSV 파일에서 나스닥 주식과 심볼 간의 매핑을 찾을 수 있다.
이번 포스팅에서는 다음의 10가지 종목을 다루고 있다고 가정하고 포트폴리오 리스크를 최소화하고자 한다.
- Google with Symbol GOOGL
- Tesla with Symbol TSLA
- Facebook with Symbol FB
- Amazon with Symbol AMZN
- Apple with Symbol AAPL
- Microsoft with Symbol MSFT
- Vodafone with Symbol VOD
- Adobe with Symbol ADBE
- NVIDIA with Symbol NVDA
- Salesforce with Symbol CRM
우리는 작년 종가를 다운받을 것이다.
import pandas as pd
import numpy as np
import yfinance as yf
from scipy.optimize import minimize
import matplotlib.pyplot as plt
%matplotlib inline
symbols = [‘GOOGL’, ‘TSLA’, ‘FB’, ‘AMZN’, ‘AAPL’, ‘MSFT’, ‘VOD’, ‘ADBE’, ‘NVDA’, ‘CRM’ ]
all_stocks = pd.DataFrame()
for symbol in symbols:
tmp_close = yf.download(symbol,
start=’2019-11-07′,
end=’2020-11-07′,
progress=False)[‘Close’]
all_stocks = pd.concat([all_stocks, tmp_close], axis=1)
all_stocks.columns=symbols
all_stocks
로그 수익률 가져오기
우리는 로그 수익률 또는 연속 복리 수익률을 사용할 것이다. 파이썬으로 계산해 보자.
returns = np.log(all_stocks/all_stocks.shift(1)).dropna(how=”any”) returns
returns.plot(figsize=(12,10))
평균 수익률 얻기
우리는 모든 주식의 표본 평균 수익률뿐만 아니라 모든 주식의 산술 평균 수익률도 얻을 수 있다.
# mean daily returns per stock
returns.mean()
GOOGL 0.001224
TSLA 0.007448
FB 0.001685
AMZN 0.002419
AAPL 0.002422
MSFT 0.001740
VOD -0.001583
ADBE 0.002146
NVDA 0.004077
CRM 0.001948
dtype: float64
# mean daily returns of all stocks
returns.mean().mean()
0.0023526909011353354
포트폴리오의 위험 최소화
우리의 목표는 다음과 같은 제약이 있는 10개 주식으로 포트폴리오를 구성하는 것이다.
- 일일 예상 수익률이 모든 평균보다 높다. 즉, 0.003보다 크다.
- 공매도는 없다. 즉, 주식만 사들이기 때문에 모든 주식의 가중치 합계는 최대 1이다.
- 모든 종목이 0에서 1까지 가중치를 가질 수 있다. 즉, 하나의 종목으로만 포트폴리오를 구성하거나 일부 종목을 제외할 수 있다.
마지막으로, 우리의 목표는 포트폴리오의 분산(즉, 위험)을 최소화하는 것이다. 행렬 연산을 사용하여 포트폴리오의 분산을 계산하는 방법에 대한 설명은 이 블로그에서 확인할 수 있다.
우리는 scipy 라이브러리로 작업할 것이다:
# the objective function is to minimize the portfolio risk
def objective(weights):
weights = np.array(weights)
return weights.dot(returns.cov()).dot(weights.T)
# The constraints
cons = (# The weights must sum up to one.
{“type”:”eq”, “fun”: lambda x: np.sum(x)-1},
# This constraints says that the inequalities (ineq) must be non-negative.
# The expected daily return of our portfolio and we want to be at greater than 0.002352
{“type”: “ineq”, “fun”: lambda x: np.sum(returns.mean()*x)-0.003})
# Every stock can get any weight from 0 to 1
bounds = tuple((0,1) for x in range(returns.shape[1]))
# Initialize the weights with an even split
# In out case each stock will have 10% at the beginning
guess = [1./returns.shape[1] for x in range(returns.shape[1])]
optimized_results = minimize(objective, guess, method = “SLSQP”, bounds=bounds, constraints=cons)
optimized_results
결과:
fun: 0.0007596800848097395
jac: array([0.00113375, 0.00236566, 0.00127447, 0.0010218 , 0.00137465,
0.00137397, 0.00097843, 0.00144561, 0.00174113, 0.0014457 ])
message: ‘Optimization terminated successfully.’
nfev: 24
nit: 2
njev: 2
status: 0
success: True
x: array([0.08447057, 0.17051382, 0.09077398, 0.10128927, 0.10099533,
0.09145521, 0.04536969, 0.09705495, 0.12378042, 0.09429676])
최적의 가중치는 배열 x이고 다음과 같은 결과를 얻을 수 있다.
optimized_results.x
결과:
array([ 8.44705689, 17.05138189, 9.07739784, 10.12892656, 10.09953316, 9.14552072, 4.53696906, 9.70549545, 12.37804203, 9.42967639])
가중치의 합계가 1이 되는지 확인할 수 있다.
# we get 1
np.sum(optimized_results.x)
그리고 포트폴리오의 기대 수익은 다음과 같다.
np.sum(returns.mean()*optimized_results.x)
결과:
0.002999999997028756
거의 0.003 (반올림 오류)이다.
최종 가중치
pd.DataFrame(list(zip(symbols,optimized_results.x )), columns=[‘Symbol’,’Weight’])
번역 – 핀인사이트 인턴연구원 강지윤(shety0427@gmail.com)
원문 보러가기>
https://medium.datadriveninvestor.com/portfolio-optimization-in-python-5c442df56ac4