PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 데이터 요청·수신하기

  • -

지난 게시글에서 캡슐화라는 개념에 대해 살펴보았고, SetInputValue와 CommRqData라는 두 개의 함수를 캡슐화를 고려하며 코드를 제작했었다. 그렇다면 이제는 데이터를 요청하는 데에 있어서 어떠한 값들을 입력해야하는지에 대해 어떤 자료를 참고해야 하는지 살펴보고, 실제로 데이터를 요청하고 수신해서 출력하는 방법에 대해 알아보고자 한다.(분량 김)

일단 데이터를 요청하는 구조는 이전 게시글에서도 여러 번 살펴봤듯이, SetInputValue → CommRqData → Event → connect to definition → GetCommData의 순서로 이루어진다.

 

 

SetInputValue에 무슨 데이터를 입력해야 돼?

어떤 데이터를 입력해야 하는지는 사실 어떤 데이터를 수신받고자 하는지에 따라 다르다. 예를 들어 국세청에서 지난 해 내가 낸 세금을 조회하기 위해서는 반드시 나의 개인정보들을 입력해야 한다. 이 때 당신의 이름과 연락처에 나의 집 주소를 입력하게 된다면 국세청 데이터베이스에서는 어떤 반응이 일어날까? ("이 새끼 뭐하자는 거지?")

이렇듯 우리는 조회하고자 하는 데이터에 알맞은 데이터를 전달해줘야 하는데, 어떤 데이터를 전달해줘야 하는지는 어디서 확인할 수 있을까? 바로 이전에 설치했던 KOA Studio를 열어서 확인할 수 있다. KOA Studio 프로그램을 열어서 우리가 확인하고자 하는 데이터를 한 번 확인해보도록 하자. 본인은 많고 많은 tr 중   OPT10016 : 신고저가요청  을 사용해볼 예정이다. 굳이 이거로 할 필요는 없고, 하고 싶은 다른 TR을 사용해봐도 된다.

아래의 사진을 보면 알 수 있듯이, 거의 대부분의 TR은 INPUT값과 OUTPUT값으로 분리되어 있으며 이 중 INPUT에 있는 목록들이 우리가 SetInputValue를 사용해서 데이터를 입력해야 하는 것들이고, OUTPUT에 있는 목록들이 GetCommData를 사용해서 데이터를 얻어올 수 있는 것들이다. 여기서, 데이터를 입력해야 하는 것들은 반드시 모두 입력해줘야 하지만 데이터를 얻어오는 것들은 반드시 사용하지 않아도 된다. 다시 말해, 내가 필요한 데이터들만 뽑아다가 사용하는 것은 가능하지만 내가 입력하고 싶은 정보만 입력할 수는 없다는 것이다. 

그럼 위의 사진에서   OPT10016 : 신고저가요청  의 INPUT을 보면 시장구분, 신고저구분, 고저종구분, 종목조건, 거래량구분, 신용조건, 상하한포함, 기간이라는 8개의 데이터를 입력해야 한다는 것을 확인할 수 있다. 즉, 우리는 SetInputValue를 8번을 이용해서 해당 데이터를 입력해줘야 한다는 것이다. 여기서 데이터를 입력하는 세부적인 방법은 해당 TR 명을 눌러보면 오른쪽에 사용 설명서가 출력된다. OPT10016 : 신고저가요청의 세부 설명을 확인해보도록 하자. (잘 안 보인다면, 직접 프로그램에서 본인이 선택한 TR 명을 눌러서 확인하도록 하자.)

위의 사진을 보면 시장구분에는 000은 전체, 001은 코스피, 101은 코스닥으로 구분해서 데이터를 입력할 수 있다고 설명하고 있고, 신고저구분에서는 1이 신고가, 2가 신저가를 나타내며 고저종구분에서는 1은 고저기준, 2는 종가기준으로 설정할 수 있음을 확인할 수 있다. 

