ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [키움API]python 샤프지수를 이용한 종목선정 2
    금융퀀트/(퀀트)증권사API활용(키움) 2024. 1. 28. 16:28
    반응형

    종목별 샤프지수 분석과 UI 변경

    샤프지수의 정의

    샤프지수는 아래 식으로 나타낼 수 있다.(샤프지수(Sharpe Ratio): 얼마나 덜 쫄리게 많이 벌었나? 참조) 

    샤프지수 =  (투자자산의 수익률 - 기초수익률 ) / 투자자산의 변동성

    우리는 [키움API]python 샤프지수를 이용한 종목선정1: 코스피 종목 종가데이터 입수에서 종목별 종가를 입수했기 때문에 종목별 일일 수익률과 계산된 일일 수익률의 표준편차로 수익률의 변동성을 알 수 있다. 그리고 기초 수익률은 해당 종목이 아닌 예금 등 무위험 자산에 투자했을 때 얻을 수 있는 수익률인데 현재 3년 국고채 금리인 3.3% 정도로 잡으면 될 것 같다. 주의해야 할 점은 일일 수익률과 일일 표준편차를 계산했기 때문에 연 금리인 3.3%를 일일 금리로 환산해야 한다는 것이다.((1+3.3%)^(1/365) -1 식을 써서 환산하면 된다. 자세한 내용은 금리의 기간/복리와 단리를 알아보자 참조) 다만 종목별 샤프지수의 상대적인 비교가 중요하기 때문에 종목별 샤프지수 계산에 공통값으로 적용되는 기초수익률 자체는 별 의미는 없다.

    화면상 입력변수 추가

    무한한 기간을 대상으로 할 수 없기 때문에 종목별 샤프지수 분석 전에 분석 기간을 먼저 정해야하고, 큰 의미는 없지만 무위험 이자율도 분석 시점에 정해줘야 한다. 결국 입력 변수를 투입하는 화면은 아래 그림 1과 같이 수정할 수 있다. 

    그림1: UI 화면변경

    코스피 종가 데이터 불러오기: loadtimeseriesdata

    이제 [키움API]python 샤프지수를 이용한 종목선정1: 코스피 종목 종가데이터 입수에서 만든 코스피 종가 데이터를 불러오는 함수만 만들면 기본적인 준비는 끝난다. 데이터를 불러오는 함수는 간단하게 테이블명과 시작일과 종료일을 list 형태로 받아서 날짜를 기준으로 데이터를 가져오는 쿼리와 pandas 의 read_sql 함수를 조합하여 데이터를 DataFrame 형식으로 만들어내는 방식으로 아래와 같다.

    #SQLhandle
    import pymysql
    import sqlalchemy as db
    import pandas as pd
    pymysql.install_as_MySQLdb()
    
    class SQLhandle:
        def __init__(self):
    ...(생략)...
        def loadtimeseriesdata(self, tablename: str, fromtodates: list):
            query = f'''
                    SELECT 일자, 종목코드, 현재가
                    FROM {tablename}
                    WHERE 일자 BETWEEN {fromtodates[0]} AND {fromtodates[1]}
                    ORDER BY 종목코드, 일자;
                    '''
            data = pd.read_sql(query, con=self.engine)
            return data

     

    데이터를 불러오는 이 함수는 MySQL을 통한 기능이기 때문에 [키움API]파이썬 주식 종목별 종가정보 불러오기5: UI파일 화면구성(Qt Designer) 및 프로그램module과 CLASS구성에서 그린 아래 그림 2의 프로그램 구조도에서 sqlhandle.py 모듈의 SQLhandle 클래스에 추가한다.

    그림2: 키움API 프로젝트 프로그램 구조도

    개별종목 샤프지수 비교해서 상위 10개 종목 추출: gethighsharperatio

    이제 추출된 데이터를 바탕으로 샤프지수를 계산하고 상위 10 개 종목만 뽑아내서 그림 1 화면 상 텍스트 상자에 보여주는 기능만 구현하면 된다. 먼저, 화면에서 변수들을 입수하고, pandas DataFrame 클래스에 내장된 pct_change 함수를 이용해서 일일 수익률을 구해서 "수익률"컬럼을 만든다. 그 뒤 종목코드별로 묶어서(groupby("종목코드")) 평균과 표준편차를 구하고, 샤프지수까지 구해서 ["종목코드", "일일수익률평균", "일일표준편차", "샤프지수"]라는 컬럼을 가진 df_sharperatio 라는 DataFrame을 만든다. 그리고 Utils 클래스의 loadkospilist 함수를 사용해서 ["종목코드", "종목명"]라는 컬럼을 가진 df_name이라는 DataFrame을 하나 더 만든다. 마지막으로 두 개의 데이터프레임을 "종목코드"를 기준으로 합쳐주면, ["종목코드", "종목명", "일일수익률평균", "일일표준편차", "샤프지수"] 컬럼을 가진 데이터프레임을 얻을 수 있다.

    얻은 데이터프레임 중 샤프지수가 큰 상위 10개의 종목에 대해서  postdfresult 라는 함수를 사용해서  그리드 형식으로 화면의 "txt_result"라는 텍스트 상자에 보여준다. postdfresult 함수에는 tabulate 라이브러리가 사용됐는데, 아나콘다 프롬프트에서 pip install tabulate 이라는 명령어로 설치하면 된다. 이 라이브러리는  표 형식의 데이터를 그리드로 표시해주는 기능을 한다. 

    #UIhandle
    from PyQt5.QtWidgets import *
    import time
    import pandas as pd
    from tabulate import tabulate
    from kiwoomapi import KIWOOMapi
    from apihandle import APIhandle
    from sqlhandle import SQLhandle
    from utils import Utils
    
    class UIhandle(QWidget):
        def __init__(self, ocx: object, mainui):
            super().__init__()
            self.mainui = mainui
            self.ocx = ocx
            self.api = KIWOOMapi(ocx)
            self.handle = APIhandle(ocx)
            self.sql = SQLhandle()
            self.utils = Utils()
            self.timer = time
    ...(생략)...
    	# 데이터 프레임을 그리드로 텍스트 상자에 보여주는 함수
        def postdfresult(self, data: pd.DataFrame):
        	# 데이터를 보여줄 텍스트 상자를 변수화
            txt_result = self.mainui.findChild(QTextEdit, "txt_result")
            # 결과값을 tabulate 함수를 써서 그리드 형태로 table_str이라는 변수에 저장
            table_str = tabulate(data, headers='keys', tablefmt='grid', showindex=False)
            # 결과값을 텍스트 상자에 표시
            txt_result.setPlainText(table_str)
    	# UI에서 날짜값을 받아오는 함수
        def getdate(self, dateeditid: str):
            date = self.mainui.findChild(QDateEdit, dateeditid)
            return date
    ...(생략)...
    	# UI의 QDoubleSpinBox에서 값을 받아오는 함수 위 그림 1의 기초수익률 칸
        def getdoublespinboxvalue(self, spinboxeditid: str):
            spinbox = self.mainui.findChild(QDoubleSpinBox, spinboxeditid)
            return spinbox.value()
    ...(생략)...
    	# 샤프지수 값을 구해서 상위 10개를 뽑아내는 함수
        def gethighsharperatio(self):
        	# 샤프지수를 구할 시작일자와 마지막일자를 받아오기
            fromdate = self.getdate("date_analfrom").date().toString("yyyyMMdd")
            todate = self.getdate("date_analto").date().toString("yyyyMMdd")
            # 날짜를 리스트에 저장
            fromtodates = [fromdate, todate]
            # 시작일자와 마지막일자에 해당하는 "stockdailychart"에 저장된 모든 종가정보를 입수
            df = self.sql.loadtimeseriesdata("stockdailychart", fromtodates)
            # 무위험수익률 입수
            riskfreerate = int(self.getdoublespinboxvalue("doubleSpinBox_rfr"))
            # 종목코드별로(GROUP BY) 현재가의 일일 수익률(pct_change)을 구해서 수익률컬럼에 저장
            df['수익률'] = df.groupby('종목코드')['현재가'].pct_change()
            # 수익률의 평균과 표준편차 구하기
            dailyreturn = df.groupby('종목코드')['수익률'].mean()
            dailystdev = df.groupby('종목코드')['수익률'].std()
            # 무위험수익률을 일일 수익률로 변환
            dailyrfr = (1 + riskfreerate)**(1/365) - 1
            # 샤프지수 계산식에 따른 샤프지수 계산
            dailyspr = (dailyreturn - dailyrfr) / dailystdev
            # 결과를 df_sharperatio 데이터프레임에 저장 
            df_sharperatio = pd.DataFrame({
                "종목코드":dailyreturn.index,
                "일일수익률평균":dailyreturn.values,
                "일일표준편차":dailystdev.values,
                "샤프지수":dailyspr.values
            })
            # 코스피 종목의 이름을 갖고오기 위해 Utils 클래스에(상단에서 self.utils = Utils()로
            # 객체화 함) loadkospilist 함수로 데이터 갖고옴
            dfstocknames = self.utils.loadkospilist()
            # dfstocknames 정보로 "종목코드", "종목명"으로 이루어진 데이터프레임 구성
            df_name = pd.DataFrame(data=dfstocknames, columns=["종목코드","종목명"])
            # df_sharperatio의 "종목코드" 컬럼을 기준으로 dfstocknames 데이터 붙이기
            df = pd.merge(df_sharperatio, df_name, how="left", on=["종목코드"])
            # 샤프지수가 높은 순서대로 정렬
            df = df.sort_values(by="샤프지수", ascending=False)
            # 컬럼 재정의
            df = df[["종목코드", "종목명", "일일수익률평균", "일일표준편차", "샤프지수"]]
            # 상위 10개 만 뽑아서 postdfresult 함수를 사용해서 화면에 보여주기
            self.postdfresult(df.head(10))

     

    메인화면 설정 변경

    마지막으로 main 화면에 아래와 같이 버튼과 날짜입력란을 변수로 설정해 주면 모든 기능이 구현된다.

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QAxContainer import *
    from PyQt5.QtCore import QDate
    from PyQt5 import uic
    from uihandle import UIhandle
    
    form_class = uic.loadUiType("UI 파일의 경로 입력")[0]
    
    class KiwoomAPIForm(QMainWindow, form_class):
        def __init__(self):
    ...(생략)...
        def initsetting(self):
            yesterday = QDate.currentDate().addDays(-1)
            self.findChild(QDateEdit, "date_base").setDate(yesterday)
            # 샤프지수 분석 시작일자 ~ 종료일자 칸 설정: 데이터 입수시 2021-08-12 데이터부터
            # 2024-01-19 데이터 까지 입수해서 기본설정값을 20210812, 20240119로 설정함
            self.findChild(QDateEdit, "date_analfrom").setDate(QDate.fromString("20210812", "yyyyMMdd"))
            self.findChild(QDateEdit, "date_analto").setDate(QDate.fromString("20240119", "yyyyMMdd"))
            self.buttongroup.addButton(self.btn_login, 1)
            self.buttongroup.addButton(self.btn_dataget, 2)
            # 함수와 연결하기 위한 버튼 그룹 추가 위 그림 1에 따라 종목추천 버튼의 오브젝트 명은
            # btn_recommend 임
            self.buttongroup.addButton(self.btn_recommend, 3)
        
        def buttonfunction(self, button):
            buttonid = self.buttongroup.id(button)
            if buttonid == 1:
                self.ui.apilogin()
            elif buttonid == 2:
                self.ui.gettimeseriesdata()
            # buttonid = 3 즉 btn_recommend 를 눌렀을 때 gethighsharperatio 작동하도록 설정
            elif buttonid == 3:
                self.ui.gethighsharperatio()
                
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = KiwoomAPIForm()
        window.show()
        sys.exit(app.exec_())

     

    구현된 기능을 이용해서 2021.08.12 ~ 2024.01.19 일자에 대해서 코스피 종목에 대한 데이터를 수집하여 샤프지수 분석을 하면 아래와 같은 결과를 얻을 수 있다.(역시 에코프로가 1등이다.)

    그림3: 최종 분석 결과

     

    반응형
Designed by Tistory.