ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [키움API]파이썬 주식 종가정보 불러오기3: 전체종목 기간별 종가조회
    금융퀀트/(퀀트)증권사API활용(키움) 2023. 12. 2. 09:13
    반응형

    시계열데이터 만들기: GetCommData 함수 재구성

    KOAStudio 조회 화면을 보면 아래 그림 1에 표시됨 부분의 숫자가 0에서부터 늘어나면서 599까지 조회가 되는 것을 알 수 있다. 0 번째 인덱스 데이터가 기준일자 데이터이고 599 번째 데이터는 599 영업일 뒤의 데이터니까 약 2년 6개월 정도 기간 동안의 종가 데이터를 한 번에 볼 수 있는 것이다.(1년은 약 250 영업일 정도 된다.)

    그림1: opt10081 tr데이터 요청 전체 구조

    위 그림 1의 인덱스를 나타내는 부분은 아래 GetCommData 의 세 번째 변수로 지금까지 단순히 0으로 설정했던 부분을 변수로 아래와 같이 바꿔주면 된다. 

    # AS-IS
    def GetCommData(self, trcode: str, trname: str, item: str):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)", trcode, trname, 0, item)    
    
    # TO-BE
    # 0 부분을 변수로 두면 다양한 날짜의 데이터를 뽑을 수 있음
    def GetCommData(self, trcode: str, trname: str, resultidx: int, item: str):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)", trcode, trname, resultidx, item)

     

    종목별 데이터: DataFrame 구성하기

    한 가지 종목만 불러올 때는 [키움API]파이썬 코스피 종목별 종가정보 불러오기2: 개별종목 종가조회에서 했던 것처럼 getstockpriceinfo 함수를 만들어서 단순히 종목 정보를 print 해주면 되었다. 하지만 여러 종목에 대한 작업을 할 때는 과거까지 만들어진 데이터프레임을 불러와서 새로 만든 데이터 프레임과 합치는 과정이 추가되어야 한다. 결국  [키움API]파이썬 코스피 종목별 종가정보 불러오기2: 개별종목 종가조회에서 만들었던 getstockpriceinfo 라는 함수를 아래와 같이 바꿀 수 있다. 

    # getstockpriceinfo 함수, 과거까지 작업된 df 라는 데이터프레임을 변수로 받음
    def getstockpriceinfo(self, trcode: str, trname: str, df: pd.DataFrame):
        data = []
        # 컬럼 정의
        columns = ["종목코드", "일자", "현재가", "거래량", "시가", "고가", "저가"]
        # GetCommData 에서 resultidx 를 변수로 줘서 599 번째 index 결과까지 구성
        for resultidx in range(600):
            items = []
            # 각 컬럼별 데이터 append 하여 특정index(일자)에 대한 종목의 데이터 구성
            for column in columns:
                item = self.GetCommData(trcode, trname, resultidx, column)
                # index 가 0 일 때만 데이터에 "종목코드" 를 주기 때문에 index 가 0 일 때 받은
                # "종목코드" 정보를 stockcode에 저장하여 index > 0 일 때는 stockcode 정보 사용
                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 가 구성되게 됨 
            data.append(items)
        # 데이터 프레임 생성
        data = pd.DataFrame(columns=columns, data=data)
        # concat을 사용해여 기존 데이터프레임과 통합
        df = pd.concat([df, data])
        return df

     

    getstockpriceinfo 함수는 지금까지 작업한 df라는 변수를 받고, 0번부터 599번까지 resultidx를 바꿔가면서 GetCommData 함수로 컬럼별 데이터를 호출한다. 특이한 점은 아래 그림 2와 같이 resultidx가 0일 때는 "종목코드"컬럼에 값이 있지만 resultidx 가 0보다 크면 "종목코드" 컬럼 값은 아래 그림 2처럼 값은 빈칸으로 주기 때문에 최초의 "종목코드"를 stockcode라는 변수에 넣어서 resultidx 가 0 보다 클 때는 "종목코드" 컬럼에 stockcode 값을 넣어주는 것이다. 

    그림2: resultidx 별 데이터 구조 예시

    전체종목 기간별 종가 조회

    전체 장내 종목에 대한 기간별 종가를 조회하는 전체 코드의 흐름을 그림으로 나타내면 아래 그림 3과 같다. GetCodeListByMarket 함수로 장내 종목코드를 전부 받아온 뒤에 OnReceiveTrData로 데이터 입수상태를 활성화 시킨다. rqstockpriceinfo에서 장내 종목코드(stockcode)를 SetInputValue, CommRqData로 API 요청을 한다. 이때 CommRqData( "RQName" ,  "opt10081" ,  "0" ,  "화면번호")에서 RQName을 종목코드로(stockcode) 부여해서 API요청명이 중복되지 않도록 하는 것이 중요하다. API 결과는 getstockpricedf 에서 처리하여 df라는 데이터프레임을 만든다. 미리 정의한 self.df 와 df를 합친 데이터를 다시 self.df라고 다시 재정의하는 과정을 반복하며 최종적으로 self.df라는 모든 데이터가 처리된 하나의 데이터프레임을 만들어 낸다.

    그림3: 전체종목 기간별 종가조회 코드 흐름

    이를 코드로 나타내면 아래와 같다.

    import sys
    import pandas as pd
    import time
    from PyQt5.QtWidgets import *
    from PyQt5.QAxContainer import *
    from PyQt5.QtCore import QDate
    from PyQt5 import uic
    
    #UI파일 연결
    form_class = uic.loadUiType("C:/Users/admin/Documents/kiwoom_test/mainform.ui")[0]
    
    class KiwoomAPIform(QMainWindow, form_class):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
            self.setWindowTitle("KiwoomTest")
            
            self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
            # 클래스 객체 자체에서 self.df 설정(처리된 데이터를 모으기 위함)
            self.df = pd.DataFrame()
            self.timer = time
            
    ...(중략)...
            
            self.btn_login.clicked.connect(self.btn_loginFunc)
            self.btn_dataget.clicked.connect(self.btn_datagetFunc)
        
    ...(로그인 부분 중략)...
    
        def btn_datagetFunc(self):
            data = self.GetCodeListByMarket("0")
            stockcodes = data.split(";")
            stockcodes = stockcodes[:-1] if stockcodes[-1] == "" else stockcodes
            # 기준일자는 일단 20231201로 설정(나중에 화면에서 변수로 받아서 처리)
            basedate = "20231201"
            callback = lambda: self.getstockpricedf("opt10081", "주식일봉차트", self.df)
            self.ocx.OnReceiveTrData.connect(callback)
            # 일단 0 ~ 10 까지 데이터만 처리하는 것으로 고정(나중에 화면에서 변수로 받아서 처리)
            for stockcode in stockcodes[0:11]:
                self.rqstockpriceinfo(stockcode, "opt10081", "주식일봉차트", stockcode, basedate)
                # 데이터 처리 시간 1초 정도 간격
                self.timer.sleep(1)
                print("{} processed!".format(stockcode))
            self.ocx.OnReceiveTrData.disconnect(callback)
            print(self.df)
       
    ...(로그인 부분 중략)...
        
        def rqstockpriceinfo(self, rqname: str, trcode: str, trname: str, stockcode: str, date: str):
            self.SetInputValue("종목코드", stockcode)
            self.SetInputValue("기준일자", date)
            self.SetInputValue("수정주가구분", "")
            self.CommRqData(rqname, trcode, trname)
        
        def getstockpriceinfo(self, trcode: str, trname: str, df: pd.DataFrame):
            data = []
            columns = ["종목코드", "일자", "현재가", "거래량", "시가", "고가", "저가"]
            for resultidx in range(600):
                items = []
                for column in columns:                    
                    item = self.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)
            df = pd.concat([df, data])
            return df
      
      # getstockpriceinfo에서 통합된 지금까지의 모든 데이터프레임을 다시 self.df로 정의하는 함수
        def getstockpricedf(self, trcode: str, trname: str, df: pd.DataFrame):
            self.df = self.getstockpriceinfo(trcode, trname, df)
        
        # API
    ...(로그인 부분 중략)...
            
        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)    
        
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = KiwoomAPIform()
        window.show()
        sys.exit(app.exec_())
    반응형
Designed by Tistory.