본문 바로가기

Front-End

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

728x90

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

 

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

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

sieon-dev.tistory.com

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

 

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

1편을 참고해주세요. https://sieon-dev.tistory.com/41 Chartjs를 사용해 효율적 투자선 그리기 - 1편 포트폴리오 이론과 효율적 투자선에 대해선 제가 간단하게 소개한 글이 있으니 읽어주시면 감사하겠습

sieon-dev.tistory.com

지금까지 백엔드에서 최적화 과정을 통해 사용자의 포트폴리오, 효율적 투자선, GMV 포트폴리오 등을 산출해서 프런트엔드로 값을 가져왔습니다. 

 

 

이제 이 값들로 그래프를 그려줍니다. 

 

우선 Chartjs는 아래 사이트에 가면 자세한 사용 방법에 대해 알 수 있습니다.

https://www.chartjs.org/

 

Chart.js | Open source HTML5 Charts for your website

New in 2.0 New chart axis types Plot complex, sparse datasets on date time, logarithmic or even entirely custom scales with ease.

www.chartjs.org

home.html

$(document).ready(function () {
        $('#draw').click(function (){
            $.ajax({
                url: '/get_efline/',
                type: "POST",
                dataType: "json",
                data : {'code' : code, 'weight' : weight, 'from': from, 'to' : to},
                success: function (data) {
                    opt_result(jname, data.GMV, data.GMV_weight, data.efline_points, data.efweights, data.User, data.User_weight);
                },
                error: function (request, status, error) {
                    console.log('실패');
                }
            });
        });
        
    });

이젠 통신에 성공하면 console에 찍는 것이 아니라 opt_result라는 함수를 만들어 chartjs 그래프를 그려주도록 합니다.

opt_result 코드는 다음과 같습니다.

function opt_result(jname, GMV, GMV_weight, efline_points, efweights, User, User_weight) {
    var Ef_ctx = document.getElementById("efficient_frontier_graph").getContext('2d'); 
    ef_storage = []; 
    
    for (var i = 0; i < efline_points.length; i++) {//efline을 chartjs에서 그려주기 위해 형식에 맞는 json형태로 변환
        x = Number(efline_points[i][0]);
        y = Number(efline_points[i][1]);
        var json = { x: x, y: y };
        ef_storage.push(json);
    }

    window.Efchart = new Chart(Ef_ctx, {
        type: 'scatter',
        data: {
            datasets : [{
                label : '효율적 투자선',
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data : ef_storage,
                showLine : true,   //점을 선으로 잇기 위해선 true로 설정
                fill : false,
                order : 1,
            }],
        },
        options: {
            responsive: true, // Instruct chart js to respond nicely.
            maintainAspectRatio: false, // Add to prevent default behaviour of full-width/height 
            hover:{
                mode: "nearest",
            },
            tooltips:{      //점에 마우스를 hover했을 때 보이는 Tooltip
                displayColors:false,
                titleFontColor:'#fff',
                titleAlign: 'center',
                bodyFontSize: 15,
                bodySpacing: 2,
                bodyFontColor: '#fff',
                bodyAlign: 'center',
            },
            scales: {
                yAxes: [{
                    scaleLabel: {
                        display: true,
                        labelString: '평균 수익률(Y)',
                        fontStyle: 'bold',
                        fontSize: '10',
                    }
                }],
                xAxes: [{
                    scaleLabel: {
                        display: true,
                        labelString: '표준편차(Y)',
                        fontStyle: 'bold',
                        fontSize: '10',
                    }
                }]
            },
        }
    });
 }

결과는 이렇습니다.

 

이제 점에 마우스를 올렸을 때 x축, y축 값만 보여주는게 아닌 각 자산에 대한 투자 비중을 같이 보여주는 걸로 바꿔보려고 합니다.

이 부분은 options에 tooltips 부분에 callback 함수를 구현해 주면 됩니다.

 

tooltips:{      //점에 마우스를 hover했을 때 보이는 Tooltip
  displayColors:false,
  titleFontColor:'#fff',
  titleAlign: 'center',
  bodyFontSize: 15,
  bodySpacing: 2,
  bodyFontColor: '#fff',
  bodyAlign: 'center',
  callbacks: {
  	label: function(tooltipitem, data){
  		console.log(tooltipitem);
  		console.log(data);
  		}
  	}
 }

우선 tooltipitem과 data가 어떤 값인지 console에 출력해보겠습니다.

tooltipitem은 x, y 값과 Index, datasetIndex등 좌표에 필요한 값들이 출력되고 data에는 설정들이 저장되어 있습니다. 지금 chart에 그려진 그래프는 효율적 투자선 하나이기 때문에 datasetIndex는 0이 끝이겠지만 추후 GMV 포트폴리오나 USER 포트폴리오를 추가적으로 반영하면 datasetIndex는 0, 1, 2의 값을 가질 것입니다. 

 

