1. 국내주식/1-2. 키움 OpenAPI (사용)

(주식 자동 매매) 키움증권 OpenAPI - 파이썬을 통해 1분봉 받기 (opt10080)

봄이오네 2022. 10. 11. 08:24
반응형

1. 들어가며

지난 글에서는 키움증권 OpenAPI를 이용하여,

4종목의 1분봉 데이터를 엑셀에 저장해 보았다.

 

참고 : https://springcoming.tistory.com/27?category=1048804 

 

(주식 자동 매매) 키움증권 OpenAPI - 1분봉 데이터 실시간 받기(opt10080)

1. 들어가며 지난 시간에는 키움증권 OpenAPI를 통해 이미 매수하여 계좌에 보유중인 4종목의 매수가격과 매수량을 출력해 보았다. 이번에는 키움증권 OpenAPI를 활용하여 4종목의 1분봉 데이터를 받

springcoming.tistory.com

 

이번 글에서는 파이썬을 이용하여 삼성전자(005930)의

1분봉 데이터 900개를 다운받아 엑셀에 저장해 보자.

 

※ 아래에서는 영웅문에 직접 접속하여 다운로드 받았다면,

    여기서는 파이썬을 통해 1분봉 데이터를 받을 것이다.

 

참고 : https://springcoming.tistory.com/28?category=1025768 

 

(주식 자동매매) 키움증권 영웅문4에서 1분봉 엑셀로 받는 방법

1. 들어가며 키움증권의 영웅문4은 사용자에게 각종 데이터를 제공한다. 사용자는 종목차트, 보조지표, 주식 현재가 등 주식 매매를 위해 필요한 정보를 얻을 수 있다. 이번 시간에는 영웅문4에

springcoming.tistory.com

 

글에 들어가기에 앞서, 미리 설명해야 될 부분이 있다.

 

지난 글(실시간 1분봉 받기)가 중요한 이유는 패턴에 따라 매매를 할 때,

수익이 많이 날 수 있는 진입점을 알려준다는 것이다. → 패턴 파악에 따른 수익극대화를 위한 진입점 찾기

 

이번 글(1분봉 데이터 한방에 받기)에서는 1분봉 모음을 만들어서,

패턴을 파악하고자 한다. → 패턴 파악을 위한 로데이터 확보

 

※ 약간 특이한 건, 키움은 1초당 5회 이상 데이터를 요청하면 조회 제한이 걸리는데, 

    밑에서 설명할 코드는 조회 제한이 걸리지 않는다.

     (for문으로 빠르게 요청했는데도, 초당 5회 요청에 해당되지 않는가 보다)

 


  2. KOA Studio 설명

opt10080 관려하여 데이터를 요청하는 KOA Studio는

앞의 글에서 설명하였으므로 생략한다.


3. 코드 설명

  • 먼저는 키움 OpenAPI에 접속/로그인을 하고
  • 삼성전자(005930)의 1분봉 데이터를 요청
  • 1분봉 데이터(OHLCV)를 1초 마다 사용자에게 전달/수신
  • 엑셀에 저장하여 작업 완료

        ※ 주의할 점은, for문이 계속 돌고 있다. 1개 받는데 1초 정도 걸린다.

            900개면 900초 = 15분 후에 파이참 실행을 정지시키면 된다.

 

< 그림1. 1분봉 받기 위한 기본 로그인 및 이벤트 연결 >

 

1줄~9줄 : 필요한 모듈를 임포트한다. os 모듈은 파일의 경로를 확인할 때 사용한다.

    * 조회 제한에 걸리지 않으므로, 대기시간을 주기 위해 QTest은 활용하지 아니함

    * 반복작업은 for문으로 돌려서 자료를 받을 것이므로, threading.Timer 미활용

    * 시간과 관련된 함수 내용은 없으므로, time 모듈 미활용

         → 프로그램을 짤때는 미활용하는 7줄~9줄은 삭제해도 된다.

              (사용하지 않음으로 보여주기 위해, 필자는 삭제하지 않음)

 

