< 주가 일봉 데이터 저장 프로그램 개발 - 프로그램 GUI 구현>


1. "프로그램 개발 개요"

2. "프로그램 DB 설계 및 구현"

3. "키움 OPEN API 구현"

4. "프로그램 기능 구현"


해당 포스트를 읽기 전에 이전 포스트를 읽기 바란다.


"주가 일봉 데이터 저장 프로그램 개발" 포스트 시리즈의 마지막 포스트다. 이번 포스트에서 프로그램 GUI를 구현하고 다음 포스트에 실행 프로그램 파일을 올릴 예정이다. 구현할 GUI는 다음과 같다.


위 GUI는 "Qt Designer"라는 툴을 이용해 구현했다. "Qt Designer"에 대해서 잘 모른다면 아래에 링크된 사이트에서 배울 수 있다. 

"파이썬으로 배우는 알고리즘 트레이딩"


"Qt Designer"를 몰라도 된다. ui 구성은 아래 첨부파일에 있다. 다운 받아서 바로 사용하면 된다.


stock_regist.ui


전체 GUI 구현 코드는 다음과 같다. 이전 포스트에서 구현한 MainFunctions 클래스를 이용하고 모든 예외 처리를 하지 않았기에 간단하다.

import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
import MainFunctions as mf

form_class = uic.loadUiType("stock_regist.ui")[0]

class MainWindow(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setFixedSize(355,400)
        self.loginBtn.clicked.connect(self.loginBtn_clicked)
        self.dbBtn.clicked.connect(self.dbBtn_clicked)
        self.excelBtn.clicked.connect(self.excelBtn_clicked)

        self.mainFucntions = mf.MainFunctions()

    def loginBtn_clicked(self):
        pwd = self.pwdEdit.toPlainText()
        if pwd == "":
            QMessageBox.about(self, "로그인 실패", "패스워드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("패스워드를 입력하세요"))
            return
        if self.mainFucntions.db_login(pwd):
            QMessageBox.about(self, "로그인 성공", "로그인 성공")
            self.pwdEdit.setEnabled(False)
            self.loginBtn.setEnabled(False)
            self.dbBtn.setEnabled(True)
            self.listWidget.addItem(QListWidgetItem("로그인 성공"))
        else:
            QMessageBox.about(self, "로그인 실패", "패스워드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("비밀번호가 틀렸습니다. 다시 입력하세요"))

    def dbBtn_clicked(self):
        code = self.codeEdit.toPlainText()
        if code == '':
            QMessageBox.about(self, "실패", "종목 코드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 입력하세요"))
            return

        if self.mainFucntions.is_stock(code):
            QMessageBox.about(self, "입력 시작", "종목 데이터 전송을 시작하겠습니다.  OK를 눌러주세요")
            self.mainFucntions.db_insert_stock(code)
            QMessageBox.about(self, "입력 성공", "종목 데이터를 DB에 저장했습니다.")
            self.listWidget.addItem(QListWidgetItem("종목 데이터 전송 성공. DB에 데이터가 저장되었습니다."))

        else:
            QMessageBox.about(self, "실패", "종목 코드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 다시 입력하세요. 코드에 해당하는 종목이 없습니다."))

    def excelBtn_clicked(self):
        code = self.codeEdit.toPlainText()
        if code == '':
            QMessageBox.about(self, "실패", "종목 코드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 입력하세요"))
            return
        if self.mainFucntions.is_stock(code):
            QMessageBox.about(self, "입력 시작", "종목 데이터 전송 시작을 시작하겠습니다.. OK를 눌러주세요")
            self.mainFucntions.data_to_excel(code)
            QMessageBox.about(self, "입력 성공", "종목 데이터 엑셀 파일을 다운로드했습니다.")
            self.listWidget.addItem(QListWidgetItem("종목 데이터 전송 성공. 엑셀에 데이터가 저장되었습니다."))
            self.listWidget.addItem(QListWidgetItem("이 프로그램 설치 위치에 엑셀 파일이 있습니다."))
        else:
            QMessageBox.about(self, "실패", "종목 코드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 다시 입력하세요. 코드에 해당하는 종목이 없습니다."))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    app.exec_()


