PYTHON/Kiwoom Open API

키움증권 Open API 차트 데이터 불러오기 (1)

  • -

 

OnReceiveTrData 이벤트 처리하기

지난 포스팅에서 Open API를 열고 로그인을 하는 코드까지 모두 구축하고 넘어왔다. 이번 포스팅에서는 로그인 이후에 조회하고자 하는 종목의 차트 데이터를 불러오는 것에 대해 서술하고자 한다. 일단 기본적으로 로그인의 경우에는 OnEventConnect 이벤트를 발생시키는 것처럼 차트 데이터 조회 또는 종목 데이터에 대한 조회 등과 같은 정보 요청의 경우에는 OnReceiveTrData라는 이벤트를 발생시키게 된다. 이와 비슷한 이벤트로는 OnReceiveRealData라는 이벤트가 있는데, 이는 장중 데이터 조회 및 반환값 처리 시에 사용하는 것이므로 여기서는 일단 논외로 하겠다.  

그렇다면 로그인 처리 시에 했던 것처럼, 차트 조회 등과 같은 여러 가지 정보들을 조회했을 때 발생하는 이벤트인 OnReceiveTrData가 발생했을 경우 그와 연결하여 동작할 함수와 연결해주도록 하자.

self.kiwoom.OnReceiveTrData.connect(self.OnReceiveTrData)

그렇다면 이제 def OnReceiveTrData라는 내용을 통해 해당 이벤트 발생 시 동작할 활동 내역들을 해당 함수 아래에 제작해두면 OnReceiveTrData 이벤트가 발생했을 때의 내용들을 처리할 수 있다. 

 

OnReceiveTrData 함수 제작하기

이전 포스팅에서 OnEventConnect 이벤트를 OnEventConnect() 함수에 연결하는 과정에서 함수 내에 변수를 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의 약자로 데이터 요청을 명령하는 함수이다. 그렇다면 위 내용을 바탕으로 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")

 즉, 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 함수가 실행되지 않는다. 이와 관련하여 아래의 내용을 살펴보도록 하자.

 


728x90

 

 

이벤트 발생 이후의 처리, 이벤트 루프

앞서 로그인과 관련된 코드를 제작하는 과정에 있어서도 그러했듯이, 정보를 요청한 후에는 이벤트 루프를 통해 해당 이벤트가 종료될 때까지는 해당 코드를 지속적으로 사용하도록 해두었고 정보를 받아온 후에는 해당 이벤트 루프를 종료하도록 함으로써 다음 코드를 실행하도록 했었다.

이러한 구조는 차트 정보 조회에 있어서도 마찬가지이다. 즉, 정보를 요청하는 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개 이상의 데이터를 어떻게 가져올 수 있는지 등에 대해 작성할 예정이다. 아마도 다음 포스팅에서 차트 조회와 관련된 내용을 마칠 수 있을 것 같다.

 

 


728x90
반응형
Contents

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

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