이 데이터들 중 어떤 설정값을 입력해서 데이터를 조회할지는 개개인의 마음대로 설정할 수 있지만, 위의 설명서에서 알려주고 있는 설정값 외의 데이터를 입력하게 되면 오류가 발생할 것이다. 그렇다면 여기서   시장구분은 전체(000), 신고저구분은 신고가(1), 고저종구분은 고저기준(1), 종목조건은 전체조회(0), 거래량구분도 전체조회(00000), 신용조건도 전체조회(0), 상하한포함은 포함(1), 기간은 60일(60)  으로 설정해서 데이터를 조회하기 위해서는 어떤 코드를 구축해야 할까? 일단 입력해야 하는 값이 8개이니 코드도 8줄이 될 것이다.
※ Line : 19~27

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

 

CommRqData(RqName, TrCode, PreNext, ScrNo)

이제 우리는 서버에서 요구하는 데이터들을 모두 입력했으니, 이제는 데이터를 요청하면 된다. 데이터를 요청하는 과정에 있어서 RqName와 TrCode, PreNext, ScrNo 등등 처음 보는 것들이 나오는데 하나하나 파헤쳐보도록 하자.

가장 먼저   RqName  은 아무 것도 아니고 참고해야 할 가이드라인이 있는 것도 아니고 단순하게 우리가 지정해주면 되는 것이다. 그니까, RqName에는 "OPT10016"을 입력해줘도 되고 "신고저가요청"을 입력해줘도 되고, "쓰고버릴거"라고 입력해줘도 된다. 그만큼 아무런 영향이 없는 값이지만, 우리가 추후 데이터를 받아올 때 필요하기 때문에 다른 함수와는 구분이 가능할 수준으로 입력해주도록 하자.

다음으로   TrCode  는 우리가 KOA Studio에서 봤던 OPT10016 등과 같은 이름에 해당한다. 만약 일봉차트조회요청을 사용했다면 TrCode 자리에는 "opt10081"을, 신고저가요청을 사용한다면 "OPT10016"을 입력해줘야 한다. 이는 대소문자를 구분해서 입력해줘야 하기 때문에 반드시 KOA Studio를 참고해야 한다.

  PreNext  는 연속조회 여부를 나타내는 것인데, 이는 차트 데이터를 조회하는 시점 외에는 크게 중요하지 않기 때문에 OPT10016 : 신고저가요청 부분에서는 설명을 하지 않고, 차트 조회 시에 설명하도록 하겠다. (간략히 설명하자면 0을 전달해주면 단일조회, 2를 전달해주면 연속조회에 해당한디.) 

마지막으로   ScrNo  는 말 그대로 화면 번호(Screen Number)를 의미한다. 영웅문을 사용해봤다면 알겠지만, 각 창에는 화면 번호들이 있다. 현재가 화면[0101], 차트 화면[0600~0603] 등등이 있는데, 우리가 사용하고자 하는 TrCode(OPT10016, opt10081)은 모두 제각각의 화면 번호가 있는데, 이 역시 KOA Studio에서 확인할 수 있다. 프로그램의 하단에서 [화면목록] 메뉴에 들어간 후, 아래의 사진처럼 TrCode를 입력해서 검색해주면 화면 번호를 찾아준다. OPT10016의 경우 [0161]이다. 

 

이제 이 내용을 바탕으로 OPT10016 : 신고저가요청의 데이터를 요청하는 CommRqData 부분을 제작해보도록 하자.
※ Line : 28

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 


728x90

 

 

Event 처리하기

이제 데이터를 요청했으니 서버에서는 데이터 요청을 확인했다는 이벤트를 발생시킬 것이다. 키움증권 Open API에서 발생시키는 이벤트는 총 8종류인데, 일단 간단하게 데이터 송수신 시에 사용할 이벤트만 구축하고 넘어가보도록 하자. 이벤트를 처리하는 코드는 반드시   def __init__(self):  구간에서 작성해줘야 하며, 데이터 송수신시에는 OnReceiveTrData라는 이벤트가 발생하게 되므로 해당 이벤트만 처리해주면 된다.
※ Line : 19~23

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self):
        pass


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

