1편을 참고해주세요.
https://sieon-dev.tistory.com/41
*몇몇 과정에는 Jupyter Notebook에서 실행한 결과 화면이 첨부되어 있습니다.
주가 데이터 가져오기
주가 데이터는 FinanceDataReader 라이브러리를 사용해서 불러옵니다.
samsung = fdr.DataReader('005930','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp005930'})
lgdisplay = fdr.DataReader('034220','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp034220'})
kakao = fdr.DataReader('035720','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp035720'})
data = pd.concat([samsung, lgdisplay, kakao], axis=1)
data = data.resample('M').mean() #일별 데이터를 월별 데이터로 만들어줌
data = data.pct_change() #월별 주가 데이터를 이용해 수익률 데이터로 변환
data.dropna(inplace=True) #결측치 제외(첫 row)
mu = data.mean() * 12 #연평균 수익률
cov = data.cov() * 12 #연평균 공분산
data를 찍어보면 다음과 같습니다.
GMV 포트폴리오 구하기
최적화 라이브러리에는 scipy.optimize의 minimize 메서드를 사용했습니다.
def gmv_opt(code): #최소분산 포트폴리오 지점을 찾는 코드
lgth = len(code)
w0 = np.ones(lgth) / lgth #각 자산의 비중을 1/3 로 초기화
fun = lambda w: np.dot(w.T, np.dot(cov, w)) #목적함수 : 분산의 최소화
constraints = ({'type':'eq', 'fun':lambda x: np.sum(x)-1}) #제약조건 : 종목 비중의 합이 1이 되어야 한다.
bd = ((0,1),) * 3 #비중의 범위를 0 ~ 1 까지 지정
gmv = minimize(fun, w0, method = 'SLSQP', constraints=constraints, bounds=bd)
return gmv.x
gmv_weights= gmv_opt(code).tolist()
gmv_return = np.dot(gmv_weights, mu) #GMV 포트폴리오의 수익률 => 행렬계산
gmv_volatility = np.sqrt(np.dot(gmv_opt(code), np.dot(cov, gmv_weights))) #GMV 포트폴리오의 표준편차 => 행렬계산
gmv_weights의 결과는 이렇습니다.
이 의미는 삼성전자와 LG디스플레이, 카카오에 저 비중대로 투자할 경우가 GMV 포트폴리오라는 것입니다.
이 비중 행렬에 각 자산의 평균 수익률을 곱하는 행렬 계산을 해주면 GMV 포트폴리오의 평균 수익률이 산출됩니다. 또한 비중의 전치 행렬에 공분산을 곱하고 다시 비중을 곱해주면 포트폴리오의 표준편차가 구해집니다. 이 값들을 곧 그래프에서 GMV 포트폴리오의 Y축과 X축이 될 것입니다.
효율적 투자선 구하기
efline_returns = np.linspace(gmv_return, max(mu), 30) # GMV지점에서 부터 mu값이 최대인 지점까지 30등분을 한다.
efline_volatilities = []
efpoints = []
efweights= []
for i, tret in enumerate(efline_returns): #개별 return마다 최소 분산 찾기
lgth = len(code)
w0 = np.ones(lgth) / lgth
fun = lambda w: np.dot(w.T ,np.dot(cov, w)) #목적함수 : 분산의 최소화
constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, #제약식 : 자산 비중의 합은 1이다.
{'type': 'ineq', 'fun': lambda x: np.dot(x, mu) - tret}] # 졔약식 : 같은 수익률을 가지는 점 중에 분산이 최소인 지점
bd = ((0,1),) * lgth
minvol = minimize(fun, w0, method='SLSQP',bounds = bd, constraints=constraints)
efpoints.append([np.sqrt(np.dot(minvol.x, np.dot(cov, minvol.x))),tret]) #efpoints 배열에 [표준편차, 수익률] 배열을 삽입한다.
efweights.append(minvol.x.tolist()) #efweights 배열에 각 투자 자산의 비중을 삽입한다.
result = {"GMV": [gmv_volatility, gmv_return], "GMV_weight" : gmv_weight, "efline_points" : efpoints, "efweights" : efweights, "User" : [user_volatility, user_return], "User_weight" : weight}
효율적 투자선은 최소분산 포트폴리오의 y값(수익률) 부터 각 자산의 평균 수익률 중 가장 큰 값까지 30등분을 한 뒤 각 점에서 분산을 최소화하는 최적화 공식으로 x값을 찾습니다. 이후 front에 넘겨줄 값들을 result라는 딕셔너리 형태의 변수에 저장해줍니다.
result의 결과는 다음과 같습니다.
지금까지의 코드들을 views.py 에 붙여넣습니다.
views.py
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse,JsonResponse
import FinanceDataReader as fdr
import pandas as pd
import numpy as np
from scipy.optimize import minimize
# Create your views here.
def home(request):
return render(request, 'plot/home.html',{})
@csrf_exempt
def get_efline(request):
code = request.POST.getlist('code[]')
_weight = request.POST.getlist('weight[]')
weight =[]
for i in range(len(_weight)): #ajax로 부터 들어온 값은 string 타입이기 때문에 float로 변환
weight.append(float(_weight[i]))
_from = request.POST.get('from')
_to = request.POST.get('to')
samsung = fdr.DataReader('005930','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp005930'})
lgdisplay = fdr.DataReader('034220','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp034220'})
kakao = fdr.DataReader('035720','2019-01-01', '2020-01-01')[['Close']].rename(columns={'Close':'kp035720'})
data = pd.concat([samsung, lgdisplay, kakao], axis=1)
data = data.resample('M').mean() #일별 데이터를 월별 데이터로 만들어줌
data = data.pct_change() #월별 주가 데이터를 이용해 수익률 데이터로 변환
data.dropna(inplace=True) #결측치 제외(첫 row)
mu = data.mean() * 12 #각 자산의 연평균 수익률 배열
cov = data.cov() * 12 #각 자산의 연평균 공분산 배열
user_return = np.dot(weight, mu)
user_volatility = np.sqrt(np.dot(weight, np.dot(cov, weight)))
def gmv_opt(): #최소분산 포트폴리오 지점을 찾는 코드
lgth = len(code)
w0 = np.ones(lgth) / lgth #각 자산의 비중을 1/3 로 초기화
fun = lambda w: np.dot(w.T, np.dot(cov, w)) #목적함수 : 분산의 최소화
constraints = ({'type':'eq', 'fun':lambda x: np.sum(x)-1}) #제약조건 : 종목 비중의 합이 1이 되어야 한다.
bd = ((0,1),) * 3 #비중의 범위를 0 ~ 1 까지 지정
gmv = minimize(fun, w0, method = 'SLSQP', constraints=constraints, bounds=bd)
return gmv.x
gmv_weight= gmv_opt().tolist()
gmv_return = np.dot(gmv_weight, mu) #GMV 포트폴리오의 수익률 => 행렬계산
gmv_volatility = np.sqrt(np.dot(gmv_opt(), np.dot(cov, gmv_weight))) #GMV 포트폴리오의 표준편차 => 행렬계산
efline_returns = np.linspace(gmv_return, max(mu), 30) # GMV지점에서 부터 mu값이 최대인 지점까지 30등분을 한다.
efline_volatilities = []
efpoints = []
efweights= []
for i, tret in enumerate(efline_returns): #개별 return마다 최소 분산 찾기
lgth = len(code)
w0 = np.ones(lgth) / lgth
fun = lambda w: np.dot(w.T ,np.dot(cov, w)) #목적함수 : 분산의 최소화
constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, #제약식 : 자산 비중의 합은 1이다.
{'type': 'ineq', 'fun': lambda x: np.dot(x, mu) - tret}] # 졔약식 : 같은 수익률을 가지는 점 중에 분산이 최소인 지점
bd = ((0,1),) * lgth
minvol = minimize(fun, w0, method='SLSQP',bounds = bd, constraints=constraints)
efpoints.append([np.sqrt(np.dot(minvol.x, np.dot(cov, minvol.x))),tret]) #efpoints 배열에 [표준편차, 수익률] 배열을 삽입한다.
efweights.append(minvol.x.tolist()) #efweights 배열에 각 투자 자산의 비중을 삽입한다.
result = {"GMV": [gmv_volatility, gmv_return], "GMV_weight" : gmv_weight, "efline_points" : efpoints, "efweights" : efweights, "User" : [user_volatility, user_return], "User_weight" : weight}
return JsonResponse(result)
result 값을 ajax에서 받아서 console 창에 찍어보면 다음과 같이 성공적으로 통신이 완료된 것을 볼 수 있습니다.
'Front-End' 카테고리의 다른 글
[JS] new Date() 함수의 위험성 (1) | 2022.08.20 |
---|---|
[JS] Object Sorting Issue (0) | 2022.07.27 |
localstorage를 활용한 메뉴 History 저장 (0) | 2022.05.31 |
Chartjs를 사용해 효율적 투자선 그리기 - 3편 (0) | 2021.07.27 |
Chartjs를 사용해 효율적 투자선 그리기 - 1편 (0) | 2021.07.27 |