또한 효율적 투자선은 30개의 점으로 이루어져있기 때문에 현재 제가 마우스를 올린 점은 20번째 점이라는 뜻에서 index는 19로 나와있습니다. 

 

이제 본격적으로 tooltips의 콜백함수를 구현해줍니다. 

 

callbacks: {
	label: function(tooltipitem, data){
		var title = "기대수익률 : "+ tooltipitem['yLabel'].toFixed(2) + " 표준편차 : "+tooltipitem['xLabel'].toFixed(2); 
		var body = "";
		for (var i=0; i<jname.length; i++){
			body += jname[i] + ": " + (efweights[tooltipitem.index][i]*100).toFixed(2)+'% \n';
			}
		return [title, "", body];
	}
}

tooltip은 title과 body를 나눠 출력하려고 합니다. title에는 x축과 y축 값인 표준편차와 수익률을 표기해주고 body에는 각 자산에 대한 비중을 표기해줍니다. toFixed() 메서드는 숫자를 문자열로 바꾸는 동시에 지정해준 소수점 이하를 제거해줍니다. 

 

body에는 앞서 opt_result의 매개변수로 받은 efweights를 사용해 각 자산에 대한 투자 비중을 표기해줍니다. 이 과정에서 점이 30개가 되기 때문에 어떤 점인지 알려주기 위해 tooltipitem.index가 필요하고 이를 다시 %로 변환하기 위해 100을 곱해준 다음 소수점 두 번째 자리 이하를 제거해줍니다. 결과는 다음과 같습니다.

 

다른 포트폴리오 추가하기 

이제 같은 그래프 위에 GMV 포트폴리오도 추가하고 사용자 본인의 포트폴리오도 추가합니다.

 

우선 pushscatter이라는 함수를 구현해줍니다.

 function pushscatter(chart, x, y, label, color, order) {
        chart.data.datasets.push({
            type: 'scatter',
            label: label,
            showLine: false,
            data: [{ x: x, y: y }],
            backgroundColor: color,
            pointBackgroundColor: color,
            pointRadius: 8,
            pointHoverRadius: 8,
            order: order,
        });
        chart.update();
    }

매개변수로 chart와 x, y 값, label 값 등을 받아서 chart에 반영해주는 함수입니다. order 은 이미 찍혀있는 효율적 투자선과 겹치지 않게 하기 위해 효율적 투자선의 order 값인 1보다 큰 값으로 해줘야합니다.

 

pushscatter(Efchart, GMV[0], GMV[1], 'GMV Portfolio', '#003f5c', '2');
pushscatter(Efchart, User[0], User[1], "내 포트폴리오","#ffa600", "2");

위 코드를 opt_result 함수 맨 밑에 붙여 넣습니다. 

 

여기서 끝이 아닙니다. 차트가 변경되었으니 tooltip도 수정해줘야 합니다.

우선 다시 tooltipitem과 data값을 찍어보겠습니다.

 

callbacks: {
  label: function(tooltipitem, data){

    console.log(tooltipitem);
    console.log(data);

    /*var title = "기대수익률 : "+ tooltipitem['yLabel'].toFixed(2) + " 표준편차 : "+tooltipitem['xLabel'].toFixed(2); 
    var body = "";
    for (var i=0; i<jname.length; i++){
    body += jname[i] + ": " + (efweights[tooltipitem.index][i]*100).toFixed(2)+'% \n';
    }
    return [title, "", body];*/
  	}
  }

첫 번째 출력 결과는 User 포트폴리오 점을 찍었을 때고 두 번째 출력 결과는 GMV 포트폴리오를 찍었을 경우입니다. 보다시피 datasetIndex 가 다른 것을 볼 수 있습니다. 이 datasetIndex 값으로 차트에서 구분해줘야 합니다. 만약 datasetIndex가 2이면 GMV 포트폴리오에 대한 툴팁을 보여줘라 이런 식으로 말이죠.

callbacks: {
	label: function(tooltipitem, data){
    	var title = "기대수익률 : "+ tooltipitem['yLabel'].toFixed(2) + " 표준편차 : "+tooltipitem['xLabel'].toFixed(2); 
        var body = "";
        if(tooltipitem.datasetIndex == 2){
        	for (var i=0; i<jname.length; i++){
            	body += jname[i] + ": " + (User_weight[i]*100).toFixed(2)+ '% \n';
                }
        }
        else if(tooltipitem.datasetIndex==1){
        	for (var i=0; i<jname.length; i++){
            	body += jname[i] + ": " + (GMV_weight[i]*100).toFixed(2)+ '% \n';
                }
        }else{
            for (var i=0; i<jname.length; i++){
            	body += jname[i] + ": " + (efweights[tooltipitem.index][i]*100).toFixed(2)+'% \n';
                }
        }
        return [title, "", body];
        }
  }