EGW00201 초당 거래건수 초과 — KIS 호출 제한 해결
EGW00201은 코드 버그가 아니라 초당 호출 한도를 넘겼다는 신호입니다. 요청 내용은 정상이므로 tr_id나 파라미터를 고칠 필요가 없습니다. 고칠 곳은 호출 속도를 관리하는 계층 하나입니다. 핵심은 두 가지입니다 — ① 한도는 계좌가 아니라 APP KEY 단위로 공유되므로 봇·백테스트·수동 스크립트가 같은 앱키를 쓰면 호출량이 전부 합산됩니다. ② time.sleep(0.05)로 버티지 말고 모든 호출이 지나가는 자리에 토큰 버킷 리미터 하나를 두고, 그래도 뚫리면 지수 백오프 + 지터로 재시도하면 됩니다.
KIS API 에러코드 11가지에서 여러 오류를 묶어 다뤘지만, EGW00201 하나만 검색해서 들어오는 분이 유독 많습니다. 이유는 분명합니다. 이 오류는 처음 만드는 봇이 거의 반드시 한 번은 만나고, 그런데 에러 메시지만 봐서는 "내 코드가 뭘 잘못했나"를 알 수 없기 때문입니다.
이 글은 EGW00201 한 건만 다룹니다. 다른 KIS 오류가 섞여 있다면 위 묶음 글을, 애초에 한도 안에서 돌도록 설계하는 방법이 궁금하다면 호출 제한 설계 쪽을 보시는 게 빠릅니다. 여기서는 지금 에러가 뜨고 있는 상황을 가정합니다.
1. 응답이 어떻게 생겼나
KIS API는 실패를 HTTP 상태코드로만 알려 주지 않습니다. 200을 받고도 본문의 rt_cd가 0이 아니면 실패입니다. EGW00201은 대체로 이런 모양으로 돌아옵니다.
{
"rt_cd": "1",
"msg_cd": "EGW00201",
"msg1": "초당 거래건수를 초과하였습니다."
}
여기서 가장 중요한 사실은 msg1이 "초당"이라고 말한다는 점입니다. 계좌 문제도, 권한 문제도, 종목 문제도 아닙니다. 같은 요청을 잠시 뒤에 다시 보내면 성공합니다. 그래서 대응도 단순합니다 — 재시도 가능한 오류로 분류하고, 애초에 덜 발생하도록 속도를 조절하면 됩니다.
여기서 가장 흔한 실수: EGW00201을 보고 tr_id를 바꾸거나, appkey·appsecret을 재발급받거나, access_token을 다시 받으러 갑니다. 전부 무관합니다. 특히 토큰 재발급은 그 자체로 별도의 제한이 걸려 있어 — 토큰 발급 호출은 짧은 간격으로 반복하면 차단됩니다 — 문제를 하나 더 만드는 길입니다. 토큰은 파일이나 메모리에 캐싱해 두고 만료 전까지 재사용하세요.
2. 한도가 걸리는 단위가 핵심이다
이 오류를 오래 못 잡는 사람들의 공통점이 있습니다. "내 봇은 초당 3번밖에 안 부르는데요"라고 말합니다. 맞습니다. 그런데 한도는 프로세스 단위가 아니라 APP KEY 단위로 걸립니다.
| 착각 | 실제 |
|---|---|
| 봇 프로세스마다 따로 한도가 있다 | 같은 앱키를 쓰면 전부 합산 |
| 계좌를 나누면 한도가 늘어난다 | 계좌가 아니라 앱키 기준 |
| 주문만 세고 조회는 안 센다 | 조회도 호출이다 — 잔고·시세·체결 전부 |
| 실전과 모의는 한도가 같다 | 모의투자 한도가 훨씬 낮다 |
그래서 실제 원인은 대개 이런 조합입니다. 실매매 봇이 초당 3건, 그 옆에서 백테스트용 데이터 수집 스크립트가 초당 15건, 거기에 텔레그램 명령으로 잔고를 조회하는 핸들러가 가끔 끼어듭니다. 같은 앱키라면 이 셋이 하나의 한도를 나눠 씁니다.
한도 값 자체는 실전투자 초당 20건으로 널리 알려져 있고, 모의투자는 그보다 훨씬 낮습니다. 다만 모의투자 한도는 자료마다 초당 1건과 5건으로 엇갈려 소개되고 있어 그대로 믿기 어렵습니다. 정확한 값은 KIS Developers 공식 문서와 공지에서 확인하시고, 코드에 박지 말고 설정 파일로 빼 두시기 바랍니다. 정책은 바뀝니다.
모의투자에서만 EGW00201이 뜬다면 그건 정상입니다. 모의 계좌의 한도가 낮아서 그렇습니다. 여기서 코드를 뜯어고치면 실전에서는 오히려 과하게 느린 봇이 됩니다. 모의와 실전에서 달라지는 항목들은 모의투자 실전 전환에 따로 정리해 두었습니다.
3. 진단 — 3분 안에 원인 좁히기
- 같은 앱키를 쓰는 프로세스를 전부 세어 봅니다. 터미널에 띄워 둔 주피터 노트북, 어제 껐다고 생각한 수집 스크립트, cron에 걸어 둔 잔고 리포터까지 포함해서입니다.
- 루프 안에 종목 단위 조회가 있는지 봅니다.
for code in codes: get_price(code)형태는 종목 수가 곧 초당 호출 수가 됩니다. 70종목이면 순간 70건입니다. - 재시도 코드가 폭주를 만들고 있는지 봅니다. 실패하면 즉시 재시도하는 로직은 한도 초과 상황에서 호출량을 오히려 늘립니다.
- 토큰 재발급이 루프 안에 있는지 봅니다. 호출마다 토큰을 새로 받는 코드는 별개의 차단을 부릅니다.
네 가지 중 하나만 걸려도 EGW00201이 납니다. 그리고 실무에서 보면 2번(루프 조회)이 압도적으로 많습니다.
4. 해결 — 토큰 버킷 하나로 전부 통과시키기
가장 흔한 임시 처방이 time.sleep(0.05)입니다. 넘어가기는 하지만 두 가지가 나쁩니다. 한가할 때도 느려지고, 여러 곳에서 동시에 호출하면 여전히 몰립니다. 대신 모든 호출이 지나가는 자리에 리미터 하나를 둡니다.
import time, threading
class TokenBucket:
"""초당 rate건까지 즉시 통과, 넘으면 필요한 만큼만 대기."""
def __init__(self, rate, capacity=None):
self.rate = rate
self.capacity = capacity or rate
self.tokens = self.capacity
self.updated = time.monotonic()
self.lock = threading.Lock()
def acquire(self):
with self.lock:
now = time.monotonic()
self.tokens = min(self.capacity,
self.tokens + (now - self.updated) * self.rate)
self.updated = now
if self.tokens < 1:
wait = (1 - self.tokens) / self.rate
time.sleep(wait)
self.tokens = 0
self.updated = time.monotonic()
else:
self.tokens -= 1
# 공식 한도보다 낮게 — 여유를 둔다 (값은 설정 파일에서 주입)
LIMITER = TokenBucket(rate=15)
이제 KIS를 호출하는 모든 함수가 이 리미터를 지나가게 합니다. 중요한 건 일부가 아니라 전부라는 점입니다. 하나라도 빠지면 그 하나가 한도를 넘깁니다.
import random, requests
RETRYABLE = {"EGW00201"} # 기다렸다 재시도하면 되는 것
def kis_call(method, url, headers, **kw):
for attempt in range(5):
LIMITER.acquire()
r = requests.request(method, url, headers=headers, timeout=5, **kw)
body = r.json()
if body.get("rt_cd") == "0":
return body
code = body.get("msg_cd")
if code in RETRYABLE:
# 지수 백오프 + 지터 — 몰린 요청이 같은 시점에 재시도되는 것을 막는다
wait = (2 ** attempt) * 0.1 + random.uniform(0, 0.1)
time.sleep(wait)
continue
raise KisApiError(code=code, msg=body.get("msg1"), tr_id=headers.get("tr_id"))
raise KisRateLimited("EGW00201 재시도 5회 초과 — 호출 설계를 점검할 것")
지터(무작위 소량)를 반드시 넣으세요. 없으면 동시에 실패한 요청들이 정확히 같은 시점에 다시 몰려 똑같이 실패합니다. 이것이 재시도 코드가 상황을 악화시키는 전형적인 경로입니다.
주문 재시도는 조회 재시도와 다릅니다. 시세 조회는 몇 번을 다시 불러도 안전하지만, 주문은 중복 체결 위험이 있습니다. EGW00201은 애초에 요청이 접수되지 않은 상태이므로 재시도가 비교적 안전한 편이지만, 그래도 주문 경로에서는 재시도 전에 주문 내역을 조회해 실제로 안 들어갔는지 확인하는 단계를 넣는 편이 안전합니다.
5. 리미터로도 안 되면 — 구조를 바꿀 차례
① 종목 루프를 WebSocket으로 옮긴다
종목이 수십 개인데 REST로 시세를 폴링하면 리미터가 대기만 시키다가 봇이 느려집니다. 이때는 WebSocket 실시간 시세로 옮기는 것이 정석입니다. 구독 방식이라 호출량 자체가 줄어듭니다. 다만 웹소켓에도 세션당 구독 가능 종목 수 제한이 있어 흔히 41건으로 소개되므로, 그보다 많으면 세션을 나누는 설계가 필요합니다. 이 값 역시 공식 문서에서 확인하세요.
② 앱키를 용도별로 분리한다
실매매 봇과 데이터 수집을 다른 앱키로 돌리면 한도가 서로를 침범하지 않습니다. 다만 계좌·권한 조건이 따라붙으므로 무작정 늘릴 수 있는 것은 아닙니다.
③ 호출 자체를 줄인다
잔고를 매 틱 조회할 필요는 대개 없습니다. 체결 이벤트가 있을 때만 갱신하고 평소에는 로컬 상태를 신뢰하는 구조로 바꾸면 호출량이 크게 줄어듭니다. 운영 시간이 길어질수록 이 차이가 커집니다 — 예를 들어 NXT 연장시간까지 봇을 돌리면 같은 폴링 주기라도 하루 총 호출량이 거의 두 배가 됩니다.
대개 호출 구조 자체를 바꿔야 하는 단계입니다. 종목 수·전략 주기·조회 항목을 놓고 어디를 줄일 수 있는지 함께 봐 드립니다.
→ 무료 상담으로 호출 구조 점검받기정리
| 단계 | 할 일 |
|---|---|
| 1 | rt_cd·msg_cd를 반드시 검사 — HTTP 200이어도 실패일 수 있다 |
| 2 | 같은 APP KEY를 쓰는 프로세스를 전부 찾아낸다 |
| 3 | 모든 호출을 토큰 버킷 하나로 통과시킨다 (공식 한도보다 낮게) |
| 4 | EGW00201은 재시도 가능으로 분류 — 백오프 + 지터 |
| 5 | 그래도 느리면 WebSocket 전환 · 앱키 분리 · 호출 자체 축소 |
증권사마다 한도 체계와 오류 규격이 다릅니다. 키움·LS·대신과의 차이는 증권사 API 비교에서, KIS 발급 절차 전반은 KIS API 발급 30분 완성에서 확인하실 수 있습니다.
자주 묻는 질문
계좌를 하나 더 만들면 한도가 늘어나나요?
늘어나지 않습니다. 한도는 계좌가 아니라 APP KEY에 걸립니다. 같은 앱키로 여러 계좌를 조회하든, 여러 프로세스가 나눠 쓰든 호출량은 합산됩니다. 실제로 분리하려면 앱키 자체를 나눠야 합니다.
재시도를 몇 번까지 하는 게 적당한가요?
5회 정도에서 끊고 예외로 올리는 것을 권합니다. 그 이상 재시도해도 성공한다면, 이미 봇이 감당 못 할 호출량을 만들고 있다는 뜻이라 리미터 값이나 호출 구조를 고쳐야 할 신호입니다. 재시도 횟수를 늘려 덮으면 원인이 계속 숨습니다.
다른 KIS 오류도 같은 방식으로 재시도하면 되나요?
안 됩니다. EGW00201은 "속도" 문제라 시간이 해결하지만, 토큰 만료·권한 오류·잘못된 종목코드처럼 사람이 손대야 끝나는 오류를 재시도로 돌리면 무한 루프가 됩니다. 재시도 가능 목록을 상수로 명시해 두고 그 밖은 즉시 예외로 올리세요. 오류별 성격 구분은 KIS API 에러코드 11가지에 정리돼 있습니다.
공식 한도인 초당 20건을 그대로 리미터에 넣어도 되나요?
권하지 않습니다. 네트워크 지연과 서버 측 계측 시점 차이 때문에 한도에 딱 맞춘 값은 경계에서 자주 걸립니다. 실무에서는 20~30% 낮게 잡습니다. 또 한도 값 자체가 정책에 따라 바뀔 수 있으므로 코드에 하드코딩하지 말고 설정으로 빼 두시기 바랍니다.
한국투자증권 자동매매 봇 맞춤 제작
호출 한도 설계·재시도 정책·WebSocket 전환·24시간 무중단 운영까지 — 알고랩이 통합 패키지로 제작합니다.
24시간 빠른 답변 가능합니다.