초기화 부분을 보자. 'uic.loadUiType"을 이용해 "Qt Designer"에서 만든 UI 파일을 로드하고 'setupUi"를 통해 부착한다. ~Btn.clicked.connect 를 이용해 버튼을 눌렀을 때 호출되는 메서드를 연결한다. "loginBtn", "dbBtn", "excelBtn"은 각각 "로그인", "DB 저장", "엑셀 저장" 버튼 ID 값이다. 해당 ID 값은 "Qt Designer" 툴에서 설정한다. 마지막으로 프로그램 기능 역할을 하는 MainFunctions()을 생성한다. MainFunctions 클래스 코드는 이전 포스트에 있다.  

form_class = uic.loadUiType("stock_regist.ui")[0]

class MainWindow(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setFixedSize(355,400)
        self.loginBtn.clicked.connect(self.loginBtn_clicked)
        self.dbBtn.clicked.connect(self.dbBtn_clicked)
        self.excelBtn.clicked.connect(self.excelBtn_clicked)

        self.mainFucntions = mf.MainFunctions()

로그인 버튼을 눌렀을 때 호출되는 메서드를 보자. 'pwdEdit.toPlainText()'를 통해 로그인 버튼 옆 에디트 박스에 입력된 값을 얻는다. 조건문을 통해 입력 값이 비어있는 지 확인하고 그렇다면 'QMessageBox.about'을 이용해 "로그인 실패" 팝업창을 띄우고 리스트 박스에 "패스워드를 입력하세요"라고 출력한다. 만약 입력 값이 비어있지 않다면 "mainFunctions.db_login(pwd)" 를 통해 로그인한다. 이전 포스트에서 배웠듯이 결과값이 True를 리턴하면 로그인을 성공한다. False를 리턴하면 로그인을 실패한다. 로그인에 성공했다면 로그인 버튼과 옆에 에디트 박스를 'setEnabled(False)'를 통해 비활성화 시키고 "DB 저장" 버튼을 활성화 시킨다. 

    def loginBtn_clicked(self):
        pwd = self.pwdEdit.toPlainText()
        if pwd == "":
            QMessageBox.about(self, "로그인 실패", "패스워드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("패스워드를 입력하세요"))
            return
        if self.mainFucntions.db_login(pwd):
            QMessageBox.about(self, "로그인 성공", "로그인 성공")
            self.pwdEdit.setEnabled(False)
            self.loginBtn.setEnabled(False)
            self.dbBtn.setEnabled(True)
            self.listWidget.addItem(QListWidgetItem("로그인 성공"))
        else:
            QMessageBox.about(self, "로그인 실패", "패스워드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("비밀번호가 틀렸습니다. 다시 입력하세요"))


"DB 저장" 버튼을 눌렀을 때 호출되는 소스는 다음과 같다. 사용자가 입력한 코드 값을 읽는다. 비어있는 지 확인하고 "mainFuctions.is_stock()" 메서드를 이용해 사용자 입력 값이 올바른 지 확인한다. 오라르다면 'mainFuctions.db_insert_stock()" 을 호출해 DB에 데이터를 저장한다. 

   def dbBtn_clicked(self):
        code = self.codeEdit.toPlainText()
        if code == '':
            QMessageBox.about(self, "실패", "종목 코드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 입력하세요"))
            return

        if self.mainFucntions.is_stock(code):
            QMessageBox.about(self, "입력 시작", "종목 데이터 전송을 시작하겠습니다.  OK를 눌러주세요")
            self.mainFucntions.db_insert_stock(code)
            QMessageBox.about(self, "입력 성공", "종목 데이터를 DB에 저장했습니다.")
            self.listWidget.addItem(QListWidgetItem("종목 데이터 전송 성공. DB에 데이터가 저장되었습니다."))

        else:
            QMessageBox.about(self, "실패", "종목 코드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 다시 입력하세요. 코드에 해당하는 종목이 없습니다."))


"엑셀 저장" 버튼을 눌렀을 때 호출되는 소스는 위에서 본 "dbBtn_clicked()" 메서드와 로직이 동일하다. 차이점이라면 'db_insert_stock()' 부분이 엑셀 저장을 위해 'data_to_excel()' 메서드로 바뀐 것 밖에 없다. 

    def excelBtn_clicked(self):
        code = self.codeEdit.toPlainText()
        if code == '':
            QMessageBox.about(self, "실패", "종목 코드를 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 입력하세요"))
            return
        if self.mainFucntions.is_stock(code):
            QMessageBox.about(self, "입력 시작", "종목 데이터 전송 시작을 시작하겠습니다.. OK를 눌러주세요")
            self.mainFucntions.data_to_excel(code)
            QMessageBox.about(self, "입력 성공", "종목 데이터 엑셀 파일을 다운로드했습니다.")
            self.listWidget.addItem(QListWidgetItem("종목 데이터 전송 성공. 엑셀에 데이터가 저장되었습니다."))
            self.listWidget.addItem(QListWidgetItem("이 프로그램 설치 위치에 엑셀 파일이 있습니다."))
        else:
            QMessageBox.about(self, "실패", "종목 코드를 다시 입력하세요")
            self.listWidget.addItem(QListWidgetItem("종목 코드를 다시 입력하세요. 코드에 해당하는 종목이 없습니다."))



