PYTHON/Kiwoom Open API

키움증권 Open API - 전 종목 차트 자동 조회하기

  • -

이번 게시글에서는 이전에 제작했던 filtered_code() 함수와 rq_chart_data 함수를 이용해서 전체 종목의 종목 코드를 조회한 후에 해당 종목을 데이터베이스에 저장하는 방법에 대해 포스팅하고자 한다.

 

filtered_code() 함수 보완

여느 때와 마찬가지로 if문 아래에 다음과 같은 코드를 제작함으로써 필터링을 거친 종목 코드들을 불러올 수 있다. 하지만 지난 포스팅에서 제작하다 만 부분이 조금 있기 때문에, 이번 포스팅에서 마저 마무리 제작하고 for문을 돌리도록 하겠다. 2021년 6월 기준, 우선주 또는 ETN 등의 종목을 걸러내는 방법은 아래와 같다.

if 검색할 단어 in 대상 단어:

예를 들어 우선주를 걸러내고 싶다면 if "우" in codename과 같은 형태로, 스팩주를 걸러내고 싶다면 if "스팩" in codename과 같은 형태로 제작함으로써 codename(종목명) 안에 스팩 또는 '우'가 들어가 있는 경우를 걸러낼 수 있게 된다. 하지만 "우"의 경우에는 우선주도 있겠지만, 단순하게 '우'가 들어가는 종목명들이 있기 때문에, 별도의 방법으로 제거해야 한다. 제작한 코드는 아래와 같다.

def filtered_code(self):
	kospi = trade.GetCodeListByMakret('0').split(';')
	kosdaq = trade.GetCodeListByMakret('10').split(';')
	all_list = kospi+kosdaq

	code_list = []

	for code in all_list:
		codename = trade.GetMasterCodeName(code)

		# 스팩주 제거
		if "스팩" in codename or "4호" in codename:
			pass
			# print(codename)

		# ETN 제거
		elif "ETN" in codename:
			pass
			# print("ETN:", codename)

		# 우선주 제거
		elif codename[-1:] == "우" or "3우C" in codename or "우B" in codename or "G3우" in codename:
			pass

		# 지수 관련주 제거 1
		elif "TIGER" in codename or "KOSEF" in codename or "KBSTAR" in codename or "KODEX" in codename or "KINDEX" in codename or "TREX" in codename:
			pass

		# 지수 관련주 제거 2
		elif "ARIRANG" in codename or "SMART" in codename or "FOCUS" in codename or "HANARO" in codename or "TIMEFOLIO" in codename or "네비게이터" in codename:
			pass
		
        # 그 외의 종목들은 code_list에 append
		else:
			code_list.append(code)
			# print("종목명:", codename, " 종목코드:", code)

	# print("종목의 개수 :", len(code_list))
	return code_list

 


728x90

 

if 문 아래에서 for문 돌리기

일단 if문 아래에서는 우리가 바로 위에서 제작했던 filtered_code() 함수를 code_list라는 변수에 대입한 후에, code_list 변수를 대상으로 for문을 제작함으로써 각 종목 별로 rq_chart_data 함수에 종목 코드를 전달하는 방식으로 차트 데이터를 조회할 수 있다.

if __name__ == "__main__":
	app = QApplication(sys.argv)
	trade = system_trading()

	code_list = trade.filtered_code()

	for code in code_list:
		print("종목 코드:", code)
		trade.rq_chart_data(code, "20210101", 1)
		df_day_data = pandas.DataFrame(trade.day_data, columns=['date', 'open', 'high', 'low', 'close', 'volume', 'trade_volume'])
		print(df_day_data)

 

이제 코드를 실행해보면, 각 종목별로 아래와 같은 결과값이 출력되는 것을 확인할 수 있다.

종목 코드: 000020
          date   open   high    low  close   volume trade_volume