11줄~29줄 : 로그인 관련 함수는 생략

17줄 : 키움서버에 입력/요청한 내용을 받기 위해 OnReceiveTrData와 

           데이터를 수신받는 trdata_get 함수를 연결(connect)

22줄 : 1분봉을 담기위해 딕셔너리 형태로 날짜, 시간, OHLCV 등을 초기화

 

31줄~38줄 : 종목코드, 틱단위, 수정주가구분을 입력해주면,

                     SetInputValue와 CommRqData 함수로 키움서버에 데이터 입력/요청

 

< 그림2. 2페이지 존재 여부에 따라 대기시간을 주는 경우도 있다 >

 

40줄~47줄 : 51줄에서 2페이지가 존재하면, 40줄~47줄 실행

                    키움서버는 600개의 데이터를 사용자에게 전달하는데,

                   2페이지가 존재한다는 것은, 제공하는 데이터가 600개 이상이라는 뜻이다.

 

49줄 : trdata_get 함수를 임의로 만들어, 31줄~47줄에서 입력/요청한 데이터를 받아온다.

50줄~51줄 : 2페이지가 있으면, 41줄~47줄을 실행하라.

53줄~54줄 : 2페이지가 없으면, 33줄~38줄을 실행하라.

 

56즐 : 45줄에서 요청받은 데이터가 "opt_10080"이면, 아래 실행

58줄 : 키움서버에서 제공하는 GetRepeatCnt 함수는 조회한 데이터 갯수를 센다. (int형이다)

61줄 : for 문이 돌아가는 것을 보여주기 위해 print("4444444444444444")를 넣었다.

 

62줄~69줄 : 61줄의 for 문에 따라 1분봉을 얻어온다.

72줄~79줄 : 22줄의 self. minute_data의 OHLCV 등을 업데이터(추가)한다.

 

< 그림3. 키움서버에서 받은 데이터를 엑셀에 보낸다 >

 

83줄 : 72줄~79줄에서 업데이트한 22줄의 self.minute_data를 pandas의 DataFrame에 넣는다.

84줄 : 83줄의 데이터 프레임 중 마지막 1개(tail(n=1))만 받는다.

              * tail(n=1)을 넣지 않으면, 자료가 누적되어 출력된다.

 

87줄 : 데이터프레임한 내용을 df1 변수에 담는다.

89줄 : 엑셀이 있는 경로를 dir 변수에 담는다.

 

92줄 : 비록 114줄에서 삼성전자를 선언해 주었으나,.

            for문을 돌고 있는 변수명(종목코드 이름)을 알 수 없으므로,

           임의로 stock_code에 삼성전자를 담는다. (선언)

 

94줄 ~102줄 : pandas의 ExcelWriter를 활용하여, 데이터프레임 데이터를 엑셀에 넣는다.

97줄~98줄 : 89줄의 dir 경로에 엑셀이 있으면, 97줄~98줄에 따라, 데이터를 엑셀어 적어라

102줄         : 89줄의 dir 경로에 엑셀이 없으면, 102줄에 따라, 데이터를 엑셀어 적어라

 

104줄~108줄 : for문으로 데이터를 받았으면, 이벤트 루프를 일단 빠져 나가라.

114줄 : 삼성전자를 31즐의 rq_data_opt100080함수에 넣어서, 프로그램을 돌려라

 

< 그림4. 키움서버에서 받아온 OHLCV 등의 형태 >
< 그림5. 위 코드가 실행되면, 파이참의 실행창(run)에 뜨는 print문 내용이다 >

 

4. 전체 코드