위의 코드를 보면, 19번째 줄에서는 OnReceiveTrData라는 이벤트가 발생했을 때 self.receive_trdata라는 함수로 연결(connect)되도록 설정했음을 확인할 수 있다. 다시 말해, 지금까지 우리는 데이터 입력(SetInputValue)과 데이터 요청(CommRqData) 부분을 모두 구현했으니, 해당 구간의 코드가 정상적이라면 데이터가 요청됐다는 이벤트(OnReceiveTrData)가 발생할 것이고, 그 결과로 연결되어 있는 함수(self.receive_trdata)가 실행되는 것이다.

 

 

receive_trdata 수정하기

OnReceiveTrData를 통해 데이터를 수신하기 위해서는 receive_trdata를 통해 데이터를 얻어와야 하는데, 그러기 위해서는 def receive_trdata 내에 인자가 들어올 자리를 생성해줘야 한다. 아래의 개발 가이드 사진을 살펴보도록 하자.

딱 봐도 전달받는 데이터가 되게 복잡하게 되어있다.

 

원형 부분에서의 LPCTSTR은 string, LONG은 int형을 의미하는 것으로, ScrNo라는 화면 번호 데이터는 문자열 형태로 반환되고, 데이터 개수라는 DataLength라는 데이터는 숫자형으로 반환된다는 것을 확인할 수 있다. 다만 그 아래의 입력값 부분을 보면 "사용하지 않음"이라고 표시되어 있는 것들이 있기 때문에, 해당 부분을 제외한 나머지 인자들로만 함수를 제작해보도록 하자.

또한 비고를 통해서도 확인할 수 있는 내용이 있는데, RqName으로는 CommRqData에서 사용했던 "신고저가요청"이라는 RqName과 동일하며, TrCode도 CommRqData에서 사용했던 "OPT10016"이라는 TrCode와 동일하다고 설명하고 있다.
※ Line : 22

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        pass


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

데이터를 수신해볼까?

앞서 CommRqData 부분을 작성할 때, RqName의 인자로 "opt10016"이 됐든 "신고저가요청"이 됐든 "쓰고버릴것"이 됐든 간에 상관없지만, 입력했던 값은 기억하고 있어야 한다는 설명을 했었다. 그 이유는 바로 receive_trdata라는 함수에서 RqName이라는 인자의 자리에 전달되기 때문이다. 다시 말해, 우리는 CommRqData에서 RqName 자리에는 기능별로 서로 다른 이름을 입력해줘야 receive_trdata 내에서 해당 데이터 처리를 구분할 수 있다는 것이다. (지금은 어렵겠지만, 이쯤하고 넘어가도 된다.) 그럼 이제 receive_trdata 내에서 우리가 입력했던 RqName인 "신고저가요청"을 기준으로 하여 또 다른 함수로 연결해보도록 하자.
※ Line : 22~29

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        if RqName == "신고저가요청":
            self.OPT10016(TrCode, RecordName)
        pass

    """데이터 처리 구간"""
    def OPT10016(self, TrCode, RecordName):
        pass


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

 

 

GetCommData 사용하기

OnReceiveTrData 이벤트가 발생하면 receive_trdata 함수를 실행하고, 해당 함수의 RqName을 기준으로 TrCode를 구분해서 실행하도록 코드를 구축했으니, 이제 OPT10016 함수 내에서 GetCommData 함수를 이용해서 데이터를 조회하면 되는데, 앞서 SetInputValue와 CommRqData를 캡슐화를 이용해 함수로 제작해주었던 것처럼 GetCommData로 캡슐로 만들어주면 된다. 이 역시 아래의 사진을 참고해서 여러분이 직접 코드를 제작해보고 결과물을 봐도 좋다! 이 역시 반환값에 수신 데이터라는 내용이 있기 때문에, 전달받은 값을 return으로 보내주어야 한다.
※ Line : 53~54

 

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        if RqName == "신고저가요청":
            self.OPT10016(TrCode, RecordName)
        pass

    """데이터 처리 구간"""
    def OPT10016(self, TrCode, RecordName):
        pass


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])

    """데이터 수신 구간"""
    def __getcommdata(self, trcode, recordname, index, itemname):
        return self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", [trcode, recordname, index, itemname])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

잠깐, 못 보던 애들이 나타났는다. : index, itemname