0     20201230  19100  19800  18800  19650   623218        12110
1     20201229  18750  19400  18750  19150   471430         9017
2     20201228  20000  20050  18600  18650  1215378        23326
3     20201224  20300  20500  19500  20000  1222707        24381
4     20201223  23500  24300  20450  20450  2745447        61422
...        ...    ...    ...    ...    ...      ...          ...
9527  19850109   1649   1649   1649   1649     3828            0
9528  19850108   1664   1664   1649   1649     2650            0
9529  19850107   1611   1633   1611   1633     6243            0
9530  19850105   1596   1598   1596   1598     3769            0
9531  19850104   1598   1598   1598   1598      471            0
[9532 rows x 7 columns]

종목 코드: 000040
           date    open    high     low   close   volume trade_volume
0      20201230   19100   19800   18800   19650   623218        12110
1      20201229   18750   19400   18750   19150   471430         9017
2      20201228   20000   20050   18600   18650  1215378        23326
3      20201224   20300   20500   19500   20000  1222707        24381
4      20201223   23500   24300   20450   20450  2745447        61422
...         ...     ...     ...     ...     ...      ...          ...
19059  19850109  206028  212209  206028  206028       57            0
19060  19850108  212895  212895  206028  206028       11            0
19061  19850107  206028  212895  206028  212895       16            0
19062  19850105  206028  206028  206028  206028        3            0
19063  19850104  206028  206028  206028  206028        0            0
[19064 rows x 7 columns]

종목 코드: 000050
           date   open   high    low  close   volume trade_volume
0      20201230  19100  19800  18800  19650   623218        12110
1      20201229  18750  19400  18750  19150   471430         9017
2      20201228  20000  20050  18600  18650  1215378        23326
3      20201224  20300  20500  19500  20000  1222707        24381
4      20201223  23500  24300  20450  20450  2745447        61422
...         ...    ...    ...    ...    ...      ...          ...
28591  19850109    781    781    781    781        0            0
28592  19850108    780    780    780    780      549            0
28593  19850107    744    744    744    744        0            0
28594  19850105    744    744    744    744        0            0
28595  19850104    744    744    744    744        0            0
[28596 rows x 7 columns]

 

그런데, 가만히 내비두면 다섯 번째 종목(또는 여섯 번째 종목)을 조회할 때, 아래와 같은 오류가 발생한다.

 

조회횟수 제한 : -210

반복되는 조회 요청의 시간 간격에 여유를 두라고 하는데, 이에 대한 키움증권의 설명은 다음과 같다.

주문에 대한 제한은 기존 초당5초 외에 추가된 제한이 없습니다.(추가내용)
제한기준이 시장상황과 서버상황에따라 유동적인 이유로 이에 차단메세지를 수신하신 고객님(Client)들께 공통적인 가이드를 제시하는데 시간이 지체되었습니다.

서버운영에 문제를 야기시켰던 부분은 1초당 5회라는 횟수보다는 이를 반복하는 것 입니다.
서버의 점유율이 해소되기 위한 idle Time을 확보하기가 어렵습니다.

서버리스크를 회피하면서, OpenAPI 의 모든 고객분들께서 조회 차단을 회피하는 가이드는 아래와 같습니다.
(1초당 5회로 기작업되어 있다는 전제하에 이를 기준으로 한 가이드 입니다.)
- 1초당 5회 조회를 1번 발생시킨 경우 : 17초대기
- 1초당 5회 조회를 5연속 발생시킨 경우 : 90초대기
- 1초당 5회 조회를 10연속 발생시킨 경우 : 3분(180초)대기

위 방법으로 대응이 어려운 성격의 프로그램을 운영중이신 고객분께서는 글 올려주시면 별도의 대안으로 답변드리도록 하겠습니다.
안정적인 서비스 운영이 최우선이니 양해바라겠습니다.

