-
[키움API]파이썬 주식 종목별 종가정보 불러오기5: UI파일 화면구성(Qt Designer) 및 프로그램module과 CLASS구성금융퀀트/(퀀트)증권사API활용(키움) 2023. 12. 19. 08:07반응형
프로그램 UI 재구성
코스피의 전체종목 정보를 불러와서([키움API]파이썬 주식 종가정보 불러오기3: 전체종목 기간별 종가조회)DB에 저장하는([키움API]파이썬 주식 종가정보 불러오기4: 데이터 DB저장(mysql)) 기능을 [키움API]python 메인 화면 만들기(Qt Designer 사용)에서 만든 메인 화면버튼에 추가할 필요가 있다.
위 그림 1 처럼 먼저 화면 내에 다양한 모양의 위젯을 추가해 준다. 각 위젯에는 클래스명과 아이디가 있고 위 그림 1에서는 "클래스명":"아이디"로 표시했다. 위젯에서 데이터를 가져오거나 클릭 이벤트를 감지할 때 클래스명과 아이디를 통해서 기능을 구현한다.
프로그램 구조도
"주식 종목별 종가정보를 불러오는" 단순한 작업은 "메인화면에서 전체 기능을 조율", "키움 API 호출", "키움 API로 받은 데이터를 정제", "MySQL을 이용해서 DB에 데이터 저장", "UI파일의 화면에 기능 부여"라는 복잡한 과정을 거쳐서 완료할 수 있다. 따라서 프로그램의 기능을 세분화하여 모듈화하고(기능별로 파이썬 파일을 만들고), 모듈별로 클래스를 만들어서 프로그램을 구조화하는 것이 필요하다. 따라서 아래 그림 2와 같이 main.py(메인화면에서 전체 기능을 조율), kiwoomapi.py(키움API 호출), apihandle.py(키움 API로 받은 데이터를 정제), sqlhandle.py(MySQL을 이용해서 DB에 데이터 저장), uihandle.py(UI파일의 화면에 기능 부여)라는 5개의 파일을 만들고(각각의 파일을 모듈이라고 한다.) 모듈에 각각 클래스를 만들어서 프로그램을 구성한다.
프로그램 기능별 코드
main.py(KiwoomAPIForm)
메인에서는 main UI를 불러오고, QApplication 실행, 키움 API 객체를 불러와서 ocx라는 변수에 저장, 초기화면 구성, 버튼별 함수 부여 기능을 한다. 여기서 로그인 버튼의 함수는 uihandle 모듈의 UIhandle 클래스 내의 apilogin 함수를 사용하고, 기간별 종가데이터 입수 버튼의 함수는 uihandle 모듈의 UIhandle 클래스 내의 timeseriesdataget 함수를 사용한다.
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): super().__init__() self.setupUi(self) self.setWindowTitle("KiwoomTest") self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1") # UIhandle 클래스 불러오고 현재 QApplication 을 변수로 넘김(두 번째 self 변수) self.ui = UIhandle(self.ocx, self) # UI의 버튼들을 그룹화 self.buttongroup = QButtonGroup() # 하단의 초기 세팅 함수 작동 self.initsetting() # 버튼 클릭시 하단의 butoonfunction 함수 작동 self.buttongroup.buttonClicked.connect(self.buttonfunction) def initsetting(self): # 직전일 가져오기 yesterday = QDate.currentDate().addDays(-1) # 화면 내부에서 그림 1의 date_base 부분 찾아서 직전일로 세팅 self.findChild(QDateEdit, "date_base").setDate(yesterday) # 버튼그룹에 btn_login, btn_dataget 두가지 버튼 넣어주기(인덱스 1, 2 부여) self.buttongroup.addButton(self.btn_login, 1) self.buttongroup.addButton(self.btn_dataget, 2) def buttonfunction(self, button): buttonid = self.buttongroup.id(button) # 인덱스 1 일 때 UIhandle 클래스의 apilogin 함수 불러오기 if buttonid == 1: self.ui.apilogin() # 인덱스 2 일 때 UIhandle 클래스의 timeseriesdataget 함수 불러오기 elif buttonid == 2: self.ui.timeseriesdataget() if __name__ == "__main__": app = QApplication(sys.argv) window = KiwoomAPIForm() window.show() sys.exit(app.exec_())
kiwoomapi.py(KIWOOMAPI)
키움API에서는 키움 API에서 제공하는 함수를 API개발가이드에 있는 그대로 사용하는 부분이다. 키움 API의 ActiveX를 변수로 받아서 변수로 받은 ActiveX를 사용해서 키움API 함수를 호출하는 것이 핵심이다.
#KIWOOMapi class KIWOOMapi: def __init__(self, ocx: object): self.ocx = ocx def CommConnect(self): self.ocx.dynamicCall("CommConnect()") def GetCodeListByMarket(self, mkcode: str): return self.ocx.dynamicCall("GetCodeListByMarket(Qstring)", mkcode) def SetInputValue(self, valuename: str, value: str): self.ocx.dynamicCall("SetInputValue(QString, QString)", valuename, value) def CommRqData(self, rqname: str, trcode: str, trname: str): self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, 0, trname) def GetCommData(self, trcode: str, trname: str, resultidx: int, item: str): return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)", trcode, trname, resultidx, item)
apihandle.py(APIHANDLE)
API핸들은 키움API함수를 사용해서 실제 KOAStudio에 구현된 것처럼 데이터를 실제로 주고받고, 처리할 수 있게 구현한 부분이다. rqstockpriceinfo 부분은 주식일봉차트 데이터를 요청하는 부분을 키움 API의 함수를 조합해서 만들고, getstockpricedf는 입수된 주식일봉차트 데이터를 dataframe으로 만들어서 return 하는 부분 insertstockpricedf는 받은 데이터를 아래에 나오는 SQL핸들을 이용해서 DB에 저장하는 부분이다.(자세한 설명은 [키움API]파이썬 주식 종가정보 불러오기3: 전체종목 기간별 종가조회 참조)
#APIhandle from kiwoomapi import KIWOOMapi from sqlhandle import SQLhandle import pandas as pd class APIhandle: def __init__(self, ocx: object): self.api = KIWOOMapi(ocx) self.sql = SQLhandle() def connectevent(self, err_code): if err_code == 0: return "Login Success!!" else: return "Login Failed" def rqstockpriceinfo(self, rqname: str, trcode: str, trname: str, stockcode: str, date: str): self.api.SetInputValue("종목코드", stockcode) self.api.SetInputValue("기준일자", date) self.api.SetInputValue("수정주가구분", "") self.api.CommRqData(rqname, trcode, trname) def getstockpricedf(self, trcode: str, trname: str): data = [] columns = ["종목코드", "일자", "현재가", "거래량", "시가", "고가", "저가"] for resultidx in range(600): items = [] for column in columns: item = self.api.GetCommData(trcode, trname, resultidx, column) if resultidx == 0 and column == "종목코드": stockcode = item.strip() items.append(item.strip()) elif resultidx > 0 and column == "종목코드": items.append(stockcode) else: items.append(item.strip()) data.append(items) data = pd.DataFrame(columns=columns, data=data) return data def insertstockpricedf(self, trcode: str, trname: str): df = self.getstockpricedf(trcode, trname) self.sql.timeseriesdatasave("stockdailychart", df)
sqlhandle.py(SQLHANDLE)
SQL핸들은 주식일봉데이터 저장을 위해서 존재한다. 그 사전작업으로 createengine으로 MySQL 연결을 만들고 checktableexists로 테이블이 존재하는지 확인하고, checkdataexists로 데이터가 존재하는지 확인하고, getcolumns로 데이터의 컬럼을 확인하는 작은 함수들이 존재한다.(자세한 코드 설명은 [키움API]파이썬 주식 종가정보 불러오기4: 데이터 DB저장(mysql) 참조)
#SQLhandle import pymysql import sqlalchemy as db import pandas as pd pymysql.install_as_MySQLdb() class SQLhandle: def __init__(self): self.user = "MySQL에 설정한 사용자명" self.passwd = "서버 비밀번호" self.host = "127.0.0.1" # 내 컴퓨터의 localhost 접속이므로 기본 ip인 127.0.0.1 입력 self.db_port = "포트번호" # 로컬이면 3306 self.db_name = "kiwoom" self.engine = self.createengine() def createengine(self): db_connection_str = "mysql+mysqldb://" + self.user + ":" + \ self.passwd + "@" + self.host + ":" + self.db_port +"/"+ \ self.db_name db_connection = db.create_engine(db_connection_str) return db_connection def checktableexists(self, engine, tablename: str): inspector = db.inspect(engine) return inspector.has_table(tablename) def checkdataexists(self, tablename: str, condition: list): with self.engine.connect() as connection: query = db.text(f"SELECT * FROM {tablename} WHERE 종목코드 = '{condition[0]}' AND 일자 = '{condition[1]}'") result = connection.execute(query) return result.fetchall() def getcolumns(self, tablename: str): with self.engine.connect() as connection: columns = db.inspect(connection).get_columns(tablename) columns = [column['name'] for column in columns] return columns def timeseriesdatasave(self, tablename: str, df: pd.DataFrame): if len(df)>0: conn = self.engine.connect() if self.checktableexists(self.engine, tablename): columns = self.getcolumns(tablename) columnnames = ", ".join(columns) valueslist = [] for row in df.itertuples(): conditions = [row[1], row[2]] if self.checkdataexists(tablename, conditions): pass else: values = [f"'{value}'" for value in row[1:]] values = ", ".join(values) valueslist.append(f"({values})") if len(valueslist) > 0: valueslist = ", ".join(valueslist) query = db.text(f"INSERT INTO {tablename} ({columnnames}) VALUES {valueslist}") try: conn.execute(query) conn.commit() except Exception as e: print(f"Error executing query: {e}") else: pass conn.close() else: df.to_sql(name=tablename, con=conn, index=False) conn.close() else: pass
uihandle.py(UIHANDLE)
UI핸들은 프로그램 UI에서 나타나는 이벤트를 처리하는 부분이다. 그림 1의 QTextEdit 클래스의 txt_result에 결과값을 넣어주는 postresult 함수, QDateEdit 클래스에서 날짜를 받아오는 getdate 함수, QSpinBox에서 값을 받아 오는 getspinboxvalue 함수 등이 기본적으로 존재한다. 그리고 화면에서 로그인 버튼을 눌렀을 때 작동하는 apilogin, 데이터 입수 버튼을 눌렀을 때 작동하는 timeseriesdataget 함수가 존재한다.
#UIhandle from PyQt5.QtWidgets import * import time from kiwoomapi import KIWOOMapi from apihandle import APIhandle from sqlhandle import SQLhandle 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.timer = time # data 받아서 txt_result 에 값 입력(set) def postresult(self, data: object): txt_result = self.mainui.findChild(QTextEdit, "txt_result") txt_result.setPlainText(str(data)) # 날짜 나타내는 위젯 아이디 받아서 날짜값 return def getdate(self, dateeditid: str): date = self.mainui.findChild(QDateEdit, dateeditid) return date # spinbox 아이디 받아서 spinbox 값 return def getspinboxvalue(self, spinboxeditid: str): spinbox = self.mainui.findChild(QSpinBox, spinboxeditid) return spinbox.value() # api 로그인 함수 def apilogin(self): # KIWOOMapi의 객체인 self.api 의 CommConnect 함수 사용 self.api.CommConnect() # 콜백함수 정의 def login_callback(err_code): # APIhandle의 객체인 self.handle 의 connectevent 함수 사용 result = self.handle.connectevent(err_code) # data 받아서 txt_result 에 값 입력(set) self.postresult(result) self.ocx.OnEventConnect.connect(login_callback) def timeseriesdataget(self): # KIWOOMapi의 객체인 self.api 의 GetCodeListByMarket 함수 사용 data = self.api.GetCodeListByMarket("0") stockcodes = data.split(";") stockcodes = stockcodes[:-1] if stockcodes[-1] == "" else stockcodes # 이 클래스의 위에 정의된 getdate 함수 사용해서 날짜 받기 basedate = self.getdate("date_base").date().toString("yyyyMMdd") # 이 클래스의 위에 정의된 getspinboxvalue 함수 사용해서 작업범위 받기 fromno = int(self.getspinboxvalue("spinBox_from")) tono = int(self.getspinboxvalue("spinBox_to")) # 콜백 함수 정의 # APIhandle의 객체인 self.handle 의 insertstockpricedf 함수 사용 callback = lambda: self.handle.insertstockpricedf("opt10081", "주식일봉차트") self.ocx.OnReceiveTrData.connect(callback) for stockcode in stockcodes[fromno:tono+1]: # APIhandle의 객체인 self.handle 의 rqstockpriceinfo 함수 사용 self.handle.rqstockpriceinfo(stockcode, "opt10081", "주식일봉차트", stockcode, basedate) self.timer.sleep(0.5) print("{} processed!".format(stockcode)) self.ocx.OnReceiveTrData.disconnect(callback) # 이 클래스의 위에 정의된 postresult 함수 사용해서 결과 txt_result에 입력하기 self.postresult("Process Done!")
반응형'금융퀀트 > (퀀트)증권사API활용(키움)' 카테고리의 다른 글
[키움API]python 샤프지수를 이용한 종목선정1: 코스피 종목 종가데이터 입수 (0) 2024.01.22 [키움API]파이썬 주식 종목별 종가정보 불러오기6: 데이터 수집시 주의사항 (0) 2024.01.20 [키움API]파이썬 주식 종가정보 불러오기4: 데이터 DB저장(mysql) (0) 2023.12.06 [키움API]파이썬 주식 종가정보 불러오기3: 전체종목 기간별 종가조회 (2) 2023.12.02 [키움API]파이썬 주식 종가정보 불러오기2: 개별종목 종가조회 (1) 2023.11.30