import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd
import os
from PyQt5.QtTest import *  # QtTest(ms) 사용 안함
import threading
import time

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        print("로그인 시작!")

        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)
        self.kiwoom.dynamicCall("CommConnect()")
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.minute_data = {'date': [],'time': [], 'open': [], 'high': [], 'low': [], 'close': [], 'volume': [], 'pattern': []}

    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
        else:
            print('로그인 실패했습니다!')
        self.login_event_loop.exit()

    def rq_data_opt10080(self, stock_code, tik, jugagubun):
        print("1")
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code)
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "틱범위", tik)
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "수정주가구분", jugagubun)
        self.kiwoom.dynamicCall("CommRqData(QString,QString,int,QString)", "opt_10080", "opt10080", 0, "0102")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

        while self.remained_data == True:
            print("2")
            self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code)
            self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "틱범위", tik)
            self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "수정주가구분", jugagubun)
            self.kiwoom.dynamicCall("CommRqData(QString,QString,int,QString)", "opt_10080", "opt10080", 0, "0102")
            self.tr_event_loop = QEventLoop()
            self.tr_event_loop.exec_()

    def trdata_get(self, sScrNo, rqname, trcode, sRecordName, prenext, nDataLength, sErrorCode, sMessage, sSplmMsg):
        print("3")
        if prenext == "2":
            self.remained_data = True
        elif prenext != "2":
            self.remained_data = False

        if rqname == 'opt_10080':
            print("4")
            rows = self.kiwoom.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
            for i in range(rows):
            # for i in range(1,3):
                print("444444444444444444")
                date = int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "체결시간").strip()[0:8])
                time = int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "체결시간").strip()[8:])
                open = abs(int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "시가").strip()))
                high = abs(int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "고가").strip()))
                low = abs(int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "저가").strip()))
                close = abs(int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "현재가").strip()))
                volume = abs(int(self.kiwoom.dynamicCall("GetCommData(QString, QString, int, QString)", "opt10080", "주식분봉차트조회요청", i, "거래량").strip()))
                pattern = str(high - open) + str(low - open) + str(close - open)

                print("5")
                self.minute_data['date'].append(date)
                self.minute_data['time'].append(time)
                self.minute_data['open'].append(open)
                self.minute_data['high'].append(high)
                self.minute_data['low'].append(low)
                self.minute_data['close'].append(close)
                self.minute_data['volume'].append(volume)
                self.minute_data['pattern'].append(pattern)

                # time.sleep(4)
                print("6")
                df_minute_data = pd.DataFrame(btl.minute_data, columns=['date', 'time', 'open', 'high', 'low', 'close', 'volume', 'pattern'])
                df_minute_data = df_minute_data.tail(n=1)

                print("7")
                df1 = df_minute_data

                dir = r'C:\Users\User\Desktop\minute_data_s.xlsx'  # 경로 설정

                print("8")
                stock_code = "005930"

                if os.path.exists(dir):
                    print("9")
                    with pd.ExcelWriter(dir, mode='a', engine='openpyxl', if_sheet_exists='overlay') as writer:
                        df1.to_excel(writer, header=False, index=False, sheet_name=stock_code,
                                     startrow=writer.sheets[stock_code].max_row)
                else:
                    with pd.ExcelWriter(dir, mode='w', engine='openpyxl') as writer:
                        print("10")
                        df1.to_excel(writer, index=False, sheet_name=stock_code)

                try:
                    print("11")
                    self.tr_event_loop.exit()
                except AttributeError:
                    pass
                # QTest.qWait(3000)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()
    btl.rq_data_opt10080("005930", "1", 0)
    app.exec_()

 

5. 마치며

좀 더 데이터를 받아올 수 있을 것이라 생각하고, 1분봉을 파이썬을 통해 받고자 하였다.

그런데, 키움에서 1분봉을 사용자에게 900개만 제공하니

1분봉을 더 받을 수 없음에 아쉬움이 있다.

 

일봉은 9,500개 정도 받을 수 있는데,

1분봉만 이렇게 적게 주는 이유는 무엇일까?

들인 시간에 비해, 받아온 데이터가 적어서 속상할 따름이다.

 

데이터를 빨리 받아오는게 편하긴 하다.

일일히 영웅문에 접속하여 데이터를 받아야 되는 번거러움은 줄었지만,

키움에서 너무 적게 데이터를 제공하니, 그 점이 조금은 답답하다.

 

반응형