즉, 1초당 5회 조회를 기준으로 하되 그마저도 반복해서 조회할 경우에는 오류가 발생한다. 이에 따라 우리는 각 차트 데이터 조회 요청 함수 별로 중간 중간에 시간 간격을 두게 함으로써 이러한 오류를 최대한 피하는 방법을 사용해야 한다. 앞서 제작했던 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_()

	while self.remained_data == True:
		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", 2, "0101")
		self.tr_event_loop = QEventLoop()
		self.tr_event_loop.exec_()

이제 while문을 기준으로 위 아래로 나뉘어 있고, CommRqData 함수가 위 아래에 각각 하나씩 위치해 있다. 여기서의 CommRqData가 바로 Command Request Data의 약자로, 데이터를 요청하는 함수이다. 복잡하게 설명하고 이해할 것 없이 CommRqData가 1회 실행될 경우에 1회 요청이 이루어지는 것이다. 위의 코드 내에서는 별도의 시간 지연 코드가 없기 때문에 특정 동작이 끝나는 대로 바로 다음 동작이 실행되면서 위의 오류가 발생한 것이다. 그러면 이벤트 루프 종료 코드의 아래 부분에 다음과 같은 시간 지연 함수를 제작해주도록 하자.

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_()
	time.sleep(0.3)

	while self.remained_data == True:
		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", 2, "0101")
		self.tr_event_loop = QEventLoop()
		self.tr_event_loop.exec_()
		time.sleep(0.3)

이처럼 한 번의 요청 이후에 0.3초의 시간 간격을 두게 된다면, 1초에 몇 회를 요청할 수 있는가? 약 3.3회일 것이다. 이렇게 하면 키움증권의 조회 요청 횟수 제한 오류에 걸리지 않아야 하는데, 오류에 걸리게 된다. 물론 이전에 제작했던 것보다는 한 번의 동작에서 더 많은 종목 코드를 불러올 수 있다.

또한 이 조회 횟수 제한 오류는 차트 데이터 조회 외의 다른 부분에서도 발생할 수 있으며 그 때마다 time.sleep()을 입력하는 것 정도는 충분히 이해할 수 있으나, 만약 나중에 0.3초라는 부분이 마음에 들지 않아 0.4초로 바꾸고자 할 때에는 time.sleep()을 일일이 다 검색해서 0.4로 바꾸어주어야 할 것이다. 따라서 이런 불편함을 미연에 방지하기 위해 0.3이 아닌 특정 변수 값을 넣어주고, 그 변수를 time.sleep()안에 넣어주도록 하자. 본인의 경우에는 request_term이라는 변수를 지정했고, 해당 변수에는 0.3이라는 값을 입력해주었다. 그 후에 rq_chart_data 밑에는 time.sleep(request_term)과 같은 형태로 제작함으로써 시간 지연의 값을 일괄적으로 관리할 수 있도록 구축하였다.

request_term = 0.3

class system_trading():

    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_()
        time.sleep(request_term)

        while self.remained_data == True:
            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", 2, "0101")
            self.tr_event_loop = QEventLoop()
            self.tr_event_loop.exec_()
            time.sleep(request_term)

 

 

여기서 조회한 일봉 차트 데이터를 데이터베이스에 저장하는 방법은 다른 포스팅에서 제작하였으니 여기서는 다루지 않겠다. 다만 이러한 차트 데이터 조회를 실시하게 되면, 우리가 000020이라는 종목 코드에 대한 일봉 차트 데이터를 데이터베이스에 저장했음에도 불구하고 또 다시 000020이라는 종목부터 데이터 조회를 실행하고 저장하게 된다. 따라서 다음 포스팅에서는 해당 종목의 데이터가 저장되었는지 등의 여부를 판단할 수 있는 별도의 데이터베이스를 만들고, 그를 바탕으로 for문을 실행할지 말지 결정하도록 하는 방법에 대해 알아보도록 하겠다.

 

 


728x90
반응형
Contents

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

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