데이터를 수신하기 위해 사용하는 함수인 GetCommData에서 보면 trcode, recordname, index, itemname이라는 네 개의 인자를 전달해줄 것을 요구하고 있는데, 여기서 trcode와 recordname은   def receive_trdata  함수 내에서 TrCode, RecordName이라는 인자를 전달해주었다. 그렇다면 index와 itemname만 처리해주면 되는데, 이 역시 하나씩 파헤쳐보도록 하자. 

일단   index  라는 인자는 데이터의 개수를 의미한다. 다시 말해, 데이터가 하나만 있다면 0, 데이터가 두 개가 있다면 1, 데이터가 3개가 있다면 2이라는 값을 전달해줘야 한다. 그렇다면 여기서 우리가 반환받을 데이터의 개수를 구하기 위해서는 어떻게 해야하는지 감이 안오는데, 이 역시 키움증권에서는 GetRepeatCnt라는 함수를 통해 데이터의 개수를 얻어갈 수 있도록 하고 있다. 마지막으로   itemname  이라는 인자는 정말이지 단순하게도, KOA Studio에서 살펴봤던 OUTPUT에 해당하는 값들이다.

 

 

데이터 개수좀 알려주세요. : GetRepeatCnt()

이 함수 역시 캡슐로 만들어줄 것인데, 아래의 이미지를 참고해서 제작하면 된다. 다만 앞서 살펴봤던 다른 함수들과의 차이점을 찾아보자면 "반환값"이라는 란에 데이터가 있다는 것이다. 그렇다면 어떻게 제작해주면 될까? 반환값이 있다는 것은 데이터 요청과 동시에 그 결과값을 반환받는다는 것을 의미한다.

그렇기 때문에 GetRepeatCnt() 함수는 데이터 요청과 동시에 그로부터 반환받는 데이터를 곧바로 return을 사용해서 데이터를 반환해줌으로써 데이터의 개수를 곧바로 사용할 수 있게 해주어야 한다.
※ Line : 57~58

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        if RqName == "신고저가요청":
            self.OPT10016(TrCode, RecordName)
        pass

    """데이터 처리 구간"""
    def OPT10016(self, TrCode, RecordName):
        pass


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])

    """데이터 수신 구간"""
    def __getcommdata(self, trcode, recordname, index, itemname):
        return self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", [trcode, recordname, index, itemname])

    """데이터 개수 반환"""
    def __getrepeatcnt(self, trcode, recordname):
        return self.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", [trcode, recordname])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

 

다시 GetCommData()를 사용해보자.

일단 아래의 사진에서 확인할 수 있듯이,   OPT10016 : 신고저가요청  을 통해 얻을 수 있는 데이터의 종류로는   종목코드, 종목명, 현재가, 전일대비기호, 전일대비, 등락률, 거래량, 전일거래량대비율, 매도호가, 매수호가, 고가, 저가  로 12개의 데이터가 있는데, 이 중에서 종목코드와 종목명, 현재가라는 세 개의 데이터만 얻어와보도록 하겠다. 

또한 각각의 데이터(종목코드는 item_code, 종목명은 item_name, 현재가는 now)를 각각의 변수에 입력해준 후에 print문을 사용해서 데이터가 제대로 수신됐는지 살펴보도록 하자.
※ Line : 29~40

 

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        if RqName == "신고저가요청":
            self.OPT10016(TrCode, RecordName)
        pass

    """데이터 처리 구간"""
    def OPT10016(self, TrCode, RecordName):
        """
        data_len : 데이터의 개수를 구함
        for문 : 데이터의 개수를 하나씩 돌면서 index의 인자로 전달
        """
        data_len = self.__getrepeatcnt(TrCode, RecordName)

        for index in range(data_len):
            item_code = self.__getcommdata(TrCode, RecordName, index, "종목코드")
            item_name = self.__getcommdata(TrCode, RecordName, index, "종목명")
            now = self.__getcommdata(TrCode, RecordName, index, "현재가")

            print(f"[{item_code}]:{item_name}. 현재가:{now}")


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])

    """데이터 수신 구간"""
    def __getcommdata(self, trcode, recordname, index, itemname):
        return self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", [trcode, recordname, index, itemname])

    """데이터 개수 반환"""
    def __getrepeatcnt(self, trcode, recordname):
        return self.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", [trcode, recordname])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

 

 

