키움증권 Open API 차트 데이터 불러오기 (1)
OnReceiveTrData 이벤트 처리하기
지난 포스팅에서 Open API를 열고 로그인을 하는 코드까지 모두 구축하고 넘어왔다. 이번 포스팅에서는 로그인 이후에 조회하고자 하는 종목의 차트 데이터를 불러오는 것에 대해 서술하고자 한다. 일단 기본적으로 로그인의 경우에는 `OnEventConnect()` 이벤트를 발생시키는 것처럼 차트 데이터 조회 또는 종목 데이터에 대한 조회 등과 같은 정보 요청의 경우에는 `OnReceiveTrData()`라는 이벤트를 발생시키게 된다. 이와 비슷한 이벤트로는 `OnReceiveRealData()`라는 이벤트가 있는데, 이는 장중 데이터 조회 및 반환값 처리 시에 사용하는 것이므로 여기서는 일단 논외로 하겠다.
그렇다면 로그인 처리 시에 했던 것처럼, 차트 조회 등과 같은 여러 가지 정보들을 조회했을 때 발생하는 이벤트인 `OnReceiveTrData()`가 발생했을 경우 그와 연결하여 동작할 함수와 연결해주도록 하자.
self.kiwoom.OnReceiveTrData.connect(self.OnReceiveTrData)
그렇다면 이제 `def OnReceiveTrData(self):`라는 내용을 통해 해당 이벤트 발생 시 동작할 활동 내역들을 해당 함수 아래에 제작해두면 `OnReceiveTrData()` 이벤트가 발생했을 때의 내용들을 처리할 수 있다.
OnReceiveTrData 함수 제작하기
이전 포스팅에서 `OnEventConnect` 이벤트를 `def OnEventConnect(self)` 함수에 연결하는 과정에서 함수 내에 변수를 self와 `err_code` 두 가지를 설정했었다. 여기서 `err_code`라는 변수를 넣었던 이유는 개발 가이드 내에서 해당 이벤트가 발생하면 반환값이라는 게 있으며 그 반환값을 바탕으로 로그인의 성공과 실패 여부를 확인할 수 있다고 설명해두었기 때문이다.
그렇다면 이제 `OnReceiveTrData`라는 이벤트가 발생한 경우에 반환값은 무엇인지 개발 가이드를 통해 확인해보고 그 내용을 바탕으로 함수를 제작해보자.
원형 | void OnReceiveTrData(ScrNo, RQName, TrCode, RecordName, PreNext, DataLength, ErrorCode, Message, SplMsg) |
설명 | 서버 통신 후 데이터를 받은 시점을 알려준다. |
입력값 | ScrNo : 화면번호 RQName : 사용자구분 명 TrCode : Tran 명 RecordName : Record 명 PreNext : 연속조회 유무 DataLength, ErrorCode, Message, SplmMsg : 미사용 |
반환값 | 없음 |
비고 | RQName : CommRqData의 RQName과 매핑되는 이름이다. TrCode : CommRqData의 TrCode와 매핑되는 이름이다. |
원형을 살펴보면 정말 많은 변수들이 있는데, 그 아래의 입력값 부분을 보면 알 수 있듯이 사용할 변수는 총 5개 뿐이다. 그리고 비고 란에 보면 `CommRqData()` 함수에 대한 내용이 있는데, 앞서 살펴봤듯이 Command Request Data의 약자로 데이터 요청을 명령하는 함수이다. 그렇다면 위 내용을 바탕으로 `def OnReceiveTrData()` 함수에 적절한 변수를 사용해서 제작해주도록 하자.
def OnReceiveTrData(self, scrno, rqname, trcode, recordname, prenext):
print("scrno:", scrno)
print("rqname:", rqname)
print("trcordname:", trcode)
print("recordname:", recordname)
print("prenext:", prenext)
개발 가이드 내에서는 `ScrNo` 등 대문자가 들어가 있고 실제로 파이참(파이썬) 내에서도 대문자로 변수를 지정해줘도 되긴 하지만, 그 변수를 재사용할 때마다 또 대문자를 사용해야 변수를 사용할 수 있게 되는 번거로움이 있으니 그냥 소문자로 해서 작성해주었다. 그리고 해당 이벤트가 발생했을 때 나타나는 일들을 확인하고 동작 과정을 이해하기에는 위와 같이 제작해두는 편이 좋다. 즉, `OnReceiveTrData`가 어떻게 작동하는지 그리고 어디서 입력했던 정보가 어떻게 나타나게 되는지를 이해하기에는 아주 좋은 방법이다. 단순하게 pass를 입력해두었다면 무슨 일이 일어나는지 그리고 이 함수 내에서 어떤 코드를 구축해야하는지 모를 수 있기 때문이다.
SetInputValue와 CommRqData 작성하기
일단 예전에 KOA Studio라는 프로그램을 다운로드 했었을텐데, 잠시 그를 실행한 후에 화면의 가장 왼쪽 최하단에 자리해 있는 [TR 목록] 메뉴를 클릭한 후, 조금만 드래그해서 opt10081:주식일봉차트조회요청이라는 부분을 클릭해보자.
그렇다면 위와 같은 화면을 볼 수 있다. opt10081: 주식일봉차트조회요청라는 칸 아래에 종목코드, 기준일자, 수정주가구분이라는 세 가지 변수가 표시되며 그 위에는 조회라는 버튼이 있음을 알 수 있다. 여기서 이야기하고자 하는 바가 바로 이것이다. 언뜻 봐도 어렵지 않게 이해할 수 있는데, 종목코드, 기준일자, 수정주가구분을 입력하는 동작을 수행하는 함수가 바로 `SetInputValue()`이고 그 위에 위치해 있는 [조회] 버튼을 눌러주는 함수가 바로 `CommRqData()`이다. 그렇다면 이제 세 개의 변수에 대해 어떤 값을 입력해야 하는지 알아보도록 하자.
- 종목코드 : 말 그대로 종목의 코드를 입력하는 칸
- 기준일자 : YYYYMMDD 형식으로, 해당 일자를 기준으로 하여 이전의 데이터를 모두 불러옴
- 수정주가구분 : 1은 유상증자, 2는 무상증자, 4는 배당락, 8은 액면분할 등등... 0은 그냥 수신 데이터를 나타냄
다음으로 `CommRqData()`의 경우에는 RQName, TrCode, 연속조회 여부, 화면번호라는 네 개의 변수를 전송해야 하는 것으로 나온다. 여기서 `RQName`의 경우에는 본인이 확인할 수 있는 값이라면 아무런 값이나 입력해주어도 된다. 어차피 `RQName`은 `CommRqData()` 함수 동작 이후 실행될 `OnReceiveTrData()`라는 함수 내에서 확인할 수 있는 변수이기 때문이다. 다음으로 `TrCode`의 경우에는 KOA Studio에서 살펴봤듯이 opt10081이라는 값이 일봉차트조회이므로 그 값을 입력해주고, 연속조회 여부는 0회, 화면 번호는 차트를 조회할 수 있는 0600번으로 입력하여 정보를 요청할 수 있다.
그렇다면 이제 해당 정보를 입력하고 서버에 요청할 함수를 제작해보도록 하자.
def rq_chart_data(self, itemcode, date, justify):
self.kiwoom.dynamicCall("SetInputValue(QString, QString, int)", itemcode, date, justify)
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "hello", "opt10081", 0, "0600")
즉, `def rq_chart_data()`라는 함수는 종목코드(`itemcode`), 기준일자(`date`), 수정주가구분(`justify`)이라는 세 개의 변수를 받아오면 그를 바탕으로 정보를 입력하게 되고, 그 입력한 정보를 기반으로 해서 `CommRqData()`를 실행하게 된다.
그렇다면 이제 차트 데이터를 조회하는 함수가 제대로 동작하는지 확인해보도록 하자. 파이참에서 디버그를 실행하는 단축키는 Shift + F9이고 단순하게 실행하는 단축키는 Shift + F10이다. 본인은 웬만하면 디버그를 사용한다. 오류가 발생할 경우 오류가 발생한 지점의 직전 지점에서 멈추게 되고, 해당 지점까지 실행된 부분들을 거치면서 발생한 변수의 값들을 모두 확인할 수 있기 때문이다.
if __name__ == "__main__":
app = QApplication(sys.argv)
trade = system_trading()
trade.rq_chart_data("005930", "20210530", 0)
이제 코드의 최하단에 있는 부분을 위와 같이 수정해주도록 하자. `system_trading()`이라는 클래스를 바탕으로 trade라는 인스턴스를 생성한 후, 그 인스턴스 내에 있는 `rq_chart_data()`라는 함수를 실행하도록 하는 것이다. 위의 코드를 보면 세 가지 변수 자리에 모두 값을 입력했음을 확인할 수 있는데, 이를 말로 풀어보자면 005930이라는 종목코드에 대해 20210530이라는 날까지 발생한 모든 차트 데이터를 가지고 오라는 함수이다.
여기까지 구축된 코드
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
class system_trading():
def __init__(self):
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
print("연결되었습니다..")
self.kiwoom.OnEventConnect.connect(self.OnEventConnect)
self.kiwoom.OnReceiveTrData.connect(self.OnReceiveTrData)
self.kiwoom.dynamicCall("CommConnect()")
self.login_event_loop = QEventLoop()
self.login_event_loop.exec_()
def rq_chart_data(self, itemcode, date, justify):
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", itemcode)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "기준일자", date)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", justify)
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "hello", "opt10081", 0, "0600")
def OnEventConnect(self, err_code):
if err_code == 0:
print("로그인에 성공하였습니다.")
else:
print("로그인에 실패하였습니다.")
def OnReceiveTrData(self, scrno, rqname, trcode, recordname, prenext):
print("scrno:", scrno)
print("rqname:", rqname)
print("trcordname:", trcode)
print("recordname:", recordname)
print("prenext:", prenext)
if __name__ == "__main__":
app = QApplication(sys.argv)
trade = system_trading()
trade.rq_chart_data("005930", "20210530", 0)
이제 이를 실행하게 된다면 오류가 발생하지는 않지만 초기의 "연결되었습니다."라는 문구 이외에는 아무런 동작도 이루어지지 않는다는 것을 확인할 수 있다. 그 이유는 바로 `CommConnect()` 아래의 두 줄 때문이다. 즉, 해당 구간 아래에 잘 보면 self.login_event_loop.exec_()라는 코드를 두어 해당 코드가 완료된 후에 다음 코드를 실행하도록 하였는데, 문제는 `CommConnect()`가 실행되면서 `OnEventConnect` 이벤트가 발생하게 되고 그로부터 `OnEventConnect()`함수로 잘 연결되었지만 그 어디에도 "이러한 상황이라면 이벤트를 종료해라."라는 의미를 가진 self.login_event_loop.exit()가 없다. 따라서 `def OnEventConnect(self, err_code)`를 다음과 같이 수정해주도록 하자.
def OnEventConnect(self, err_code):
if err_code == 0:
print("로그인에 성공하였습니다.")
else:
print("로그인에 실패하였습니다.")
self.login_event_loop.exit()
여기까지 제작했다면 '로그인에 성공하였습니다.'라는 문구까지 확인이 가능하게 되지만 또 rq_chart_data 함수가 실행되지 않는다. 이와 관련하여 아래의 내용을 살펴보도록 하자.
이벤트 발생 이후의 처리, 이벤트 루프
앞서 로그인과 관련된 코드를 제작하는 과정에 있어서도 그러했듯이, 정보를 요청한 후에는 이벤트 루프를 통해 해당 이벤트가 종료될 때까지는 해당 코드를 지속적으로 사용하도록 해두었고 정보를 받아온 후에는 해당 이벤트 루프를 종료하도록 함으로써 다음 코드를 실행하도록 했었다.
이러한 구조는 차트 정보 조회에 있어서도 마찬가지이다. 즉, 정보를 요청하는 `CommRqData()`를 사용한 이후에 이벤트 루프를 걸고, 이벤트가 발생하게 되면 해당 함수 내에서 결과값을 처리해주어야 한다는 것이다. 다시 말해, 이벤트 루프를 사용하지 않으면 이벤트를 처리할 수 없는 것과 같다.
이에 따라, 차트 조회를 요청하는 함수인 `def rq_chart_data():` 역시 다음과 같이 수정해주도록 하자.
def rq_chart_data(self, itemcode, date, justify):
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", itemcode)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "기준일자", date)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", justify)
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "hello", "opt10081", 0, "0101")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
즉, 아래의 두 줄에서 이벤트 루프를 걸어주는 것이다.
지금까지의 최종 코드는 다음과 같다.
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
class system_trading():
def __init__(self):
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
print("연결되었습니다..")
self.kiwoom.OnEventConnect.connect(self.OnEventConnect)
self.kiwoom.OnReceiveTrData.connect(self.OnReceiveTrData)
self.kiwoom.dynamicCall("CommConnect()")
self.login_event_loop = QEventLoop()
self.login_event_loop.exec_()
def rq_chart_data(self, itemcode, date, justify):
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", itemcode)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "기준일자", date)
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", justify)
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "hello", "opt10081", 0, "0101")
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()
def OnEventConnect(self, err_code):
if err_code == 0:
print("로그인에 성공하였습니다.")
else:
print("로그인에 실패하였습니다.")
self.login_event_loop.exit()
def OnReceiveTrData(self, scrno, rqname, trcode, recordname, prenext, unused1, unused2, unused3, unused4):
print("scrno:", scrno)
print("rqname:", rqname)
print("trcordname:", trcode)
print("recordname:", recordname)
print("prenext:", prenext)
if __name__ == "__main__":
app = QApplication(sys.argv)
trade = system_trading()
trade.rq_chart_data("005930", "20210530", 1)
이제 위의 코드를 실행해보면 다음과 같은 결과물을 얻을 수 있다.
연결되었습니다..
로그인에 성공하였습니다.
scrno: 0101
rqname: hello
trcordname: opt10081
recordname:
prenext: 2
잠깐. 이 정보들은 모두 어떤 정보들인지 기억 나는가? 바로 앞에서 정보를 요청할 때 사용한 `CommRqData()`를 통해 입력했던 정보들이다. 함께 비교하여 보자. `prenext`와 `recordname` 두 개의 값을 제외하고 나머지 `scrno`, `rqname`, `trcord`는 우리가 입력한 정보이고, `prenext`는 추가적으로 조회할 정보가 있다는 것이다. 이는 다른 게시글에서도 작성했듯이, 한 번 조회할 때 최대로 가져올 수 있는 차트의 양은 600개이다. 다만 1년 치 일봉이 약 240개로 구성되어 있기 때문에 한 번 조회할 때 약 2년 반 정도의 일봉 데이터를 불러올 수 있다. 여기서 `prenext`가 2라는 것은 해당 종목의 일봉 데이터가 오늘을 기준으로 2년 6개월보다 더 된 기간의 데이터가 있다는 것이다. 즉, 추가적으로 조회할 데이터가 남아있다는 것이다.
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "hello", "opt10081", 0, "0101")
이번 포스팅에서는 데이터 조회를 요청하고 이벤트가 발생할 때까지 기다리도록 하는 이벤트 루프, 그리고 이벤트 발생 시 특정 함수로 연결하도록 함으로써 해당 결과값을 받아올 수 있도록 하는 코드를 구축하였다. 다음 포스팅에서는 받아온 결과값을 바탕으로 차트 정보를 어떻게 가져올 수 있는지 그리고 차트 조회 시 600개 이상의 데이터를 어떻게 가져올 수 있는지 등에 대해 작성할 예정이다. 아마도 다음 포스팅에서 차트 조회와 관련된 내용을 마칠 수 있을 것 같다.
'AUTO TRADE > [키움증권] Kiwoom Open API' 카테고리의 다른 글
키움증권 Open API 차트 데이터 불러오기 (3) (3) | 2021.05.31 |
---|---|
키움증권 Open API 차트 데이터 불러오기 (2) (2) | 2021.05.31 |
키움증권 OpenAPI 개발 가이드 사용 설명서 (2) (0) | 2021.05.22 |
키움증권 Open API 개발 가이드 사용 설명서 (1) (3) | 2021.05.22 |
키움증권 Open API 작동 과정 (2) | 2021.05.13 |
소중한 공감 감사합니다