바로 전 시간에 종목코드 리스트와 종목정보를 키움증권에서 제공하는 Open API+를 사용해서 가져왔었습니다. 그럼 이번에는 가져온 종목코드를 가지고 해당 종목의 일봉차트를 조회해서 가져와 보도록 하겠습니다. 이 일봉차트가 있으면, 향후 이동평균선(이평선)을 구하고 이를 가지고 매매의 기준의 아주 기본적인 척도로 사용이 가능합니다.
키움증권의 KOA Studio를 확인해보면, TR목록 중에 [opt10081 : 주식일봉차트조회요청]이 존재합니다. 종목코드중에 하나를 가지고 속성에 값을넣고 조회를 해보면 아래와 같습니다.
해당 데이터는 [GetCodeListByMarket]를 통해서 받은 코스피항목 중 첫번째인 [동화약품]에 대한 데이터 입니다. 결과를 보면 해당 종목에 대해서 기준일자를 기준으로 그 전 일자의 모든 일봉정보를 전달 받습니다. 일자 별로 가장 최근 데이터 부터 [0] ~ [599]까지 600개의 데이터를 받아왔습니다. 그렇다면 차근차근 구현을 해 보겠습니다.
1. TR 서버 전송
지난 번에도 살짝 맛을 봤지만, TR을 키움증권의 API서버로 전송하기 위해서는 2가지의 메서드를 사용해야 합니다. KOA Studio에서 테스트를 해보았듯이 속성을 지정하는 SetInputValue와 지정된 값을 가지고 서버에 TR을 전송하는 CommRqData가 그것입니다.
이 모든 단계는 QAxWidget의 dynamicCall 메서드를 통해서 이루어 집니다. 실제 호출하는 코드는 아래와 같습니다.
#코스피 첫번째 종목 일봉차트 조회하기
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "종목코드", self.code_0[0])
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "기준일자", "20200221")
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")
self.OCXconn.dynamicCall("CommRqData(QString, QString, QString, QString)", "AT_opt10081", "opt10081", "0", "0101")
기존 시간에 코스피 항목과 코스닥항목을 전체 가져왔기 때문에, 그 변수의 첫번째를 일단 종목코드로 정보를 넣어주겠습니다. 또한, CommRqData에서 첫번째 데이터기 때문에 nPrevNext인자는 0:조회로 설정합니다.
2. 이벤트 리스너 추가
기존에 [opt10001 : 주식기본정보요청]을 조회하기 위해서 TR을 한번 전송하고 데이터를 받았었습니다. 그때, TR전송 후 서버로부터 오는 이벤트를 받는 로직을 구현했었습니다. 바로 OnReceiveTrData 이벤트 인데, 코드에서는 실제 해당 이벤트가 발생시 정해진 메서드로 connect( )만 시켜줬었습니다.
self.OCXconn.OnReceiveTrData.connect(self.trEvent)
따라서, 이번에는 해당 메서드에 새로운 사용자구분명을 통한 분기를 추가하겠습니다. 기존에 사용하는 이벤트처리 메서드는 trEvent( )입니다. 여기에 elif sRQName == 'AT_opt10081': 이런식으로 넣고 처리하면 되겠습니다. 아참!! 여기서 주의할 점이 2가지가 있습니다. 바로 대량데이터를 수신 및 표시하기 위한 반복작업입니다.
- 수신한 복수 데이터 반복 표시
- 대량데이터 반복 TR요청
[수신한 복수 데이터 반복 표시]
해당 TR에 대한 return은 복수개일 가능성이 99.99%이기 때문에, 반복문을 통해서 해당 값을 받아와야 합니다. 하지만 반복문을 수행하려면 몇개의 데이터가 있는지를 알아야 하는데... 역시나 이를 제공하는 메서드가 있습니다.
역시 이것도 dynamicCall을 통해서 수행합니다. 인자는 역시 TR코드와 요청이름으로 이 정보만 알면 거의 모든 메서드 처리가 가능합니다.
elif sRQName == 'AT_opt10081':
dataCnt = self.OCXconn.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName);
print('데이터 계속여부 : ', sPreNext, ', 가져온 갯수 : ', dataCnt)
for i in range(dataCnt):
date = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "일자").strip()
finish = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "현재가").strip()
mount = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래량").strip()
start = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "시가").strip()
high = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "고가").strip()
low = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "저가").strip()
print(date, finish, mount, start, high, low)
결과는 아래와 같이 nPreNext : 2로 계속된 데이터가 있고, 600개를 일단 가져왔습니다.
데이터 계속여부 : 2 , 가져온 갯수 : 600
20200221 7180 218425 7330 7350 7090
20200220 7330 139692 7520 7540 7260
20200219 7450 43486 7420 7480 7390
20200218 7390 256144 7520 7670 7380
[대량데이터 반복 TR요청]
다음은, 대량데이터에 대한 반복 TR요청입니다. KOA Studio를 통해서 조회해본 결과 한번에 CommRqData에서 파라미터로 nPrevNext에 최초에는 0:조회로 넣어서 보내는데 이때는 600개의 데이터를 받아오는 것으로 보입니다... 이의 결과로 이벤트가 왔을때, sPreNext로 값이 2가 오게되면 다음에 추가 조회할 데이터가 있다는 말이 됩니다. 그렇다면 CommRqDatap nPrevNext에 2:연속으로 하고 응답이 0이 올때까지 요청을 해야하는 것이지요.
CommRqData -> 0
2 <- OnReceiveTrData
CommRqData -> 2
2 <- OnReceiveTrData
CommRqData -> 2
0 <- OnReceiveTrData
종료
elif sRQName == 'AT_opt10081':
dataCnt = self.OCXconn.dynamicCall("GetRepeatCnt(QString, QString)", sTrCode, sRQName);
print('데이터 계속여부 : ', sPreNext, ', 가져온 갯수 : ', dataCnt)
for i in range(dataCnt):
date = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "일자").strip()
finish = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "현재가").strip()
mount = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "거래량").strip()
start = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "시가").strip()
high = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "고가").strip()
low = self.OCXconn.dynamicCall("GetCommData(QString, QString, int, QString)", sTrCode, sRQName, i, "저가").strip()
#print(date, finish, mount, start, high, low)
if sPreNext == '2':
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "종목코드", self.code_0[0])
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "기준일자", "20200221")
self.OCXconn.dynamicCall("SetInputValue(QString, QString)", "수정주가구분", "1")
self.OCXconn.dynamicCall("CommRqData(QString, QString, QString, QString)", "AT_opt10081", "opt10081", "2", "0101")
else:
print('종료')
이렇게 하면 결과는....
아무리봐도... 뭔가 덜 온것 같습니다. 왜 그럴까요?? 그 이유는 각 증권사에서 제공하는 API를 통해서 정보를 요청시 제약사항이 있습니다. 왜냐하면, API 서버의 부하를 고려해야 하기 때문인데요. 키움증권의 경우는 1초에 5개 Transaction이 제약입니다. 따라서 지연 요청을 해야합니다.
time.sleep( )을 적용한 코드는 아래와 같습니다. 중요한 위치의 부분만 기재하겠습니다.
import time
# time.sleep
self.sleepDuration = 0.2
if sPreNext == '2':
time.sleep(self.sleepDuration)
time package를 import하고, sleep시간을 초 기준으로 변수를 설정하고, 이전에 sPreNext가 2인경우는 다시 요청하기 때문에 이 부분에 sleep을 넣어줍니다. 이렇게 한 결과는....
다음과 같습니다. 정상적으로 가져왔습니다. 그렇다면 이 데이터는 DB에 저장하고 추후에 Tensorflow를 통한 분석이 필요하기 때문에, 다음에는 해당 데이터들을 DB에 저장하는 여러가지 방법론을 알아보겠습니다.
-Ayotera Lab-
댓글