본문 바로가기

Front-End

Chartjs를 사용해 효율적 투자선 그리기 - 2편

728x90

1편을 참고해주세요.

https://sieon-dev.tistory.com/41

 

Chartjs를 사용해 효율적 투자선 그리기 - 1편

포트폴리오 이론과 효율적 투자선에 대해선 제가 간단하게 소개한 글이 있으니 읽어주시면 감사하겠습니다. https://sieon-dev.tistory.com/40?category=989136 Harry Markowitz의 포트폴리오 이론과 효율적 투자.

sieon-dev.tistory.com

 

*몇몇 과정에는 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 창에 찍어보면 다음과 같이 성공적으로 통신이 완료된 것을 볼 수 있습니다.