이것으로 GUI 구현을 마쳤다. 주요 기능들을 이전 포스트들에서 다 구현했기에 쉽게 구현 가능했다. 해당 전체 소스파일은 아래 첨부 파일에 있다. 


MainWindow.py


여기까지 해서 "주가 일봉 데이터 저장 프로그램" 개발을 마쳤다. 



필자는 파이썬을 전문으로 하는 현업 프로그래머가 아니라 평범한 컴퓨터 공학과 대학생입니다. 따라서 코드의 효율성, 최적화 측면에서 부족한 면이 있습니다. 잘못된 부분이나 좀 더 효율적으로 수정해야할 부분이 있다면 댓글 부탁드립니다.

해당 프로그램의 최종 결과 파일 및 소스 코드는 다음 포스트에 첨부할 예정입니다. 

< 주가 일봉 데이터 저장 프로그램 개발 - 프로그램 기능 구현>


1. "프로그램 개발 개요"

2. "프로그램 DB 설계 및 구현"

3. "키움 OPEN API 구현"


해당 포스트를 읽기 전에 이전 포스트를 읽기 바란다.


이번에는 프로그램 전체 기능을 구현할 것이다. 다음 포스트에서 구현할 프로그램 GUI를 보고 어느 기능이 필요한 지 살펴 보자. 


보다시피 DB 로그인, 데이터 DB 저장, 데이터 엑셀 저장 기능이 필요한 걸 알 수 있다. 추가로 입력한 종목 코드가 실제 상장된 주식의 종목 코드인지 확인하는 기능이 필요하다. 코스피/코스닥 지수의 경우는 종목 코드가 없기 때문에 "kospi"를 입력하면 코스피 지수 일봉 데이터가, "kosdaq"을 입력하면 코스닥 지수 일봉 데이터가 요청된다.   


전체 코드는 다음과 같다. 이전 포스트에서 Kiwoom.py와 StockDB.py를 구현했기에 소스가 간단하다.

import Kiwoom as kw
import DB as db
from datetime import date, timedelta


class MainFunctions():
    def __init__(self):
        self.ki = kw.Kiwoom()
        self.dB = db.StockDB()

        self.date = date.today() - timedelta(1)

    def db_login(self, password):
        return self.dB.init(password)

    def is_stock(self, code):
        if code == 'kospi' or code == 'kosdaq':
            return code
        stock_name = self.ki.is_stock(code)
        if stock_name is not "":
            return stock_name
        return None

    def db_insert_stock(self, code):
        table_name = ""
        if code == 'kospi':
            table_name = code
            code = '001'

        elif code == 'kosdaq':
            table_name = code
            code = '101'

        else:
            table_name = 'a' + code

        # 테이블이 생성되지 않았으면 테이블 생성
        self.dB.create_table(table_name)
        # 테이블에 입력된 데이터 중 가장 최근 날짜 획득
        recent_day = self.dB.select_max_date(table_name)

        if recent_day == self.date:
            return

        #  일봉 데이터 획득
        if code == '001' or code =='101':
            data = self.ki.req_index_daily_value(code, recent_day)
        else:
            data = self.ki.req_stock_daily_value(code, recent_day)
        # 테이블에 데이터 insert
        self.dB.insert_chart(data, table_name)

    def data_to_excel(self, code):
        if code == 'kospi':
            # 코스피 일봉 데이터 획득
            data = self.ki.req_index_daily_value('001', None)
            data.to_excel('./kospi.xlsx')
        elif code == 'kosdaq':
            # 코스닥 일봉 데이터 획득
            data = self.ki.req_index_daily_value('101', None)
            data.to_excel('./kosdaq.xlsx')
        else:
            # 종목 일봉 데이터 획득
            data = self.ki.req_stock_daily_value(code, None)
            data.to_excel('./a' + code + '.xlsx')


