[키움API]python 샤프지수를 이용한 종목선정 2
종목별 샤프지수 분석과 UI 변경
샤프지수의 정의
샤프지수는 아래 식으로 나타낼 수 있다.(샤프지수(Sharpe Ratio): 얼마나 덜 쫄리게 많이 벌었나? 참조)
샤프지수 = (투자자산의 수익률 - 기초수익률 ) / 투자자산의 변동성
우리는 [키움API]python 샤프지수를 이용한 종목선정1: 코스피 종목 종가데이터 입수에서 종목별 종가를 입수했기 때문에 종목별 일일 수익률과 계산된 일일 수익률의 표준편차로 수익률의 변동성을 알 수 있다. 그리고 기초 수익률은 해당 종목이 아닌 예금 등 무위험 자산에 투자했을 때 얻을 수 있는 수익률인데 현재 3년 국고채 금리인 3.3% 정도로 잡으면 될 것 같다. 주의해야 할 점은 일일 수익률과 일일 표준편차를 계산했기 때문에 연 금리인 3.3%를 일일 금리로 환산해야 한다는 것이다.((1+3.3%)^(1/365) -1 식을 써서 환산하면 된다. 자세한 내용은 금리의 기간/복리와 단리를 알아보자 참조) 다만 종목별 샤프지수의 상대적인 비교가 중요하기 때문에 종목별 샤프지수 계산에 공통값으로 적용되는 기초수익률 자체는 별 의미는 없다.
화면상 입력변수 추가
무한한 기간을 대상으로 할 수 없기 때문에 종목별 샤프지수 분석 전에 분석 기간을 먼저 정해야하고, 큰 의미는 없지만 무위험 이자율도 분석 시점에 정해줘야 한다. 결국 입력 변수를 투입하는 화면은 아래 그림 1과 같이 수정할 수 있다.
코스피 종가 데이터 불러오기: 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 클래스에 추가한다.
개별종목 샤프지수 비교해서 상위 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등이다.)