키움 REST API 전종목 조회 — ka10099 필드 함정
ka10099(종목정보 리스트) 한 번으로 받습니다.
POST /api/dostk/stkinfo에 요청 항목은 mrkt_tp 하나뿐이고,
코스피 0 · 코스닥 10 · ETF 8입니다.
가장 많이 걸리는 지점은 응답 필드 표기법입니다 —
키움의 다른 TR은 stk_cd처럼 snake_case인데
ka10099만 code·name·listCount처럼 camelCase입니다.
그리고 listCount·lastPrice는 0으로 채워진 문자열로 옵니다.
봇을 만들 때 아무도 처음에 묻지 않지만 반드시 걸리는 질문이 있습니다. "그래서 무슨 종목을 감시하나?"
삼성전자 하나만 돌릴 거라면 코드를 상수로 박아 두면 됩니다. 그런데 조건검색이든 스캐너든 대상이 여럿인 순간 종목 목록이 필요해집니다. 키움 REST API 자동매매 완전 가이드가 다루는 여러 축 중 이 글은 "감시 대상 목록을 어디서 받아 어떻게 거르나" 하나만 떼어 다룹니다.
목차
- ka10099 — 요청 항목은 mrkt_tp 하나
- mrkt_tp 코드 16종 — 순서대로 세면 틀린다
- 함정 ① 혼자만 camelCase
- 함정 ② 0-padding 문자열
- 함정 ③ orderWarning — 봇이 빼야 할 종목
- 함정 ④ nxtEnable과 ka10100의 6자리 제약
- 실전 코드 — 유니버스 만들기
- 자주 묻는 질문
1. ka10099 — 요청 항목은 mrkt_tp 하나
키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD 기준)에서
국내주식 > 종목정보 아래 네 개의 TR이 같은 URL을 씁니다.
| api-id | 공식 이름 | 요청 항목 | 쓰임 |
|---|---|---|---|
ka10099 | 종목정보 리스트 | mrkt_tp (필수) | 시장 하나를 통째로 |
ka10100 | 종목정보 조회 | stk_cd (필수·6자리) | 한 종목만 |
ka10101 | 업종코드 리스트 | mrkt_tp (필수) | 업종 코드표 |
ka10001 | 주식기본정보요청 | stk_cd (필수) | PER·PBR·시총 등 상세 |
넷 다 POST /api/dostk/stkinfo이고, 갈리는 것은
차트 TR 3종과 마찬가지로
헤더 api-id입니다. 요청은 이렇게 단순합니다.
POST https://api.kiwoom.com/api/dostk/stkinfo
Content-Type: application/json;charset=UTF-8
authorization: Bearer {ACCESS_TOKEN}
api-id: ka10099
{ "mrkt_tp": "0" }
토큰 발급과 만료 규칙은 접근토큰 유효기간·폐기에 정리돼 있습니다.
2. mrkt_tp 코드 16종 — 순서대로 세면 틀린다
ka10099의 유일한 요청 항목인데 값이 16종입니다.
그리고 연속된 번호가 아닙니다.
| 값 | 시장 | 값 | 시장 |
|---|---|---|---|
0 | 코스피 | 2 | 인프라투융자 |
10 | 코스닥 | 3 | ELW |
30 | K-OTC | 4 | 뮤추얼펀드 |
50 | 코넥스 | 5 | 신주인수권 |
60 | ETN | 6 | 리츠종목 |
70 | 손실제한 ETN | 7 | 신주인수권증서 |
80 | 금현물 | 8 | ETF |
90 | 변동성 ETN | 9 | 하이일드펀드 |
가장 흔한 사고 — 코스닥이 1이 아니라 10입니다.
for i in range(2) 같은 반복문으로 코스피·코스닥을 돌리면
두 번째 호출이 코스닥이 아니라 존재하지 않는 값이 됩니다.
그리고 ETF는 8이라 한 자리 코드 그룹에 섞여 있습니다.
ETF만 골라 돌리는 봇을 만든다면 이 값을 씁니다 —
ETF가 개별종목과 무엇이 다른지는
ETF 자동매매 — 개별종목 봇과 다른 5가지에 있습니다.
3. 함정 ① 혼자만 camelCase
키움 REST API를 며칠 붙여 본 사람은 응답 필드가
stk_cd·cur_prc·trde_qty·oso_qty처럼
밑줄로 이어진 snake_case라는 것에 익숙해집니다.
ka10099는 다릅니다.
{
"list": [
{
"code": "005930",
"name": "삼성전자",
"listCount": "0000000005969782550", // 상장주식수 · 0-padding 문자열
"auditInfo": "정상", // 감리구분
"regDay": "19750611", // 상장일 YYYYMMDD
"lastPrice": "00071800", // 전일종가 · 0-padding 문자열
"state": "관리종목아님",
"marketCode": "0",
"marketName": "거래소",
"upName": "전기전자", // 업종명
"upSizeName": "대형주", // 회사크기분류
"orderWarning": "0", // 투자유의종목여부
"nxtEnable": "Y" // NXT 가능 여부
}
],
"return_code": 0,
"return_msg": "정상적으로 처리되었습니다"
}
요청은 mrkt_tp(snake_case), 응답은 listCount(camelCase)가
한 호출 안에 섞여 있습니다. 다른 TR용 파서를 재사용하면
모든 필드가 None으로 잡히고 빈 목록을 받은 것처럼 보입니다.
에러도 안 나고 return_code도 0이라
"왜 종목이 하나도 안 나오지"를 한참 헤매게 되는 자리입니다.
참고로 단건 조회 ka10100도 같은 camelCase이고,
ka10001(주식기본정보요청)은 stk_nm·per·pbr처럼
다시 snake_case입니다.
4. 함정 ② 0-padding 문자열
공식 명세의 항목 설명을 그대로 옮기면 이렇습니다.
| 항목 | 한글명 | 명세 설명 |
|---|---|---|
listCount | 상장주식수 | 단위: 1주, 좌측 0-padding 처리된 부호 포함 16자리 숫자 |
lastPrice | 전일종가 | 단위: 원, 좌측 0-padding 처리된 부호 포함 8자리 숫자 |
regDay | 상장일 | YYYYMMDD |
orderWarning | 투자유의종목여부 | 코드값 (아래 5절) |
ka10099의 응답 항목 타입은 전부 String입니다.
그래서 lastPrice를 문자열 그대로 비교하면
"00071800" < "00500000"처럼 사전순 비교가 되어
자릿수가 맞을 때만 우연히 맞습니다.
시가총액을 listCount × lastPrice로 구하려면 둘 다 정수 변환이 먼저입니다.
def to_int(v) -> int:
"""0-padding·부호 포함 문자열 → 정수. 빈 값은 0."""
s = str(v or "").strip().replace(",", "")
if not s or s in ("+", "-"):
return 0
return int(s) # "0000000005969782550" → 5969782550, "-00001200" → -1200
이 성질은 이 TR만의 특징이 아닙니다. 잔고조회 kt00018이나 차트 TR에서도 값은 전부 문자열로 옵니다. 응답 직후 숫자로 바꾸는 계층을 한 번 두고 그 아래 전략 코드는 숫자만 다루게 하는 구조가 안전합니다.
5. 함정 ③ orderWarning — 봇이 빼야 할 종목
이 항목이 ka10099를 단순한 코드 목록 이상으로 만듭니다.
공식 명세의 orderWarning(투자유의종목여부) 코드값은 이렇습니다.
| 값 | 의미 | 봇 관점 |
|---|---|---|
0 | 해당없음 | 정상 후보 |
2 | 정리매매 | 제외 — 상장폐지 절차 구간 |
3 | 단기과열 | 제외 권장 — 매매 방식이 달라질 수 있음 |
4 | 투자위험 | 제외 |
5 | 투자경과 | 제외 권장 |
1 | ETF투자주의요망 | ETF인 경우에만 전달 |
자동매매에서 이 필터가 없으면 어떤 일이 생기냐면 —
봇은 거래량이 튀는 종목을 좋아합니다.
그런데 거래량이 갑자기 튀는 종목 중 상당수가
정리매매나 단기과열 지정 구간에 있습니다.
사람이라면 종목명을 보는 순간 손을 떼지만 봇은 숫자만 보고 들어갑니다.
알트코인 봇의 유니버스 필터에서 다룬
"무엇을 후보에서 빼는가"가 국내주식에서 구체적인 필드로 존재하는 것이 orderWarning입니다.
이것만으로 충분하지는 않습니다. orderWarning은 지정 여부를 알려 줄 뿐이고,
각 지정의 기준·효과·해제 조건은 한국거래소 공시와 증권사 안내로 확인해야 합니다.
장중에 발동되는 변동성완화장치(VI)처럼
실시간으로 바뀌는 상태는 별도로 감시해야 하고,
목록은 최소 하루 1회 갱신하는 것을 전제로 설계하십시오.
6. 함정 ④ nxtEnable과 ka10100의 6자리 제약
nxtEnable(NXT가능여부)은 Y이면 가능이라고만 적혀 있습니다.
짧지만 중요한 필드입니다 — 대체거래소가 생긴 뒤
같은 종목이라도 어디로 주문을 보낼 수 있는지가 갈리기 때문입니다.
주문 경로 설계는 KRX·NXT·SOR 주문 라우팅에 있습니다.
여기서 미묘한 제약이 하나 더 붙습니다.
차트·주문 계열 TR의 stk_cd는 길이 20에
KRX:039490 / NXT:039490_NX / SOR:039490_AL처럼
거래소 접미사를 붙일 수 있다고 명세에 적혀 있습니다.
그런데 ka10100(종목정보 조회)의 stk_cd는 길이 6, 설명도 "종목코드 6자리"입니다.
접미사를 붙인 코드를 그대로 넘기면 안 된다는 뜻이므로,
코드 문자열을 여러 TR에 돌려 쓰는 구조라면 접미사를 떼는 지점을 명시해 두어야 합니다.
7. 실전 코드 — 유니버스 만들기
코스피·코스닥을 받아 유의종목을 걸러 봇이 감시할 목록을 만드는 최소 코드입니다.
import time, requests, pandas as pd
URL = "https://api.kiwoom.com/api/dostk/stkinfo"
EXCLUDE = {"2", "3", "4", "5"} # 정리매매·단기과열·투자위험·투자경과
def fetch_market(token: str, mrkt_tp: str) -> list:
rows, cont, nkey = [], "N", ""
while True:
headers = {
"Content-Type": "application/json;charset=UTF-8",
"authorization": f"Bearer {token}",
"api-id": "ka10099",
}
if cont == "Y": # 2회차부터만 채운다
headers["cont-yn"], headers["next-key"] = "Y", nkey
r = requests.post(URL, headers=headers, json={"mrkt_tp": mrkt_tp}, timeout=10)
r.raise_for_status()
data = r.json()
if data.get("return_code") not in (0, "0"):
raise RuntimeError(data.get("return_msg"))
rows.extend(data.get("list") or [])
cont = r.headers.get("cont-yn", "N")
nkey = r.headers.get("next-key", "")
if cont != "Y":
break
time.sleep(0.3) # 호출 제한 여유
return rows
def build_universe(token: str) -> pd.DataFrame:
rows = fetch_market(token, "0") + fetch_market(token, "10") # 코스피 + 코스닥
df = pd.DataFrame(rows)
df["listCount"] = df["listCount"].map(to_int)
df["lastPrice"] = df["lastPrice"].map(to_int)
df["mktCap"] = df["listCount"] * df["lastPrice"] # 개략 시가총액(원)
df = df[~df["orderWarning"].isin(EXCLUDE)] # 유의종목 제외
df = df[df["lastPrice"] > 0] # 거래 불가 상태 제외
return df.sort_values("mktCap", ascending=False).reset_index(drop=True)
cont-yn·next-key를 쓰는 이유는
시장 전체 목록이 한 번에 다 오지 않을 수 있기 때문입니다.
연속조회 헤더 규칙은
cont-yn·next-key에 따로 정리해 두었습니다.
한 가지만 더 — 여기서 만든 mktCap은
listCount × lastPrice로 계산한 개략값입니다.
우선주·자기주식 등의 처리에 따라 공시 시가총액과 차이가 날 수 있습니다.
정확한 시가총액이 필요하면 ka10001(주식기본정보요청)의
mac(시가총액·억원 단위) 항목을 쓰십시오. 단위가 억원이라는 점에 주의해야 합니다.
자주 묻는 질문
Q. 마스터 파일을 내려받는 방식과 뭐가 다른가요?
한국투자증권 계열에서 흔히 쓰는 방식은 압축된
종목코드 마스터 파일을 내려받아
고정폭으로 잘라 읽는 것입니다. 키움 ka10099는 인증된 REST 호출 한 번으로
JSON을 돌려주므로 압축 해제·인코딩·고정폭 파싱 단계가 없습니다.
대신 호출 제한을 함께 고려해야 하고, 마스터 파일에 있는 일부 상세 항목은 없습니다.
Q. 얼마나 자주 갱신해야 하나요?
신규 상장·상장폐지·orderWarning 지정은 날짜 단위로 바뀝니다.
장 시작 전 1회 갱신해 파일이나 데이터베이스에 적재하고,
장중에는 그 스냅샷을 쓰는 구조가 무난합니다.
매 루프마다 전 종목을 다시 받는 구조는 호출 제한에 걸립니다.
Q. 여기서 거른 종목으로 바로 매매해도 되나요?
ka10099가 걸러 주는 것은 "거래하면 안 되는 상태의 종목"이지
"수익이 나는 종목"이 아닙니다. 유니버스 필터는 손실 가능성을 줄이는 위생 조치이지
성과를 보장하지 않으며, 과거 데이터로 확인한 결과가 미래를 보장하지도 않습니다.
이 글은 특정 종목을 추천하지 않습니다.
마무리
ka10099는 요청 항목이 하나뿐이라 가장 쉬운 TR처럼 보입니다.
실제로 사람을 잡는 것은 네 군데였습니다 —
mrkt_tp가 연속 번호가 아니라는 것(코스닥 10·ETF 8),
혼자만 camelCase인 응답,
0으로 채워진 문자열,
그리고 orderWarning을 안 걸러서 정리매매 종목에 봇이 들어가는 것입니다.
받은 목록으로 무엇을 할지 — 차트를 붙이는 다음 단계는
키움 차트 데이터 ka10081로 이어집니다.
※ 본문의 요청·응답 항목명과 코드값은 키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD) 기준이며
2026-08-23 확인한 내용입니다. 투자유의종목 지정 기준과 시장 제도는 변경될 수 있으므로
한국거래소 공시와 키움 REST API 공식 가이드를 함께 확인하십시오.