OPT10016 함수는 어떻게 실행해?

우리는 사전에 GUI 파일을 생성했고 GUI 파일과 코드를 연결해두었기 때문에, GUI 파일 내에서 특정 버튼을 생성해서 opt10016 함수를 실행하도록 연결해주면 된다. 일단 GUI 파일 내에 opt10016 함수를 실행할 하나의 버튼을 생성하도록 하자. 생성한 후 오른쪽 부분을 보면 빨간색 네모칸이 쳐져있는 것처럼 해당 버튼의 객체 이름을 확인할 수 있는데, 해당 객체 이름을 코드로 구축해야 하기 때문에 기억해두도록 하자. 본인의 GUI 파일 내에서의 버튼은 pushButton이라는 이름을 갖고 있다.(이름을 다른 이름으로 변경해도 되는데, 변경했다면 그 이름으로 코드를 구축해야 한다.)

 

그 후 다시 코드의   def __init__(self)  부분에서, 해당 부분이 클릭(clikced)됐을 때 opt10016을 요청하는 함수(request_opt10016)를 실행하도록 연결(connect)해주자.
※ Line : 18

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *

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


class tradesystem(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  ## GUI 켜기
        self.setWindowTitle("주식 프로그램")  ## 프로그램 화면 이름 설정

        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  ## OpenAPI 시작
        self.kiwoom.dynamicCall("CommConnect()")  ## 로그인 요청

        self.pushButton.clicked.connect(self.request_opt10016)

        """이벤트 처리 구간"""
        self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)  ## 데이터 조회 요청 처리 함수

    """데이터 수신 구간"""
    def receive_trdata(self, ScrNo, RqName, TrCode, RecordName, PreNext):
        if RqName == "신고저가요청":
            self.OPT10016(TrCode, RecordName)
        pass

    """데이터 처리 구간"""
    def OPT10016(self, TrCode, RecordName):
        """
        data_len : 데이터의 개수를 구함
        for문 : 데이터의 개수를 하나씩 돌면서 index의 인자로 전달
        """
        data_len = self.__getrepeatcnt(TrCode, RecordName)

        for index in range(data_len):
            item_code = self.__getcommdata(TrCode, RecordName, index, "종목코드")
            item_name = self.__getcommdata(TrCode, RecordName, index, "종목명")
            now = self.__getcommdata(TrCode, RecordName, index, "현재가")

            print(f"[{item_code}]:{item_name}. 현재가:{now}")


    """OPT10016 : 신고저가요청"""
    def request_opt10016(self):
        self.__setinputvalue("시장구분", "000")
        self.__setinputvalue("신고저구분", "1")
        self.__setinputvalue("고저종구분", "1")
        self.__setinputvalue("종목조건", "0")
        self.__setinputvalue("거래량구분", "00000")
        self.__setinputvalue("신용조건", "0")
        self.__setinputvalue("상하한포함", "1")
        self.__setinputvalue("기간", "60")
        self.__commrqdata("신고저가요청", "OPT10016", 0, "0161")

    """데이터 입력 구간"""
    def __setinputvalue(self, item, value):
        self.kiwoom.dynamicCall("SetInputValue(QString, QString)", [item, value])

    """데이터 요청 구간"""
    def __commrqdata(self, rqname, trcode, pre, scrno):
        self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", [rqname, trcode, pre, scrno])

    """데이터 수신 구간"""
    def __getcommdata(self, trcode, recordname, index, itemname):
        return self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", [trcode, recordname, index, itemname])

    """데이터 개수 반환"""
    def __getrepeatcnt(self, trcode, recordname):
        return self.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", [trcode, recordname])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = tradesystem()
    myWindow.show()
    app.exec_()

 

