키움증권 API서버에 전달하는 요청 단위를 TR이라고 한다.
1. 종목별 가격 정보 요청 함수
def get_price_data(self, code):
이 TR을 호출하려면 CommRqData 함수를 사용해야 한다.
- sTrCode에 조회하려는 TR 이름을 전달하면 API를 이용할 수 있다.
- 변수명을 꼭 sTrCode로 만들어야 하는 것이 아니라 전달되는 매개변수의 순서가 중요하다.
- TR을 호출하는 코드를 살펴보면
self.dynamicCall("CommRqData(QString, QString, int, QString", "opt10081_req", "opt10081", 0, "0001")
- 첫번째 매개변수인 "CommRqData(QString, QString, int, QString)에서 CommRqData는 API에서 제공하는 함수이며 (QString, QString, int, QString) 는 이 API 함수를 호출할 때 매개변수가 네개 필요함을 의미한다
- 두번째 매개변수("opt10081_req")는 사용자 구분명을 의미한다. 사용자 구분명이란 우리가 호출할 TR의 별명이다.
- 세번째 매개변수 "opt10081"는 우리가 호출할 TR이름이며 이 값에 따라 어느 TR이 호출될지 결정된다.
- 사용할 TR 이름은 다음과 같이 KOA의 TR목록에서 찾을 수 있다.
- 네번째 매개변수 0은 연속 조회 여부를 나타낸다. TR에 대한 응답이 너무 길어 한번에 전달하기 어려울때는 응답을 나누어서 받을지 여부를 선택할 수 있다.(응답의 최대 개수는 600개)
- 마지막 매개변수인 화면 번호는 요청한 TR들의 묶음을 지정한 네자리 숫자 형태의 문자이다.
(화번 번호는 서버의 결과를 수신할 때 어떤 요청에 의한 수신인지를 구별하기 위한 키 값의 개념)
- 위 설명을 보면 종목코드, 기준일자, 수정주가구분 세가지 입력값이 필요한 것을 알 수 있다.
- 따라서 아래와 같이 세팅해줘야된다. 물론 기준일자도 입력해야되지만 기준일자를 입력하지 않으면 최근일자까지 조회해 오기 때문에 사실상 종목코드만 있으면 가격 정보를 가져올 수 있다.
self.dynamicCall("SetInputValue(QString, QString", "종목코드", code)
self.dynamicCall("SetInputValue(QString, QString", "수정주가구분", 1)#1이면 수정주가 적용함
self.dynamicCall("CommRqData(QString, QString, int, QString", "opt10081_req", "opt10081", 0, "0001")
- 여기서 Kiwoom 클래스 생성자에서 만든 tr_event_loop 변수를 이용해서 exec_() 메소드를 실행시킨다.
- 이부분이 CommRqData를 사용하여 TR요청을 보낸 후 응답 대기 상태로 만드는 코드이다.
self.tr_event_loop.exec_()#TR요청 보낸후 응답 대기상태로 만드는 코드임 이후 코드는 TR에 대한 응답이 도착한 후 실행됨
ohlcv = self.tr_data#응답 slot함수 _on_receive_tr_data에서 수신한 일봉 데이터 저장되어있어서 사용 가능함
한번에 호출로 받아 올수있는 데이터가 최대 600개이며 600일치가 ohlcv에 저장된다.
get_price_data (tr_event_loop_exec_()함수까지) -> _on_receive_tr_data 함수를 전체 다 돌고 -> 다시 get_price_data의 while문부터 시작됨(ohlcv에 self.tr_data 데이터가 저장됨)
def get_price_data(self, code):
#종목의 상장일부터 가장 최근 일자까지 일봉정보를 가져오는 함수
self.dynamicCall("SetInputValue(QString, QString", "종목코드", code)
self.dynamicCall("SetInputValue(QString, QString", "수정주가구분", 1)#1이면 수정주가 적용함
self.dynamicCall("CommRqData(QString, QString, int, QString", "opt10081_req", "opt10081", 0, "0001")
self.tr_event_loop.exec_()#TR요청 보낸후 응답 대기상태로 만드는 코드임 이후 코드는 TR에 대한 응답이 도착한 후 실행됨
ohlcv = self.tr_data#응답 slot함수 _on_receive_tr_data에서 수신한 일봉 데이터 저장되어있어서 사용 가능함
while self.has_next_tr_data: #다음 데이터가 있다면 실행
self.dynamicCall("SetInputValue(QString, QString", "종목코드", code)
self.dynamicCall("SetInputValue(QString, QString", "수정주가구분", 1) # 1이면 수정주가 적용함
self.dynamicCall("CommRqData(QString, QString, int, QString", "opt10081_req", "opt10081", 2, "0001")#2인 이유는 연속조회이므로
self.tr_event_loop.exec_()#TR요청 보낸후 응답 대기상태로 만드는 코드임 이후 코드는 TR에 대한 응답이 도착한 후 실행됨
#최초로 얻어온 가격 데이터를 담은 ohlcv에 데이터를 이어 붙이는 작업을 함
for key, val in self.tr_data.items():
ohlcv[key] += val
df = pd.DataFrame(ohlcv, columns=['open', 'high', 'low', 'close', 'volume'], index=ohlcv['date'])
return df[::-1]
- 여기서 while문에서 tr_event_loop.exec_() 함수 부분은 600일치 이상을 불러와야할 때 한번 더 TR요청을 하고 다시 응답을 기다리는 상태로 진입하기 위해 호출된다.
- 이후 for문을 통해서 ohlcv에 데이터를 이어 붙이는 작업을 한다.
- 딕셔너리 for문을 이용해서 이어 붙이는 작업을 한다.
- 반복문(while, for문)이 끝나면 그동안 받아온 데이터를 pandas 패키지를 이용해서 DataFrame을 만든다.(행렬구조로)
- 여기서 컬럼은 date, open, high, low, close, volume이며 인덱스는 date로 한다는 의미이다.
df = pd.DataFrame(ohlcv, columns=['open', 'high', 'low', 'close', 'volume'], index=ohlcv['date'])
- 마지막 df[::-1]은 현재 날짜를 오름차순으로 출력하라는 의미이다.
2. 종목별 가격 정보 요청 응답 수신 함수
def _on_receive_tr_data(self, screen_no, rqname, trcode, record_name, next, unused1, unused2, unused3, unused4):
KOA > 개발 가이드 > 조회와 실시간 데이터 처리 > 관련 함수
1. 첫번째 매개변수 sScrNo: 임의로 정해서 사용하기로 한 화면번호(0001)
2. 두번째 매개변수sRQName은 "opt10081_req"로 TR를 호출할 때 사용했던 구분명
3. 세번째 매개변수 sTrCode는 호출되는 TR이름 "opt10081"
4. 네번째 매개변수 sRecordName은 레코드 이름이지만 실제로 안씀
5. sPrevNext는 TR 조회 후 OnReciveTrData를 통해 결과를 수신할 때 동일 TR 조회에 대해 추가적으로 받아 올 데이터가 있는지 의미함(추가로 받아올 데이터가 있다면 2로 전달받고 그렇지 않다면 0을 수신함)
6. 나머지 매개변수는 안씀
- opt10081를 보면 OUTPUT 멀티데이터에서 가져오는 값들을 볼 수 있음
- 멀티데이터를 제공하는 TR은 반복문을 이용해서 값을 가져와야 함
- 이번 요청에서 받아온 데이터 개수를 확인하려면 아래 GetRepeatCnt 함수에 호출한 TR 이름을 전달하면됨
tr_data_cnt = self.dynamicCall("GetRepeatCnt(QString, QString", trcode, rqname) #가져온 TR의 응답갯수 즉 600일치면 600이 저장됨
#만약 600일치가 넘은 일본데이터를 필요하면 next를 2로 설정해서 _on_receive_tr_data를 한번더 호출할 수 있게 함
if next == '2':
self.has_next_tr_data = True
else:
self.has_next_tr_data = False
그리고 다음 소스는 TR응답을 수신하는데 거기서 opt10081_req에 대해서만 처리하는 부분임
- 가져올 수있는 데이터들은 위에 멀티데이터 리스트에 언급되있음 그 중 필요한 부분만 가져오려면 GetCommData() 함수를 쓰면됨
if rqname == "opt10081_req":
ohlcv = {'date': [], 'open': [], 'high': [], 'low': [], 'close': [], 'volume': []}#ohlcv는 지역변수임
for i in range(tr_data_cnt):
date = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "일자")
open = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "시가")
high = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "고가")
low = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "저가")
close = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "현재가")
volume = self.dynamicCall("GetCommData(QString, QString, int, QString", trcode, rqname, i, "거래량")
ohlcv['date'].append(date.strip())
ohlcv['open'].append(int(open))
ohlcv['high'].append(int(high))
ohlcv['low'].append(int(low))
ohlcv['close'].append(int(close))
ohlcv['volume'].append(int(volume))
self.tr_data = ohlcv#글로벌 변수로 편입시킴
KOA에 보면 GetCommData 함수에 대한 설명은 다음과 같다.
1. 첫번째 매개변수: BSTR strTrCode, // TR 이름 ( opt10081)
2. 두번째 매개변수 BSTR strRecordName, // 레코드이름
3. 세번째 매개변수 long nIndex, // nIndex번째
4. 네번째 매개변수 BSTR strItemName) // TR에서 얻어오려는 출력항목이름
여기서 두번째 매개변수는 sRecordName은 레코드 이름이지만 실제로 안쓰지만 편의상 rqname을 넘겨준다.
그리고 아래 부분에서 ohlcv를 딕셔너리 형태로 저장해뒀는데 글로벌 변수로 편입시키는 부분이다.
self.tr_data = ohlcv#글로벌 변수로 편입시킴
time.sleep(0.5)는 0.5초만큼 쉰다는 의미인데 키움 API 이용 시 1초에 최대 5회의 요청만 허용하는 정책 때문이다.
self.tr_event_loop.exit()#tr 요청을 보내고 응답을 대기시키는데 사용하는 self.tr_event_loop 를 종료 시킴
time.sleep(0.5)#0.5초만듬 쉼
self.tr_event_loop.exit()는 TR요청을 보내고 응답을 대기시키는데 사용하는 self.tr_event_loop를 종료하는 역할을 한다.
(응답 대기상태를 해제함)
아래를 보면 Kiwoom 클래스 생성자에서 tr_event_loop = QEventLoop()부분이 실제 응답 요청하고 대기를 위한 변수를 생성하는 부분이다.
class Kiwoom(QAxWidget):
def __init__(self):
super().__init__()
self._make_kiwoom_instance()
self._set_signal_slots()
self._comm_connect()#로그인 요청하는 메소드 시작
self.account_number = self.get_account_number()#계좌번호 가져옴
self.tr_event_loop = QEventLoop()#TR 요청에 대한 응답 대기를 위한 변수 데이터가 올 때까지 기다렸다가 처리해!" 하는 용도로 사용하는 이벤트 루프
3. 실행 화면
- 삼성전자의 6월 2일 시가는 56300원 종가는 56800원으로 잘 맞다.
- 실제 영웅문 데이터는 아래와 같다. 거래량 데이터가 약간 다른데? 곰곰히 생각해보니까
- NXT거래소가 생기면서 종목코드만 보내면 안되고 KRX데이터인지, NXT 데이터인지 통합데이터인지를 구분해서 보내야된다.
- 즉 아래와 같이 호출할때 종목코드_AL 을 붙여줘야 전체 KRX+NXT 데이터 거래량이 나온다.
from api.Kiwoom import *
import sys
app = QApplication(sys.argv)
kiwoom = Kiwoom()
#kiwoom.get_account_number()
df = kiwoom.get_price_data("005930_AL")
print(df)
-거래량 16416858이 제대로 나온것을 알 수 있다.
참고. 소스코드
https://github.com/GaKaRi/python_trading/tree/main/SystemTrading
python_trading/SystemTrading at main · GaKaRi/python_trading
Contribute to GaKaRi/python_trading development by creating an account on GitHub.
github.com
'프로그래밍 > 파이썬' 카테고리의 다른 글
4. 파이썬 - 키움API 활용한 주문 체결 확인하기 (1) | 2025.06.15 |
---|---|
3. 파이썬 - 키움API 활용한 주문 접수하기 (2) | 2025.06.09 |
2. 파이썬 - 키움API 활용한 예수금 얻어오기 (2) | 2025.06.06 |