키움API에서 제공하는 개발 가이드의 내용 중 ‘조회와 실시간 데이터 처리’ OpenAPI 사용에 대하여 테스트한 내용을 정리합니다.
조회와 실시간 데이터 처리
OpenAPI가 제공하는 데이터를 서버에 요청해서 가져오기 위하여 TR을 요청/처리하는 OpenAPI 입니다.
기본 설명 (From. KOA StudioSA)
‘KOA StudioSA’ 에서는 아래와 같이 설명하고 있습니다.
[조회처리(조회요청)]
OpenAPI가 제공하는 데이터중에서 원하는 데이터를 서버에 요청해서 가져오는 것을 말하는데 TR(Transaction)단위로 처리됩니다.
TR이란 서버와 데이터를 주고받을때 정의한 약속된 규약이며 입력부분(Input)과 데이터를 수신하는 출력부분(Output)을 가지고 있습니다.
입력부분은 요청하는 데이터에 따라 입력갯수(입력항목)가 달라지며 출력부분은 보통 데이터갯수(출력항목)가 여러개로 구성됩니다.
출력부분은 출력항목이 한번씩만 전달되는 싱글데이터와 복수로 전달되는 멀티데이터가 있고 TR에 따라 싱글데이터(또는 멀티데이터)만
있거나 둘다 있는 경우도 있습니다.
OpenAPI가 제공하는 TR은 KOA Studio의 TR목록 탭에서 찾아볼 수 있고 각 TR별로 조회도 가능합니다.
OPT10070 : 당일주요거래원요청 - 싱글데이터
OPT10081 : 주식일봉차트조회요청 - 싱글 + 멀티데이터
[계좌비밀번호 설정]
잔고나 주문가능금액,수량등 계좌관련 조회나 주문전에 미리 계좌비밀번호를 설정해야 오류 알림창과 -301 오류코드없이 사용하실수 있습니다.
계좌비밀번호 설정은 계좌비밀번호 입력창에서만 가능하며 이 입력창은 메뉴나 함수로 출력하실수 있습니다.
메뉴이용 - 로그인후 트레이 메뉴(모니터 오른쪽 하단)에서 "계좌비밀번호 저장"선택
함수이용 - 로그인후 OpenAPI.KOA_Functions(_T("ShowAccountWindow"), _T(""))호출
[조회제한]
OpenAPI 조회는 1초당 5회로 제한되며 복수종목 조회와 조건검색 조회 횟수가 합산됩니다.
가령 1초 동안 시세조회2회 관심종목 1회 조건검색 2회 순서로 조회를 했다면 모두 합쳐서 5회이므로 모두 조회성공하겠지만
조건검색을 3회 조회하면 맨 마지막 조건검색 조회는 실패하게 됩니다.
[조회제한 강화]
기존 1초당 5회 조회제한외에 분당, 시간당 유동적 조회제한이 2017년 4월 6일 17:00이후 반영되었습니다.
(OpenAPI 게시판 조회제한 관련 공지내용을 참고하세요.)
조회제한 강화 발생시 에러코드(-200)리턴과 알림 메시지가 표시되며 조회제한 강화가 발생한 프로그램은 다시 로그인해야 조회기능을 다시 이용할 수 있으며
구체적인 조회제한 기준은 비공개로 운영하고 있습니다.
이 조회제한 강화는 거래시간, 거래일에 관계없이 OpenAPI프로그램 실행시 항상 점검합니다.
점검방법은 CommRqData()함수와 CommKwRqData()함수를 이용한 조회횟수를 합산하는 것으로 연속조회 역시 CommRqData()를 이용므로 합산됩니다.
이외 SetRealReg()함수호출이나 나머지 OpenAPI함수호출은 조회제한 강화와 관련없으며 실시간 시세데이터 사용도 관련없습니다.
조회횟수 합산은 프로그램별로 합산하며 로그인ID 별로는 합산하지 않습니다.
예를들어 같은ID로 로그인한 2개 프로그램에서 각각 10회씩 조회했다면 프로그램별 10회 조회이며 20조회로 합산하지 않습니다.
[연속조회]
TR별로 한번에 전달할 수 있는 데이터 갯수가 정해져 있습니다. 그런데 이 갯수보다 조회데이터가 많을때 연속조회로 모든 데이터를 조회하게 됩니다.
연속조회는 모든 조회처리에 적용되는 공통사항이며 CommRqData()에서 인자값만 바꿔서 처리합니다.
아래처럼 설정해서 맨 처음 조회했을때 추가로 조회할 데이터가 있다면
OpenAPI.CommRqData("일별데이터조회", "OPT10086" , 0, "0001");
OnReceiveTRData()이벤트에서 5번째 인자값(sPrevNext)에 "2"가 전달됩니다.
그렇다면 연속조회하실때는 CommRqData("일별데이터조회", "OPT10086" , 2, "0001"); 3번째 인자값을 2로 설정해서 조회하시면 됩니다.
정리하면 다음과 같습니다.
OpenAPI.CommRqData("일별데이터조회", "OPT10086" , 0, "0001"); // 처음조회시 혹은 연속데이터가 없을때
OpenAPI.CommRqData("일별데이터조회", "OPT10086" , 2, "0001"); // 연속조회시
[실시간 데이터]
시세조회요청이 성공하면 관련 실시간 시세데이터가 발생했을때 서버에서 자동으로 OnReceiveRealData()이벤트로 실시간 타입단위로 전달해줍니다.
KOA Studio의 실시간 탭을 여시면 Real Type과 "주식시세"에서 "종목프로그램매매"까지 나열된 이름을 확인할 수 있습니다.
이들 하나하나를 실시간 타입이라고 하며 관련있는 FID(숫자)와 이름(실시간 항목)를 임의로 모아놓은 것입니다.
예를들어 실시간 타입 "주식시세"는 FID 10 현재가 ~ FID 568 하한가발생시간까지 19개 FID로 구성되며 한꺼번에 전달되는것입니다.
또 실시간 타입 "주식체결"는 FID 20 체결시간 ~ FID 1313 Extra Item까지 35개 FID가 한번에 전달됩니다.
[실시간 데이터 - 주의사항]
실시간 타입 "주문체결", "잔고", "파생잔고"는 주문관련 실시간 데이터를 전달하기 때문에 시세조회한 뒤나 SetRealReg()함수로 등록해서 사용할 수 없습니다.
이 실시간 타입은 주문을 해야 발생하며 주문전용 OnReceiveChejanData()이벤트로 전달됩니다.
아래 실시간 타입은 시스템 내부용으로 사용할수없는 실시간 타입입니다.
1. 임의연장정보
2. 시간외종목정보
3. 주식거래원
4. 순간체결량
5. 선물옵션합계
6. 투자자별매매
[참고 SetRealReg() 함수]
SetRealReg()함수로도 실시간 시세데이터 수신이 가능하며 시세조회요청과 방법만 다를뿐 수신하는 실시간 시세데이터 그리고 데이터 처리 방법은 동일합니다
이 함수는 조건검색 항목에서 사용법을 설명하고 있습니다.
관련 함수 (from. KOA StudioSA)
‘KOA StudioSA’ 에서는 아래와 같이 설명하고 있습니다.
CommRqData() 함수
[CommRqData() 함수]
CommRqData(
BSTR sRQName, // 사용자 구분명
BSTR sTrCode, // 조회하려는 TR이름
long nPrevNext, // 연속조회여부
BSTR sScreenNo // 화면번호
)
조회요청함수이며 빈번하게 조회요청하면 시세과부하 에러값으로 -200이 전달됩니다.
리턴값
0이면 조회요청 정상 나머지는 에러
-200 시세과부하
-201 조회전문작성 에러
SetInputValue() 함수
[SetInputValue() 함수]
SetInputValue(
BSTR sID, // TR에 명시된 Input이름
BSTR sValue // Input이름으로 지정한 값
)
조회요청시 TR의 Input값을 지정하는 함수이며 조회 TR 입력값이 많은 경우 이 함수를 반복적으로 호출합니다.
---------
[OPT10081 : 주식일봉차트조회요청예시]
SetInputValue("종목코드" , "039490"); // 첫번째 입력값 설정
SetInputValue("기준일자" , "20160101");// 두번째 입력값 설정
SetInputValue("수정주가구분" , "1"); // 세번째 입력값 설정
LONG lRet = CommRqData( "RQName","OPT10081", "0","0600");// 조회요청
---------
DisconnectRealData() 함수
[DisconnectRealData() 함수]
DisconnectRealData(
BSTR sScnNo // 화면번호
)
화면번호 설정한 실시간 데이터를 해지합니다.
CommKwRqData() 함수
[CommKwRqData() 함수]
CommKwRqData(
BSTR sArrCode, // 조회하려는 종목코드 리스트
BOOL bNext, // 연속조회 여부 0:기본값, 1:연속조회(지원안함)
int nCodeCount, // 종목코드 갯수
int nTypeFlag, // 0:주식 관심종목, 3:선물옵션 관심종목
BSTR sRQName, // 사용자 구분명
BSTR sScreenNo // 화면번호
)
한번에 100종목을 조회할 수 있는 관심종목 조회함수인데 영웅문HTS [0130] 관심종목 화면과는 이름만 같은뿐 전혀관련이 없습니다.
함수인자로 사용하는 종목코드 리스트는 조회하려는 종목코드 사이에 구분자';'를 추가해서 만들면 됩니다.
조회데이터는 관심종목정보요청(OPTKWFID) Output을 참고하시면 됩니다.
이 TR은 CommKwRqData()함수 전용으로 임의로 사용하시면 에러가 발생합니다.
GetRepeatCnt() 함수
[CommKwRqData() 함수]
CommKwRqData(
BSTR sArrCode, // 조회하려는 종목코드 리스트
BOOL bNext, // 연속조회 여부 0:기본값, 1:연속조회(지원안함)
int nCodeCount, // 종목코드 갯수
int nTypeFlag, // 0:주식 관심종목, 3:선물옵션 관심종목
BSTR sRQName, // 사용자 구분명
BSTR sScreenNo // 화면번호
)
한번에 100종목을 조회할 수 있는 관심종목 조회함수인데 영웅문HTS [0130] 관심종목 화면과는 이름만 같은뿐 전혀관련이 없습니다.
함수인자로 사용하는 종목코드 리스트는 조회하려는 종목코드 사이에 구분자';'를 추가해서 만들면 됩니다.
조회데이터는 관심종목정보요청(OPTKWFID) Output을 참고하시면 됩니다.
이 TR은 CommKwRqData()함수 전용으로 임의로 사용하시면 에러가 발생합니
[GetRepeatCnt() 함수]
GetRepeatCnt(
BSTR sTrCode, // TR 이름
BSTR sRecordName // 레코드 이름
)
조회수신한 멀티데이터의 갯수(반복)수를 얻을수 있습니다. 예를들어 차트조회는 한번에 최대 900개 데이터를 수신할 수 있는데
이렇게 수신한 데이터갯수를 얻을때 사용합니다.
이 함수는 반드시 OnReceiveTRData()이벤트가 호출될때 그 안에서 사용해야 합니다.
---------
[OPT10081 : 주식일봉차트조회요청예시]
OnReceiveTrDataKhopenapictrl(...)
{
if(strRQName == _T("주식일봉차트"))
{
int nCnt = OpenAPI.GetRepeatCnt(sTrcode, strRQName);
for (int nIdx = 0; nIdx < nCnt; nIdx++)
{
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("종목코드")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("거래량")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("시가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("고가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("저가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("현재가")); strData.Trim();
}
}
}
----------------------------------------------------------------------------
GetCommData() 함수
[GetCommData() 함수]
GetCommData(
BSTR strTrCode, // TR 이름
BSTR strRecordName, // 레코드이름
long nIndex, // TR반복부
BSTR strItemName) // TR에서 얻어오려는 출력항목이름
OnReceiveTRData()이벤트가 호출될때 조회데이터를 얻어오는 함수입니다.
이 함수는 반드시 OnReceiveTRData()이벤트가 호출될때 그 안에서 사용해야 합니다.
---------
[OPT10081 : 주식일봉차트조회요청예시]
OnReceiveTrDataKhopenapictrl(...)
{
if(strRQName == _T("주식일봉차트"))
{
int nCnt = OpenAPI.GetRepeatCnt(sTrcode, strRQName);
for (int nIdx = 0; nIdx < nCnt; nIdx++)
{
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("종목코드")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("거래량")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("시가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("고가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("저가")); strData.Trim();
strData = OpenAPI.GetCommData(sTrcode, strRQName, nIdx, _T("현재가")); strData.Trim();
}
}
}
----------------------------------------------------------------------------
GetCommRealData() 함수
[GetCommRealData() 함수]
GetCommRealData(
BSTR strCode, // 종목코드
long nFid // 실시간 타입에 포함된FID
)
OnReceiveRealData()이벤트가 호출될때 실시간데이터를 얻어오는 함수입니다.
이 함수는 반드시 OnReceiveRealData()이벤트가 호출될때 그 안에서 사용해야 합니다.
---------
[주식체결 실시간 데이터 예시]
if(strRealType == _T("주식체결"))
{
strRealData = m_KOA.GetCommRealData(strCode, 10); // 현재가
strRealData = m_KOA.GetCommRealData(strCode, 13); // 누적거래량
strRealData = m_KOA.GetCommRealData(strCode, 228); // 체결강도
strRealData = m_KOA.GetCommRealData(strCode, 20); // 체결시간
}
---------
OnReceiveTrData() 이벤트
[OnReceiveTrData() 이벤트]
void OnReceiveTrData(
BSTR sScrNo, // 화면번호
BSTR sRQName, // 사용자 구분명
BSTR sTrCode, // TR이름
BSTR sRecordName, // 레코드 이름
BSTR sPrevNext, // 연속조회 유무를 판단하는 값 0: 연속(추가조회)데이터 없음, 2:연속(추가조회) 데이터 있음
LONG nDataLength, // 사용안함.
BSTR sErrorCode, // 사용안함.
BSTR sMessage, // 사용안함.
BSTR sSplmMsg // 사용안함.
)
조회요청 응답을 받거나 조회데이터를 수신했을때 호출됩니다.
조회데이터는 이 이벤트내부에서 GetCommData()함수를 이용해서 얻어올 수 있습니다.
OnReceiveRealData() 이벤트
[OnReceiveRealData()이벤트]
OnReceiveRealData(
BSTR sCode, // 종목코드
BSTR sRealType, // 리얼타입
BSTR sRealData // 실시간 데이터 전문
)
실시간 데이터 수신할때마다 호출되며 SetRealReg()함수로 등록한 실시간 데이터도 이 이벤트로 전달됩니다.
GetCommRealData()함수를 이용해서 실시간 데이터를 얻을수 있습니다.
OnReceiveMsg() 이벤트
[OnReceiveMsg()이벤트]
OnReceiveMsg(
BSTR sScrNo, // 화면번호
BSTR sRQName, // 사용자 구분명
BSTR sTrCode, // TR이름
BSTR sMsg // 서버에서 전달하는 메시지
)
서버통신 후 수신한 메시지를 알려줍니다.
메시지에는 6자리 코드번호가 포함되는데 이 코드번호는 통보없이 수시로 변경될 수 있습니다. 따라서 주문이나 오류관련처리를
이 코드번호로 분류하시면 안됩니다.
파일 구조
ㄴ KiwoomAPI.py
ㄴ KiwoomMain.py
KiwoomAPI.py
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
class KiwoomAPI(QAxWidget):
def __init__ (self):
super().__init__()
self.set_kiwoom_api()
self.set_event_slot()
self.ret_data = {}
self.output_list = []
# ========== #
def set_kiwoom_api(self):
self.setControl("KHOPENAPI.KHOpenAPICtrl.1")
def set_event_slot(self):
# 공통
... (생략)
# 로그인 버전처리
... (생략)
# 조회와 실시간 데이터 처리
self.OnReceiveTrData.connect(self.E_OnReceiveTrData)
self.OnReceiveRealData.connect(self.E_OnReceiveRealData)
# ========== #
### Event 함수 ###
## 공통 ##
... (생략)
## 로그인 버전처리 ##
... (생략)
## 조회와 실시간 데이터 처리 ##
def E_OnReceiveTrData(self, sScrNo, sRQName, sTrCode, sRecordName, sPrevNext, nDataLength, sErrorCode, sMessage, sSplmMsg):
print(sScrNo, sRQName, sTrCode, sRecordName, sPrevNext, nDataLength, sErrorCode, sMessage, sSplmMsg)
self.Call_TR(sTrCode, sRQName)
self.event_loop_CommRqData.exit()
def E_OnReceiveRealData(self, sCode, sRealType, sRealData):
print(sCode, sRealType, sRealData)
# ========== #
### OpenAPI 함수 ###
## 로그인 버전처리 ##
... (생략)
## 조회와 실시간 데이터 처리 ##
# 조회 요청
def CommRqData(self, sRQName, sTrCode, nPrevNext, sScreenNo):
ret = self.dynamicCall('CommRqData(String, String, int, String)', sRQName, sTrCode, nPrevNext, sScreenNo)
self.event_loop_CommRqData = QEventLoop()
self.event_loop_CommRqData.exec_()
# print(ret)
# 조회 요청 시 TR의 Input 값을 지정
def SetInputValue(self, sID, sValue):
ret = self.dynamicCall('SetInputValue(String, String)', sID, sValue)
# 조회 수신한 멀티 데이터의 개수(Max : 900개)
def GetRepeatCnt(self, sTrCode, sRecordName):
ret = self.dynamicCall('GetRepeatCnt(String, String)', sTrCode, sRecordName)
# print(ret)
return ret
# 조회 데이터 요청
def GetCommData(self, strTrCode, strRecordName, nIndex, strItemName):
ret = self.dynamicCall('GetCommData(String, String, int, String)', strTrCode, strRecordName, nIndex, strItemName)
# print(ret)
return ret.strip()
# ========== #
# TR 요청
def Call_TR(self, strTrCode, sRQName):
self.ret_data[strTrCode] = {}
self.ret_data[strTrCode]['Data'] = {}
self.ret_data[strTrCode]['TrCode'] = strTrCode
count = self.GetRepeatCnt(strTrCode, sRQName)
self.ret_data[strTrCode]['Count'] = count
if count == 0:
temp_list = []
temp_dict = {}
for output in self.output_list:
data = self.GetCommData(strTrCode, sRQName, 0, output)
temp_dict[output] = data
temp_list.append(temp_dict)
self.ret_data[strTrCode]['Data'] = temp_list
if count >= 1:
temp_list = []
for i in range(count):
temp_dict = {}
for output in self.output_list:
data = self.GetCommData(strTrCode, sRQName, i, output)
temp_dict[output] = data
temp_list.append(temp_dict)
self.ret_data[strTrCode]['Data'] = temp_list
KiwoomMain.py
import sys
from PyQt5.QtWidgets import *
import KiwoomAPI
class KiwoonMain:
def __init__(self):
self.kiwoom = KiwoomAPI.KiwoomAPI()
self.kiwoom.CommConnect()
# ========== #
def GetLoginInfo(self):
# 로그인 상태
... (생략)
# 로그인 정보
... (생략)
# TR 목록
def OPT10001(self):
self.kiwoom.output_list = ['종목명']
self.kiwoom.SetInputValue("종목코드", "005930")
self.kiwoom.CommRqData("OPT10001", "OPT10001", 0, "0101")
return self.kiwoom.ret_data
def OPT10001(self) 함수
self.kiwoom.output_list = [‘종목명’]
- Config.py에 정의된 output 정보(키움 OpenAPI로 요청할 데이터 필드)를 가져옵니다.
self.kiwoom.CommRqData(“OPT10001”, “OPT10001”, 0, “0101”)
- 키움 OpenAPI에 데이터를 요청할 때 설정하는 Input Value 입니다.
self.kiwoom.CommRqData(“OPT10001”, “OPT10001”, 0, “0101”)
- 키움 OpenAPI에 데이터를 요청하는 함수 입니다.
- 데이터를 요청하면 이벤트가 발생하고 KiwoomAPI.py에서 이벤트가 발생하면 데이터를 조회(Get) 합니다.
return self.kiwoom.ret_data
- 조회한 데이터를 Return 합니다.
- Return 되는 포멧은 Json 형식으로 아래와 같습니다.
{
'OPT10001': {
'Count': 0,
'Data': [ # 조회된 데이터 결과 (List)
{
'종목명': '삼성전자',
'종목코드': '005930',
...
}
],
'TrCode': 'OPT10001'
}
}
실행
app = QApplication(sys.argv)
api_con = KiwoonMain()
result = api_con.OPT10001()
print("result['OPT10001'] : ", result['OPT10001'])
print("result['OPT10001']['Count'] : ", result['OPT10001']['Count'])
print("result['OPT10001']['TrCode'] : ", result['OPT10001']['TrCode'])
print("result['OPT10001']['Data'] : ", result['OPT10001']['Data'])
print("result['OPT10001']['Data'][0] : ", result['OPT10001']['Data'][0])
결과
0 # nErrCode
0101 OPT10001 OPT10001 0 0 # E_OnReceiveTrData()
result['OPT10001'] : {'Data': [{'종목명': '삼성전자'}], 'TrCode': 'OPT10001', 'Count': 0}
result['OPT10001']['Count'] : 0
result['OPT10001']['TrCode'] : OPT10001
result['OPT10001']['Data'] : [{'종목명': '삼성전자'}]
result['OPT10001']['Data'][0] : {'종목명': '삼성전자'}
향후 TR별 테스트 코드에서는 result[‘OPT10001’][‘Data’][0] 값만 리턴하고 1개의 데이터를 샘플로 작성할 예정입니다.
연관 포스트
[키움API] 기본 개발 가이드 내용 및 샘플 코드 (in Python) / OpenAPI 오류코드
[키움API] TR 목록 및 샘플 코드 (in Python)
실시간 시세 데이터 요청하였는데 def SetRealReg(self, sScreenNo, strTrCode, sFidList, sRealType) 결과 -150이 나왔을 경우 어떻게 해결할 수 있을까요???
코드를 실행하고 나면 연결이 끊겨서 한던더 실행하면 또 업데이트 과정을 거치는데 계속 연결시켜 두려면 어떻게 해야하나요?