이제 코드를 실행해서 해당 버튼을 클릭해주면, 아래와 같은 결과 데이터를 얻어올 수 있을 것이다. 데이터가 조금 지저분한 형태로 오긴 하는데, 이러한 유형의 데이터는 문자열 내에서 공백을 제거하는 .stirp()을 통해 처리해줄 수 있다. getcommdata 함수를 사용한 부분에서 수정한 후에, 결과 데이터를 확인해보도록 하자.

[              900290]:                                                                                                 GRT. 현재가:               -1240
[              000210]:                                                                                                  DL. 현재가:              +71300
[              004310]:                                                                                            현대약품. 현재가:               +5730
[              004990]:                                                                                            롯데지주. 현재가:              +37600
[              005850]:                                                                                              에스엘. 현재가:              -31250
[              033100]:                                                                                            제룡전기. 현재가:               +6930
[              037440]:                                                                                                희림. 현재가:               -8780
[              054930]:                                                                                                유신. 현재가:              +29400
[              094170]:                                                                                          동운아나텍. 현재가:               +8660
[              099140]:                                                                                       KODEX 차이나H. 현재가:              -17725
[              114800]:                                                                                        KODEX 인버스. 현재가:               +5070
[              120030]:                                                                                            조선선재. 현재가:             +160000
[              123310]:                                                                                        TIGER 인버스. 현재가:               +5655
[              145670]:                                                                                       KINDEX 인버스. 현재가:               +6245
[              168580]:                                                                               KINDEX 중국본토CSI300. 현재가:              -30970
[              174360]:                                                                         KBSTAR 중국본토대형주CSI100. 현재가:              -20685
[              190620]:                                                                                   KINDEX 단기통안채. 현재가:              100605
[              192090]:                                                                                  TIGER 차이나CSI300. 현재가:              -11470
[              196230]:                                                                                   KBSTAR 단기통안채. 현재가:             -105250
[              219900]:                                                                 KINDEX 중국본토CSI300레버리지(합성). 현재가:               -5020
[              220100]:                                                                                              퓨쳐켐. 현재가:              +18300
    :
(이하생략)
    :

 

 

 

OPT10016 수정 전 후 비교

※ Line : 10~12 → 24~26

"""수정 전"""
def OPT10016(self, TrCode, RecordName):
    """
    data_len : 데이터의 개수를 구함
    for문 : 데이터의 개수를 하나씩 돌면서 index의 인자로 전달
    """
    data_len = self.__getrepeatcnt(TrCode, RecordName)

    for index in range(data_len):
        item_code = self.__getcommdata(TrCode, RecordName, index, "종목코드")
        item_name = self.__getcommdata(TrCode, RecordName, index, "종목명")
        now = self.__getcommdata(TrCode, RecordName, index, "현재가")
        print(f"[{item_code}]:{item_name}. 현재가:{now}")

"""수정 이후"""
def OPT10016(self, TrCode, RecordName):
    """
    data_len : 데이터의 개수를 구함
    for문 : 데이터의 개수를 하나씩 돌면서 index의 인자로 전달
    """
    data_len = self.__getrepeatcnt(TrCode, RecordName)

    for index in range(data_len):
        item_code = self.__getcommdata(TrCode, RecordName, index, "종목코드").strip(" ")
        item_name = self.__getcommdata(TrCode, RecordName, index, "종목명").strip(" ")
        now = self.__getcommdata(TrCode, RecordName, index, "현재가").strip(" ")
        print(f"[{item_code}]:{item_name}. 현재가:{now}")

 

 

.strip(" ") 사용 이후의 결과 데이터

[900290]:GRT. 현재가:-1240
[000210]:DL. 현재가:+71300
[004310]:현대약품. 현재가:+5730
[004990]:롯데지주. 현재가:+37600
[005850]:에스엘. 현재가:-31250
[033100]:제룡전기. 현재가:+6930
        :
      (중략)
        :
[610036]:메리츠 인버스 2X 대표 농산물 선물 ETN(H). 현재가:+13045
[700008]:하나 인버스 코스닥150 선물 ETN. 현재가:+12540
[700010]:하나 인버스 2X 구리 선물 ETN(H). 현재가:+26080
[700012]:하나 인버스 2X 옥수수 선물 ETN(H). 현재가:+13675

 

 

 


728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.