키움 REST API 주식기본정보 ka10001 — 함정 5가지
키움 REST API의 ka10001 주식기본정보요청은 POST /api/dostk/stkinfo에 {"stk_cd":"005930"} 한 줄만 보내면 시가총액·PER·PBR·ROE·외인소진률·상하한가까지 47개 필드를 한 번에 돌려줍니다. 걸리는 곳은 다섯입니다. ⑴ lst_pric은 종가가 아니라 하한가입니다. ⑵ 250hgst처럼 숫자로 시작하는 필드명이 있어 파이썬 속성 접근이 안 됩니다. ⑶ 값이 전부 문자열이고 부호가 붙어 int()가 터집니다. ⑷ 업무 오류도 HTTP 200으로 오고 실패 여부는 return_code에만 있습니다. ⑸ 종목코드가 거래소별로 달라 NXT는 039490_NX, SOR은 039490_AL 형태입니다. 아래에서 하나씩 실물로 확인합니다.
목차
ka10001은 뭘 주나 — 47개 필드 지도
키움 REST API로 자동매매를 만들 때 종목을 고르는 단계에서 반드시 한 번은 부르게 되는 것이 ka10001입니다. 종목 하나를 넣으면 시세와 재무 지표가 통째로 돌아오기 때문입니다. 키움증권 공식 REST API 저장소의 ka10001 예제 코드에 매핑된 응답 필드를 성격별로 묶으면 이렇습니다.
| 분류 | 필드 |
|---|---|
| 식별 | stk_cd 종목코드 · stk_nm 종목명 · setl_mm 결산월 |
| 주식 수·자본 | cap 자본금 · fav 액면가 · fav_unit 액면가단위 · flo_stk 상장주식 · dstr_stk 유통주식 · dstr_rt 유통비율 |
| 규모·수급 | mac 시가총액 · mac_wght 시가총액비중 · for_exh_rt 외인소진률 · crd_rt 신용비율 · repl_pric 대용가 |
| 가치 지표 | per · eps · roe · pbr · ev · bps |
| 실적 | sale_amt 매출액 · bus_pro 영업이익 · cup_nga 당기순이익 |
| 당일 시세 | cur_prc 현재가 · open_pric 시가 · high_pric 고가 · low_pric 저가 · base_pric 기준가 |
| 가격 제한 | upl_pric 상한가 · lst_pric 하한가 |
| 등락 | pre_sig 대비기호 · pred_pre 전일대비 · flu_rt 등락율 · trde_qty 거래량 · trde_pre 거래대비 |
| 기간 고저 | oyr_hgst 연중최고 · oyr_lwst 연중최저 · 250hgst · 250lwst · 250hgst_pric_dt · 250hgst_pric_pre_rt · 250lwst_pric_dt · 250lwst_pric_pre_rt |
| 장전·장후 | exp_cntr_pric 예상체결가 · exp_cntr_qty 예상체결수량 |
사실 확인 기준 — 이 글의 필드명과 한글 의미는 2026-09-02 기준 키움증권 공식 REST API 저장소의 ka10001·ka10100 예제 코드와 공통 클라이언트 코드를 직접 대조한 값입니다. 필드 구성은 키움증권 공지로 바뀔 수 있으니 키움증권 개발자 포털의 현재 명세로 재확인하십시오.
요청은 이게 전부다
보내는 건 종목코드 하나입니다. 헤더 구성이 키움 REST API 전체에서 동일하다는 점만 기억하면 됩니다 — Content-Type, api-id, authorization 세 개가 고정이고, 연속조회가 필요한 API에서만 cont-yn과 next-key가 붙습니다.
import os, requests
BASE = "https://api.kiwoom.com" # 모의투자는 https://mockapi.kiwoom.com
PATH = "/api/dostk/stkinfo"
def get_stock_info(token: str, stk_cd: str) -> dict:
headers = {
"Content-Type": "application/json;charset=UTF-8",
"api-id": "ka10001",
"authorization": f"Bearer {token}",
}
res = requests.post(BASE + PATH, headers=headers,
json={"stk_cd": stk_cd}, timeout=10)
data = res.json()
# 키움은 업무 오류도 HTTP 200으로 내려보낸다 — return_code를 반드시 본다
if data.get("return_code") not in (None, 0):
raise RuntimeError(f"{data.get('return_code')} {data.get('return_msg')}")
return data
info = get_stock_info(TOKEN, "005930")
print(info["stk_nm"], info["mac"], info["per"], info["upl_pric"], info["lst_pric"])
토큰 발급과 폐기 흐름은 키움 REST API 토큰 만료·폐기에 따로 정리해 뒀고, 전체 구조는 키움 REST API 자동매매 가이드가 허브입니다.
함정① lst_pric은 종가가 아니라 하한가
이 글에서 가장 중요한 한 줄 — ka10001의 lst_pric은 하한가입니다. last price가 아닙니다. 상한가는 upl_pric, 기준가는 base_pric, 현재가는 cur_prc입니다.
영문 축약만 보고 lst_pric을 전일 종가로 읽으면 어떤 일이 벌어지는지가 문제입니다. 삼성전자처럼 가격대가 안정적인 종목에서는 하한가가 현재가의 70% 수준이므로, “종가 대비 -3% 이하로 떨어지면 매수” 같은 조건이 영원히 참이 되거나 영원히 거짓이 됩니다. 백테스트가 아니라 실계좌에서 조용히 틀리는 유형이라 발견도 늦습니다.
더 헷갈리는 건 같은 stkinfo 경로를 쓰는 ka10100에 실제로 전일종가를 뜻하는 lastPrice 필드가 따로 있다는 점입니다. 두 API를 오가며 코드를 짜다 보면 이름이 섞입니다. 실무 대응은 단순합니다 — 원본 필드명을 그대로 쓰지 말고 한글 의미로 한 번 매핑하고 넘기십시오.
# 원본 키를 코드 곳곳에 흩뿌리지 말고 한 번만 번역한다
FIELD_KO = {
"cur_prc": "현재가", "base_pric": "기준가",
"upl_pric": "상한가", "lst_pric": "하한가", # ← last가 아니라 하한가
"mac": "시가총액", "per": "PER", "pbr": "PBR",
"for_exh_rt": "외인소진률", "flo_stk": "상장주식",
}
row = {ko: info.get(raw) for raw, ko in FIELD_KO.items()}
함정② 숫자로 시작하는 필드명
ka10001 응답에는 250hgst, 250lwst, 250hgst_pric_dt, 250hgst_pric_pre_rt, 250lwst_pric_dt, 250lwst_pric_pre_rt 여섯 개가 있습니다. 250거래일 기준 최고·최저와 그 날짜, 대비율입니다.
문제는 파이썬 식별자는 숫자로 시작할 수 없다는 것입니다. info.250hgst는 문법 오류이고, dataclass나 pydantic 모델의 속성명으로도 못 씁니다. 응답을 객체로 감싸는 구조를 쓰고 있다면 여기서 처음 막힙니다.
# ❌ SyntaxError
# print(info.250hgst)
# ✅ dict 키로만 접근한다
print(info["250hgst"], info["250hgst_pric_dt"])
# ✅ 모델로 감쌀 거면 별칭을 준다 (pydantic 예)
# high_250: str = Field(alias="250hgst")
같은 이유로 응답을 그대로 pandas DataFrame에 넣고 df.250hgst로 쓰는 것도 안 됩니다. df["250hgst"]만 됩니다. 공식 예제가 응답을 받자마자 한글 컬럼명으로 rename하는 이유도 여기에 있습니다.
함정③ 값이 전부 문자열이고 부호가 붙는다
키움 REST API의 수치 필드는 숫자가 아니라 문자열로 옵니다. 그리고 등락율·전일대비처럼 방향이 있는 값에는 +나 -가 붙습니다. 공식 예제 코드도 값을 문자열로 받아 앞의 부호를 떼고 정수부와 소수부를 나눠 서식을 만드는 방식으로 처리합니다.
그래서 int(info["mac"])을 그대로 부르면 소수점이나 부호가 섞인 필드에서 ValueError가 납니다. 빈 문자열이 오는 경우도 있어서 float()만으로도 부족합니다. 변환 함수를 하나 만들어 전 필드에 적용하는 편이 안전합니다.
def to_num(v, default=None):
"""키움 응답의 문자열 수치를 float으로. 부호·공백·빈값 처리."""
if v is None:
return default
s = str(v).strip().replace(",", "")
if s in ("", "-", "+"):
return default
sign = -1.0 if s[0] == "-" else 1.0
s = s.lstrip("+-")
try:
return sign * float(s)
except ValueError:
return default
per = to_num(info.get("per")) # '12.34' → 12.34
chg = to_num(info.get("flu_rt")) # '-1.25' → -1.25
mac = to_num(info.get("mac")) # '4567890' → 4567890.0
PER·PBR을 조건에 쓸 때 주의 — 적자 종목이나 결산 직후에는 per·eps·roe가 0이나 빈 값으로 오는 경우가 있습니다. PER < 10 같은 조건을 그대로 걸면 지표가 없는 종목이 통과합니다. 0 < per < 10처럼 하한을 반드시 함께 거십시오.
함정④ 실패도 HTTP 200으로 온다
키움증권 공식 클라이언트 코드에는 “키움은 업무 오류도 HTTP 200으로 내려보내고 실패 여부는 본문 return_code에만 담는다”는 취지의 주석이 그대로 달려 있습니다. 즉 res.raise_for_status()만 믿는 코드는 실패를 성공으로 처리합니다.
토큰 만료는 특히 형태가 둘입니다. 공식 클라이언트는 최상위 return_code가 8005인 경우와 return_code가 3이면서 return_msg 안에 [8005:...]가 들어 있는 경우를 모두 토큰 재발급 신호로 잡습니다. 한쪽만 처리해 두면 어느 날 갑자기 조회가 조용히 실패합니다.
# 응답 판정 순서 — 이 순서를 지켜야 실패를 놓치지 않는다
data = res.json()
code = data.get("return_code")
msg = str(data.get("return_msg") or "")
if code == 8005 or "8005" in msg:
token = reissue_token() # 토큰 재발급 후 1회 재시도
elif code not in (None, 0):
log.error("ka10001 실패 code=%s msg=%s", code, msg)
raise RuntimeError(msg)
호출 빈도가 올라가면 429도 같이 봐야 합니다. 공식 예제는 연속조회 루프에서 요청 간격 0.2초를 기본으로 두고 있습니다. 제한 설계는 키움 REST API 429 요청 제한에, 연속조회 커서 처리는 cont-yn·next-key 연속조회에 정리해 뒀습니다.
함정⑤ 종목코드가 거래소별로 다르다
공식 예제의 stk_cd 설명에는 거래소별 표기가 이렇게 적혀 있습니다.
| 거래소 | 표기 예 | 의미 |
|---|---|---|
| KRX | 039490 | 한국거래소 — 접미사 없음 |
| NXT | 039490_NX | 넥스트레이드 — _NX 접미사 |
| SOR | 039490_AL | 최선주문집행 — _AL 접미사 |
종목 리스트를 ka10099 종목정보 리스트로 받아 ka10001에 그대로 넘기는 파이프라인이라면, 중간에 접미사가 섞여 들어오는지를 확인해야 합니다. 접미사가 붙은 코드로 조회하면 같은 회사라도 거래소별 값이 돌아옵니다. 대체거래소 자체의 주문 라우팅 문제는 NXT·KRX 주문 라우팅에서 별도로 다뤘습니다.
ka10001 vs ka10100 — 같은 URL, 다른 세계
둘 다 POST /api/dostk/stkinfo이고 body도 stk_cd 하나로 같습니다. 그런데 필드 네이밍 규칙이 통째로 다릅니다. 이건 파서를 두 벌 만들어야 한다는 뜻입니다.
| 구분 | ka10001 주식기본정보요청 | ka10100 종목정보 조회 |
|---|---|---|
| 네이밍 | 스네이크 축약 — stk_cd, flo_stk, for_exh_rt | 카멜 영문 — code, listCount, marketName |
| 성격 | 시세 + 재무 지표 | 종목의 속성·상태 |
| 대표 필드 | mac·per·pbr·roe·upl_pric·250hgst | listCount 상장주식수 · regDay 상장일 · upName 업종명 · state 종목상태 |
| 거르기용 필드 | 시가총액·PER로 규모·가치 필터 | orderWarning 투자유의종목여부 · auditInfo 감리구분 · nxtEnable NXT가능여부 |
| 종목코드 인자 | 거래소 접미사 표기 지원 | 종목코드 6자리 |
실무 조합 — 유니버스를 만들 때는 두 개를 같이 씁니다. ka10100의 orderWarning·auditInfo·state로 먼저 위험 종목을 걷어 내고, 남은 종목만 ka10001로 시가총액·PER을 조회해 순위를 매기는 순서입니다. 반대로 하면 걸러질 종목까지 조회해 요청 수만 낭비합니다.
실전 — PER·시가총액으로 유니버스 거르기
정리하면 ka10099 → ka10100 → ka10001 세 단계입니다. 요청 간격은 공식 예제와 같은 0.2초를 두고, 실패는 return_code로 판정합니다.
import time
def build_universe(token, codes, min_mac=3_000e8, per_range=(0, 15)):
picked = []
for code in codes:
try:
info = get_stock_info(token, code) # ka10001
except RuntimeError as e:
log.warning("skip %s: %s", code, e)
time.sleep(0.2)
continue
mac = to_num(info.get("mac")) # 시가총액
per = to_num(info.get("per"))
lo, hi = per_range
if mac and mac >= min_mac and per and lo < per < hi:
picked.append({
"code": code, "name": info.get("stk_nm"),
"mac": mac, "per": per,
"foreign": to_num(info.get("for_exh_rt")),
})
time.sleep(0.2) # 공식 예제와 같은 요청 간격
return sorted(picked, key=lambda r: -r["mac"])
단위 확인 — mac(시가총액)의 단위는 응답 그대로의 값이며 조회 시점 기준입니다. 위 코드의 min_mac 값은 예시일 뿐이므로, 실제로는 한 종목을 찍어 응답을 출력해 보고 본인이 확인한 단위로 임계값을 다시 잡으십시오. 단위를 짐작해서 넣으면 필터가 전부 통과하거나 전부 탈락합니다.
거래대금·등락률 기준으로 실시간 순위를 잡는 방식은 성격이 다릅니다. 그쪽은 거래량 순위·등락률 스크리너를, 차트 데이터가 필요한 단계는 키움 차트 조회(일·분·틱)를 참고하십시오. KIS 쪽과 필드 체계를 비교하려면 키움 vs KIS API 비교가 있습니다.
정리
ka10001은POST /api/dostk/stkinfo+{"stk_cd":"005930"}하나로 47개 필드를 준다lst_pric은 하한가, 상한가는upl_pric— 이름으로 짐작하지 말 것250hgst계열은 숫자로 시작해 dict 키로만 접근 가능- 수치는 문자열 + 부호 → 변환 함수를 하나 만들어 전 필드에 적용
- 실패도 HTTP 200이다.
return_code와8005두 형태를 함께 처리 - NXT는
_NX, SOR은_AL접미사가 붙는다
고지 — 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객의 규칙을 코드로 옮기는 도구 제공만 합니다. 이 글은 특정 종목이나 매매 전략을 추천하지 않고 수익을 보장하지 않습니다. 본문의 API 스펙은 2026-09-02 기준 키움증권 공식 REST API 저장소의 예제·클라이언트 코드를 대조한 것이며 공지로 변경될 수 있으니, 개발 전 키움증권 개발자 포털의 현재 명세를 직접 확인하십시오.