키움 REST API 업종지수 조회 — ka20001·ka20006 시장 필터 함정 6가지
키움 REST API 로 코스피·코스닥 지수 일봉은 ka20006 업종일봉조회요청(POST /api/dostk/chart, 필수 inds_cd·base_dt), 장중 현재 지수는 ka20001 업종현재가요청(POST /api/dostk/sect, 필수 mrkt_tp·inds_cd)으로 받습니다. 코스피 종합은 inds_cd 001, 코스닥 종합은 101입니다. 시장 필터(코스피 종가가 20·60일 이동평균 위일 때만 신규 매수)에서 걸리는 곳은 여섯입니다 — ⑴ 두 TR 은 경로가 다르다 ⑵ ka20006 가격은 소수점을 뗀 100배 정수, ka20001 은 부호 붙은 소수라 섞으면 100배 어긋난다 ⑶ 일봉은 최신순으로 온다 ⑷ 거래량 단위가 1000주와 1주로 다르다 ⑸ 오늘 봉은 미완성일 수 있다 ⑹ 형제 TR 명세에 오기가 있다.
이 글은 키움 REST API 자동매매 완전 가이드의 업종(지수) 편입니다. 종목 일봉 ka10081 은 키움 차트 데이터 ka10081에서 다뤘습니다. 필드명은 공식 명세 kiwoom_api_spec.json 기준(2026-09-29)입니다.
1. 업종 TR 지도 — 지수는 11개 TR 에 흩어져 있다
지수 TR 은 명세의 업종(/sect)과 차트(/chart) 메뉴에 나뉘어 있습니다. 시장 필터에 쓰는 건 ka20006·ka20001 둘입니다.
| api-id | 이름 | 응답 리스트 |
|---|---|---|
ka20001 | 업종현재가요청 | inds_cur_prc_tm(시간별) |
ka20002 | 업종별주가요청 | inds_stkpc(구성 종목 시세) |
ka20003 | 전업종지수요청 | all_inds_idex |
ka20009 | 업종현재가일별요청 | inds_cur_prc_daly_rept |
ka20004 | 업종틱차트조회요청 | inds_tic_chart_qry |
ka20005 | 업종분봉조회요청 | inds_min_pole_qry |
ka20006 | 업종일봉조회요청 | inds_dt_pole_qry |
ka20007 | 업종주봉조회요청 | inds_stk_pole_qry |
ka20008 | 업종월봉조회요청 | inds_mth_pole_qry |
ka20019 | 업종년봉조회요청 | inds_yr_pole_qry |
ka10101 | 업종코드 리스트 | list(code·name) |
ka20001·ka20002·ka20003·ka20009 는 /api/dostk/sect, 봉 TR 은 /api/dostk/chart, ka10101 은 /api/dostk/stkinfo 입니다. 명세의 inds_cd 값은 001 종합(KOSPI), 002 대형주, 101 종합(KOSDAQ), 201 KOSPI200, 302 KOSTAR, 701 KRX100 등이고 나머지는 ka10101 로 받습니다.
2. 요청 — 두 TR 은 경로와 필수 필드가 다르다
첫 함정은 복사·붙여넣기입니다. 경로가 다르고, ka20001 에만 mrkt_tp(0 코스피 · 1 코스닥 · 2 코스피200)가, ka20006 에만 base_dt 가 필수입니다. 공식 Postman 컬렉션 원문입니다.
// ka20006 업종일봉조회요청 — 공식 Postman 컬렉션(PRD) 원문
POST https://api.kiwoom.com/api/dostk/chart
api-id: ka20006
authorization: Bearer {ACCESS_TOKEN}
cont-yn:
next-key:
{
"inds_cd": "001",
"base_dt": "20260507"
}
// ka20001 업종현재가요청 — 경로가 다르고 mrkt_tp 가 하나 더 붙는다
POST https://api.kiwoom.com/api/dostk/sect
api-id: ka20001
{
"mrkt_tp": "0",
"inds_cd": "001"
}
짝이 어긋나면 1504(해당 URI 에서 지원하지 않는 API ID), 필수 필드가 빠지면 1511 이 옵니다. 업무 오류는 HTTP 200 과 return_code 로 오므로 raise_for_status() 만으로는 못 잡습니다(return_code 판별).
ka20001 은 mrkt_tp 와 inds_cd 짝을 맞추십시오(101 이면 1).
3. 응답 — 100배 정수와 부호 붙은 소수
가장 비싼 함정입니다. ka20006 의 cur_prc·open_pric·high_pric·low_pric 는 “소수점 제거된 100배 값”, ka20001 의 같은 이름 필드는 “부호가 포함된 숫자”("-2394.49")입니다. 이름은 같고 단위는 100배 다릅니다.
// ka20006 응답 — 공식 명세 예시값(원문 끝의 쉼표만 제거)
{
"inds_cd": "001",
"inds_dt_pole_qry": [
{"cur_prc": "252127", "trde_qty": "393564", "dt": "20250210",
"open_pric": "251064", "high_pric": "252733", "low_pric": "249918",
"trde_prica": "10582466"},
{"cur_prc": "252192", "trde_qty": "419872", "dt": "20250207",
"open_pric": "253209", "high_pric": "253763", "low_pric": "251901",
"trde_prica": "10240141"}
],
"return_code": 0,
"return_msg": "정상적으로 처리되었습니다"
}
// ka20001 응답 일부 — 공식 명세 예시값
{"cur_prc": "-2394.49", "pred_pre_sig": "5", "flu_rt": "-10.42",
"open_pric": "-2669.53", "trde_qty": "890", "inds_cur_prc_tm": [...]}
섞으면 필터가 조용히 고장 납니다. 이동평균은 ka20006(252127 수준), 현재 지수는 ka20001(2394.49 수준)로 받아 “현재가 > 이동평균”을 비교하면 늘 거짓, 부등호를 뒤집은 차단 조건이면 늘 참입니다. ka20006 은 ÷100, ka20001 은 abs() 만. 종목 일봉 ka10081 에는 이 100배 설명이 없어, 종목용 파서를 재사용하면 여기서 틀립니다.
ka20001 부호는 방향 표시입니다. 시가 open_pric 도 "-2669.53" 으로 오므로 값은 절댓값으로 쓰고, 방향은 pred_pre_sig(2 상승 · 3 보합 · 5 하락 등)로 봅니다.
거래량도 다릅니다. trde_qty 가 ka20001 은 1000주, ka20006 은 1주 단위입니다.
4. 연속조회와 정렬 — 최신순으로 온다
명세 예시 응답은 20250210 다음에 20250207 이 오는 최신순입니다. 정렬 없이 pandas rolling() 을 돌리면 “미래 20일 평균”이 나와 미래참조가 됩니다.
한 번에 오는 봉 개수는 명세에 없습니다. cont-yn 이 Y 인 동안 next-key 를 실어 이어 받고, 필요한 개수가 모이면 멈춥니다(키움 연속조회 next-key).
import time, datetime as dt, requests, pandas as pd
BASE = "https://api.kiwoom.com" # 모의투자: https://mockapi.kiwoom.com
CHART = "/api/dostk/chart" # ka20006 업종일봉
SECT = "/api/dostk/sect" # ka20001 업종현재가 — 경로가 다르다
def post(path, api_id, body, token, cont_yn="N", next_key=""):
r = requests.post(BASE + path, json=body, timeout=10, headers={
"Content-Type": "application/json;charset=UTF-8",
"authorization": f"Bearer {token}",
"api-id": api_id, "cont-yn": cont_yn, "next-key": next_key})
r.raise_for_status()
data = r.json()
if data.get("return_code") not in (0, None): # 업무 오류도 HTTP 200
raise RuntimeError(f"{api_id} rc={data.get('return_code')} {data.get('return_msg')}")
return data, r.headers.get("cont-yn", "N"), r.headers.get("next-key", "")
def num(s):
s = (s or "").strip().replace(",", "")
return abs(float(s)) if s not in ("", "+", "-") else float("nan")
def index_daily(token, inds_cd="001", base_dt=None, need=130, max_pages=10):
base_dt = base_dt or dt.date.today().strftime("%Y%m%d")
body = {"inds_cd": inds_cd, "base_dt": base_dt}
rows, cy, nk = [], "N", ""
for _ in range(max_pages):
data, cy, nk = post(CHART, "ka20006", body, token, cy, nk)
rows += data.get("inds_dt_pole_qry") or []
if cy != "Y" or len(rows) >= need:
break
time.sleep(0.25) # 초당 5회 한도 안쪽
df = pd.DataFrame(rows)
if df.empty: raise RuntimeError("ka20006 빈 응답")
df["date"] = pd.to_datetime(df["dt"], format="%Y%m%d")
for c in ("open_pric", "high_pric", "low_pric", "cur_prc"):
df[c] = df[c].map(num) / 100 # 소수점 제거된 100배 → 지수
df = (df.rename(columns={"cur_prc": "close"})
.drop_duplicates("date")
.sort_values("date") # 최신순 → 오름차순
.set_index("date"))
return df[["open_pric", "high_pric", "low_pric", "close"]]
5. 시장 필터 함수 — 오늘 봉은 뺀다
장중에 base_dt 를 오늘로 부르면 첫 행에 오늘 날짜가 올 수 있는데, 그 값이 확정 종가인지 현재값인지는 명세에 없습니다. 그래서 판정은 전일까지 완성된 봉으로 하고, 같은 규칙을 백테스트에도 겁니다.
def market_gate(df, fast=20, slow=60, today=None):
today = today or pd.Timestamp.today().normalize()
done = df[df.index < today] # 오늘 봉은 미완성일 수 있어 뺀다
if len(done) < slow:
return {"ok": False, "reason": "봉 부족"}
c = done["close"]
ma_f = c.rolling(fast).mean().iloc[-1]
ma_s = c.rolling(slow).mean().iloc[-1]
last = c.iloc[-1]
return {"ok": bool(last > ma_f and last > ma_s), "asof": c.index[-1].date(),
"close": round(last, 2), f"ma{fast}": round(ma_f, 2), f"ma{slow}": round(ma_s, 2)}
def live_index(token, mrkt_tp="0", inds_cd="001"):
data, _, _ = post(SECT, "ka20001", {"mrkt_tp": mrkt_tp, "inds_cd": inds_cd}, token)
return {"now": num(data.get("cur_prc")), # 이미 소수 — ÷100 하지 않는다
"sig": data.get("pred_pre_sig"),
"chg_pct": float(data.get("flu_rt") or 0)}
# 장 시작 전 1회: gate = market_gate(index_daily(token, "001"))
# gate["ok"] 가 False 면 신규 매수만 멈춘다(손절·청산은 계속)
필터가 막는 건 신규 매수뿐이어야 합니다. 손절·청산까지 멈추면 보유 종목이 방치됩니다. 다른 필터와의 조합은 수급 필터 만들기 참고.
6. 명세 오기 — 형제 TR 을 복사할 때 조심할 곳
어느 쪽이 실제 동작인지는 단정하지 않습니다.
| TR | 명세 원문 | 어긋나는 점 |
|---|---|---|
ka20007 주봉 | inds_cd length 8, base_dt length 3 | 다른 봉 TR 은 3·8 — 뒤바뀐 듯 |
ka20005 분봉 | 응답 필드 정의는 inds_min_pole_qry | 예시 응답은 일봉 키 inds_dt_pole_qry — 첫 호출 때 실제 키를 로그로 확인 |
ka20006 일봉 | 예시 요청 base_dt 20250905 | 응답은 2025년 2월, 끝 쉼표로 JSON 파싱 불가 |
7. 호출 예산 — 지수는 하루 한 번이면 된다
키움증권 공지 「REST API 유량 정책 안내(2026.07.02 기준)」는 국내주식을 토큰별 조회 TR 1초당 5회로 안내합니다. 전일 종가 기준 필터라면 장 시작 전 ka20006 을 코스피·코스닥 각 1회(연속조회 포함) 부르면 끝납니다. 장중 ka20001 을 매 초 부르면 1700 에 먼저 닿습니다(키움 429 대응). 휴장일 배치는 KRX 휴장일 체크로 건너뜁니다.
붙이기 전 체크리스트
지수 필터를 봇에 넣기 전 확인할 네 줄.
ka20006→/api/dostk/chart,ka20001→/api/dostk/sect로 경로를 따로 상수화했는가ka20006가격에 ÷100,ka20001가격에abs()만 적용했는가- 일봉을 오름차순 정렬한 뒤
rolling()을 돌리는가 - 필터가 막는 건 신규 매수뿐이고 손절·청산은 계속 도는가
자주 묻는 질문
키움 REST API 로 코스피 지수 일봉을 받으려면 어떤 TR 을 쓰나요?
업종일봉조회요청 ka20006 입니다. POST /api/dostk/chart 에 api-id 헤더 ka20006, 바디에 inds_cd(코스피 001, 코스닥 101)와 base_dt(YYYYMMDD)를 넣습니다. 둘 다 필수입니다. 봉 목록은 inds_dt_pole_qry 리스트로 오고, 더 과거는 cont-yn 이 Y 일 때 next-key 를 실어 이어 받습니다.
ka20006 의 cur_prc 가 252127 처럼 너무 크게 나옵니다.
명세대로입니다. ka20006 의 가격 필드는 소수점을 제거한 100배 값이라 252127 은 2521.27 입니다. 반면 ka20001 의 cur_prc 는 -2394.49 처럼 부호 붙은 소수입니다. 둘을 섞어 비교하면 100배 차이로 필터가 늘 한쪽으로만 판정됩니다.
코스피가 이동평균선 아래면 매수를 멈추는 필터를 쓰면 손실이 줄어드나요?
그렇다고 말할 수 없습니다. 효과는 기간·시장에 따라 다르고 과거 결과가 미래를 보장하지 않습니다. 이 글은 데이터를 정확히 받는 법만 다룹니다.
업종지수도 초당 호출 제한에 포함되나요?
키움 유량 공지(2026.07.02)는 국내주식 조회 TR 을 토큰별 초당 5회로 안내하고 업종 TR 을 따로 빼지 않습니다. 지수 일봉은 하루 한 번이면 충분합니다.
고지. 이 글은 기술 자료이며 특정 지수·종목·전략을 권유하지 않고 시장 방향을 예측하지 않습니다. 필드·코드값은 키움증권 공식 명세·Postman 컬렉션·공지를 2026-09-29 기준으로 대조했지만, 증권사 API 스펙과 유량 정책은 예고 없이 바뀌므로 실제 적용 전 키움증권 공식 문서를 다시 확인하십시오. 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객이 지정한 규칙을 프로그램으로 구현해 드리는 도구 제공자입니다.