다음 코드는 DB에 로그인하는 부분이다. 'StockDB' 클래스의 init() 메서드를 호출한다. 'init()' 메서드 코드는 위에 링크된 "프로그램 DB 및 설계" 포스트에 있다. init() 메서드에 password를 전달하면 DB에 접속한다. 만약 패스워드가 틀리다면 False를 리턴한다. 
    def db_login(self,password):
        return self.dB.init(password)

아래 코드는 인자로 전달된 code 값이 올바른 지 확인한다. "ki.is_stock(code)" 메서드를 이용해 code 값을 가진 주식이 있는 지 확인한다. 해당 메서드는 위에 링크된 "키움 OPEN API 구현" 포스트에 있다. code 값이 올바르지 않다면 False를 리턴한다
    def is_stock(self,code):
        if code == 'kospi' or code == 'kosdaq' or  self.ki.is_stock(code) is not "":
            return True
        else:
            return False


다음 코드는 주식의 일봉 데이터를 엑셀로 저장한다. code에 해당하는 주식의 일봉 데이터를 요청한다. 결과 값은 'data'에 DataFrame 형식으로 저장된다. 'data.to_excel()' 메서드로 프로그램 설치 장소에 데이터를 엑셀로 변환한다. 

    def data_to_excel(self,code):
        if code == 'kospi':
            # 코스피 일봉 데이터 획득
            data = self.ki.req_index_daily_value('001', None)
            data.to_excel('./kospi.xlsx')
        elif code == 'kosdaq':
            # 코스닥 일봉 데이터 획득
            data = self.ki.req_index_daily_value('101', None)
            data.to_excel('./kosdaq.xlsx')
        else:
            # 종목 일봉 데이터 획득
            data = self.ki.req_stock_daily_value(code, None)
            data.to_excel('./a'+code+'.xlsx')


다음은 code에 해당하는 주식 일봉 데이터를 DB에 저장하는 소스이다. 소스에 달린 주석을 보면 이해 가능할 것이다. 참고로 'self.date'는 클래스 초기화 부분에서 생성한 변수로 프로그램을 실행한 어제 날짜를 가지고 있다. 'if recent_day == self. date' 는 DB에 저장된 데이터의 최근 날짜가 어제 날짜와 동일한 지 확인한다. 같다면 바로 리턴한다. 이미 주식 일봉 데이터 전체를 가지기 때문이다.(해당 프로그램은 어제까지 주식 일봉 데이터를 저장한다.) 

    def db_insert_stock(self, code):
        table_name = ""
        if code == 'kospi':
            table_name = code
            code = '001'

        elif code == 'kosdaq':
            table_name = code
            code = '101'

        else:
            table_name = 'a' + code

        # 테이블이 생성되지 않았으면 테이블 생성
        self.dB.create_table(table_name)
        # 테이블에 입력된 데이터 중 가장 최근 날짜 획득
        recent_day = self.dB.select_max_date(table_name)

        if recent_day == self.date:
            return

        #  일봉 데이터 획득
        if code == '001' or code =='101':
            data = self.ki.req_index_daily_value(code, recent_day)
        else:
            data = self.ki.req_stock_daily_value(code, recent_day)
        # 테이블에 데이터 insert
        self.dB.insert_chart(data, table_name)


여기까지 전체적인 프로그램 기능을 구현했다. 대부분이 이전 포스트에서 구현한 메서드들을 사용한 것이기에 해당 포스트까지 잘 따라왔다면 이해하기 쉬울 것이다. 전체 소스코드는 아래 첨부파일에 있다.


MainFunctions.py


다음 포스트에서는 프로그램 GUI를 구현한다. GUI만 구현하면 주가 일봉 데이터 저장 프로그램 개발을 마치게 된다. 

 

+ Recent posts