키움 OpenAPI로 분봉 데이터를 수집하다
-209에러에 막힌 적이 있다면, 원인은 "너무 빨리 조회해서"가 아니라 "이미 있는 데이터를 매번 통째로 다시 받아서"일 가능성이 높다. 이 글은 633종목 1분봉 수집기가 -209에 막힌 실제 사례에서, 원인 → 수정 → 검증까지의 기록이다.
환경
- Python 3.10 (32bit) + PyQt5 + 키움 OpenAPI
- TR:
opt10080(주식분봉차트조회) - 백테스트용 1분봉 수집기: 종목별 CSV 캐시 저장, 재실행 시 캐시 재사용
1. 증상
백테스트용으로 633종목의 1분봉을 수집하던 중, 수집이 한창 진행되다가 키움 팝업이 떴다.
조회횟수 제한 : -209
이 팝업이 뜨면 이후 모든 TR 조회가 일시 차단된다. 프로그램을 종료하고 기다렸다가(체감상 10~30분) 재실행하는 것 외에 방법이 없다. 문제는 재실행해도 수집량이 많으면 또 걸린다는 것. 기다렸다 재실행을 반복하면 하루가 다 간다.
2. -209가 뭔가
-209는 키움 OpenAPI의 TR 조회 한도 초과 에러다. 키움은 초당 조회 제한(통상 1초 5회) 외에, 짧은 시간에 누적 조회량이 과도하면 일정 시간 조회 자체를 차단한다. 조회 간격에 sleep을 넣어도, 총 조회량 자체가 많으면 결국 걸린다.
즉 근본 해결은 "천천히 받기"가 아니라 "덜 받기"다.
3. 원인: 캐시가 있는데도 매번 통째로 받는 구조
수집기 로그를 보니 이상한 점이 있었다. 이미 6월 5일까지 캐시가 차 있는 종목인데도, 연속조회가 51회씩 돌고 있었다. 51회면 사실상 시작일(1월)부터 전 구간을 다시 받고 있다는 뜻이다.
원인은 fetch_minute의 연속조회 중단 조건이었다. 핵심 배경 지식 하나:
opt10080은 최신→과거 순으로 준다. 연속조회(prevnext=2)를 할 때마다 1회당 약 900개 분봉(대략 2.3거래일치)씩 과거로 거슬러 내려간다.
기존 코드의 중단 조건은 "받은 데이터의 가장 과거 봉이 시작일에 도달하면 멈춤"이었다.
# [수정 전] fetch_minute의 중단 조건 (개념 코드)
target = start_dt - pd.Timedelta(days=warmup_days) # 항상 "시작일 - 워밍업"
while oldest > target:
# prevnext=2 연속조회... 1회당 ~900봉씩 과거로
...
그러니 캐시가 6/5까지 차 있는 종목도, 조회를 시작하면 무조건 1월까지 거슬러 내려간다. 캐시는 "받은 뒤 병합"에만 쓰이고 "어디까지 받을지"에는 전혀 반영되지 않았던 것이다.
- 종목당 조회: 약 49회 (1월~6월 전 구간)
- 이걸 633종목 × 49회 = 수만 회 → -209 확정
4. 수정: "캐시 마지막 날짜까지만 거슬러 받기"
수정은 두 파일, 두 지점이다.
4-1. fetch_minute에 중단 한계 파라미터 추가
# [수정 후] boll_dashboard.py — fetch_minute
def fetch_minute(self, code, start_dt, tick="1",
warmup_days=3, max_loops=400,
fetch_back_to=None):
"""
- 기본: start_dt - warmup_days 까지 거슬러 받음 (기존 동작 유지)
- fetch_back_to 지정 시: 그 시각까지만 거슬러 받고 멈춤 (증분 조회)
→ 캐시에 이미 있는 과거 구간은 다시 받지 않음
"""
if fetch_back_to is None:
target = start_dt - pd.Timedelta(days=warmup_days)
else:
target = pd.Timestamp(fetch_back_to)
# 이하 연속조회 루프는 동일: oldest <= target 이면 중단
포인트: 기본값이 None이라 기존 호출부는 동작이 그대로다 (하위호환). 캐시가 없는 신규 종목은 여전히 시작일까지 전체를 받는다 — 이건 받아야 하는 게 맞다.
4-2. 호출부(_get_bars)에서 캐시 최신 시각을 넘겨주기
# [수정 후] sweep_boll_dashboard.py — _get_bars
incr_from = None
if (existing is not None and not existing.empty
and existing["datetime"].min() <= start):
# 캐시가 시작일까지 커버할 때만 증분 대상
# 경계 봉 누락 방지를 위해 캐시 최신에서 5분 겹치게 받는다
incr_from = existing["datetime"].max() - pd.Timedelta(minutes=5)
df = self.kiwoom.fetch_minute(code, start, warmup_days=3,
fetch_back_to=incr_from)
설계에서 신경 쓴 두 가지:
- 경계 중복: 캐시 최신 시각 정확히에서 끊으면 경계 봉이 누락될 수 있어, 5분 겹치게 받고 병합 시 중복 제거한다.
- 증분 자격 검사: 캐시가 시작일까지 커버하지 못하는(중간에 구멍 가능성이 있는) 종목은 증분 대상에서 제외하고 전체를 받는다. 증분은 "이어붙이기"지 "구멍 메우기"가 아니기 때문이다.
5. 검증
5-1. 조회량 시뮬레이션
opt10080 1회 ≈ 900분봉 ≈ 2.3거래일이라는 특성으로, 6/5에서 멈춘 종목의 조회 횟수를 계산해봤다.
[6/5 멈춘 종목] 기존(통째, 1월~6월) 49회 → 증분(6/5~6/26) 6회 (87% 감소)
5-2. 실제 수집 로그
수정 후 재실행한 실제 로그다.
[조회 616/633] OO소재: 6564봉 (최과거 2026-06-02) ← 연속조회 1회로 종료
...
[준비완료] 633종목 확보 (신규조회 451 · 캐시재사용 182)
- 이미 최신까지 차 있던 182종목: 조회 0회, 캐시 재사용으로 건너뜀
- 캐시가 중간에 멈춰 있던 종목: 연속조회 1~6회로 종료 (기존 49~51회)
- -209 재발 없이 633종목 완주
5-3. 정합성 확인 (전 종목이 같은 날짜까지 찼는가)
수집이 끝난 뒤, 전 종목 캐시의 마지막 날짜 분포를 한 줄로 확인했다.
import pandas as pd, glob
from collections import Counter
print(Counter(str(pd.read_csv(f)['datetime'].max())[:10]
for f in glob.glob('cache/*.csv')))
# 기대 결과: {'2026-06-26': 633}
전 종목이 같은 종료일이면 수집 완료. 특정 종목만 과거 날짜에 멈춰 있으면 그 종목만 다시 보면 된다.
6. 주의사항 (직접 겪은 것들)
증분은 신규 종목에는 효과가 없다. 수정 후 첫 실행에서 로그를 보고 "왜 아직도 오래 걸리지?" 했는데, 이번 유니버스에 새로 편입된 종목이 451개였고 이들은 캐시가 없으니 전체를 받을 수밖에 없다. 증분의 효과는 두 번째 실행부터 본격적으로 나타난다. 실제로 그다음 실행부터는 대부분 캐시재사용 또는 1~2회 조회로 끝났다.
-209가 이미 뜬 상태라면 수정본으로도 바로 재실행하지 마라. 차단이 풀리는 데 시간이 필요하다(10~30분). 코드가 좋아져도 차단 중엔 조회가 안 된다.
그래도 -209가 또 뜨면 종목 간 대기(이 수집기는 0.4초)와 연속조회 간 대기(0.3초)를 늘리는 게 다음 수단이다. 다만 조회량 자체를 줄이는 것(증분)이 먼저고, 간격 조정은 보조 수단이다.
요약
| 수정 전 | 수정 후 | |
|---|---|---|
| 중단 기준 | 항상 시작일까지 | 캐시 최신 시각까지 (fetch_back_to) |
| 멈춘 종목 조회 | ~49회 | ~6회 (87%↓) |
| 최신 종목 조회 | ~49회 | 0회 (캐시 재사용) |
| -209 | 반복 발생 | 633종목 완주, 재발 없음 |
키움 -209의 근본 해법은 sleep이 아니라 조회량 감소다. 그리고 조회량 감소의 핵심은 "캐시를 병합에만 쓰지 말고, 어디까지 받을지 결정하는 데 쓰는 것"이다.
다음 글 예고: 2편: [키움 opt10080 분봉 캐시 함정 — 캐시가 있는데도 최신 데이터를 안 받아오는 이유](https://oneai.tistory.com/2)
이 시스템의 실매매 기록은 유튜브에서 → https://www.youtube.com/@quant-one-ai
📖 3년 기록을 전자책으로 정리 중입니다 — 목차·샘플 공개: (https://oneai.tistory.com/6)
One AI : AI 자동매매 시스템 만들기
이 채널은 AI와 규칙 기반 시스템으로 운영하는 퀀트매매, 자동매매의 실전 기록 채널입니다. 매매 신호, 실행, 결과, 복기 과정을 영상으로 정리합니다. 특정 종목 추천이 아닌 개인 매매 기록
www.youtube.com