키움 ka10034 외인기간별매매상위 — 기간은 6개뿐이다
ka10034(외인기간별매매상위요청)는 "최근 N일 외국인 누적 순매수 상위"를 시장 단위로 한 번에 주지만, N은 고를 수 없습니다. 기간 dt는 0(당일)·1(전일)·5·10·20·60일 여섯 값뿐이고 시작일·기준일 필드가 없어 항상 호출한 날 기준입니다 — 과거 날짜로 백테스트할 데이터는 이 TR로 못 만듭니다. 응답 for_dt_trde_upper의 수급 숫자는 netprps_qty(순매수량, 1주) 하나라 금액은 직접 근사해야 하고, 명세의 dt 설명에는 10;10일 오타가 있어 그대로 파싱하면 ValueError가 납니다(ka10036·ka10037도 같음). (공식 명세 kiwoom_api_spec.json·예제, 2026-10-16 main 953e5db 기준)
목차
- ka10035·ka10036과 무엇이 다른가
- 요청·응답 실물
- 함정 6가지
- 최소 호출 코드
- 봇에서 쓰는 순서
- 자주 묻는 질문
1. ka10035·ka10036과 무엇이 다른가
그제 정리한 ka10035 외인연속순매매상위는 "최근 사흘"만 봤습니다. 20일·60일처럼 더 긴 창으로 외국인이 많이 산 종목을 보려면 같은 /api/dostk/rkinfo 경로의 ka10034를 씁니다. 외국인 관련 순위 TR 셋을 나란히 놓으면 이렇습니다.
| 항목 | ka10034 외인기간별매매상위 | ka10035 외인연속순매매상위 | ka10036 외인한도소진율증가상위 |
|---|---|---|---|
| 기간 | dt 0·1·5·10·20·60 | base_dt_tp 0:당일·1:전일기준 | dt 0·1·5·10·20·60 |
| 방향 | trde_tp 1:순매도, 2:순매수, 3:순매매 | 1:연속순매도, 2:연속순매수 | 없음 |
| 수급 숫자 | netprps_qty 순매수량 1개 | dm1~dm3 + tot | 소진율·소진율증가 |
| 더 주는 것 | sel_bid·buy_bid·trde_qty·gain_pos_stkcnt | limit_exh_rt | poss_stkcnt·gain_pos_stkcnt |
| 순위 필드 | rank 있음 | 없음 | rank 있음 |
| 공통 | mrkt_tp 000:전체·001:코스피·101:코스닥, stex_tp 1:KRX·2:NXT·3:통합 (모두 필수) | ||
2. 요청·응답 실물
요청 body는 네 개가 전부이며 모두 필수입니다. 명세의 request_example 원문이고, 공식 예제 examples/국내주식/순위정보/get_domestic_foreign_investor_period_trade_top.py의 __main__도 같은 값을 씁니다.
{
"mrkt_tp": "001",
"trde_tp": "2",
"dt": "0",
"stex_tp": "1"
}
헤더는 api-id: ka10034, authorization: Bearer …, 연속조회 시 cont-yn·next-key입니다. 명세의 response_example을 pandas로 펼친 결과입니다(필드 일부, stk_nm은 세 행 모두 삼성전자).
rank stk_cd cur_prc pred_pre_sig pred_pre sel_bid buy_bid netprps_qty
1 005930 +74800 1 +17200 0 +74800 +290232191
2 005930 -183500 5 -900 +184900 0 +167189864
3 005930 4115 3 0 0 0 +59255646
| element | 한글명 | 명세 설명 |
|---|---|---|
rank · stk_cd · stk_nm | 순위 · 종목코드 · 종목명 | — |
cur_prc · pred_pre | 현재가 · 전일대비 | 단위: 원, 부호가 포함된 숫자 |
pred_pre_sig | 전일대비기호 | 1:상한가, 2:상승, 3:보합, 4:하한가, 5:하락 |
sel_bid · buy_bid | 매도호가 · 매수호가 | 단위: 원, 부호가 포함된 숫자 |
trde_qty | 거래량 | 단위: 1주 |
netprps_qty | 순매수량 | 단위: 1주, 부호가 포함된 숫자 |
gain_pos_stkcnt | 취득가능주식수 | 단위: 1주 |
3. 함정 6가지
① 기간은 여섯 값뿐, 날짜는 못 고른다
dt의 허용값은 0·1·5·10·20·60입니다. "3일 순매수 상위"는 없고, ka10131처럼 strt_dt·end_dt를 넣는 길도 없습니다. 더 큰 문제는 기준일이 없다는 것입니다. 2026년 3월 기준 20일 순매수 상위는 그때 저장해 두지 않았으면 다시 받을 수 없습니다. 이 TR로 신호를 만든다면 매일 장 마감 뒤 결과를 파일로 쌓아 두는 것부터 시작해야 하고, 그 전 기간은 ka10059나 ka10008(주식외국인종목별매매동향 — 일자별 chg_qty)로 종목마다 따로 만들어야 합니다.
② 명세 오타 10;10일 — 파서가 죽는다
명세 JSON에서 코드표를 자동으로 뽑는 경우(코드 생성기·MCP 도구 등)에 걸립니다. 실제로 돌린 결과입니다(pandas 2.2.3, Python 3.10).
>>> desc
'0:당일, 1:전일, 5:5일, 10;10일, 20:20일, 60:60일'
>>> dict(p.split(':', 1) for p in desc.split(', '))
ValueError: dictionary update sequence element #3 has length 1; 2 is required
>>> dict(re.split('[:;]', p, maxsplit=1) for p in desc.split(', '))
{'0': '당일', '1': '전일', '5': '5일', '10': '10일', '20': '20일', '60': '60일'}
명세 전체 337개 API 중 이 오타가 있는 요청 필드는 ka10034·ka10036·ka10037 세 개입니다. 사람이 읽을 때는 문제없지만 자동 파싱이면 [:;]로 나누거나 허용값을 상수로 박으십시오.
③ 수량뿐 — 60일이면 금액 근사가 크게 틀린다
숫자는 netprps_qty(1주) 하나입니다. 저가주가 위로 오는 문제는 ka10035와 같아서 cur_prc 절댓값을 곱해 금액으로 바꾸게 되는데, dt가 길수록 그동안 가격이 움직였으므로 오차가 커집니다. 당일·5일은 근사로 쓸 만하고, 20·60일은 순서 비교용으로만 보십시오. 금액이 필요하면 ka10131(amt_qty_tp 0:금액)이 정답입니다. 같은 경로의 ka10037은 sort_tp 1:금액이 있지만 외국계 창구 기준이라 외국인 전체와 다릅니다.
④ 매도호가 0은 "호가 없음"이다
예시 1행은 pred_pre_sig 1(상한가), cur_prc +74,800, pred_pre +17,200입니다. 전일 종가를 역산하면 57,600원이고 상승률은 29.86% — 상한가와 맞습니다. 이 행의 sel_bid는 "0"입니다. 상한가에는 매도 호가가 없으니 0은 가격이 아니라 호가 없음입니다. 스프레드나 지정가를 이 값으로 계산하면 0원 주문이 나갑니다. 0은 결측으로 바꾸고, 주문 가격은 ka10004 호가에서 다시 받으십시오.
⑤ cur_prc의 마이너스는 하락 표시다
예시 2행 cur_prc "-183500"은 pred_pre_sig 5(하락)와 같이 옵니다. 부호는 등락이지 가격이 아니므로 abs()를 거칩니다. 숫자가 전부 부호 붙은 문자열이라는 점도 같아서, pd.to_numeric 없이 정렬하면 순서가 깨집니다(ka10035 글 ⑤에 실행 결과).
⑥ 명세가 비워 둔 것들
trde_tp1(순매도)일 때netprps_qty부호 — 필드 이름은 순매수량이고 "부호가 포함된 숫자"라고만 돼 있습니다. 공식 예시는 2(순매수)뿐입니다. 첫 호출에서 부호를 확인하고 정렬 방향을 정하십시오.gain_pos_stkcnt— 외국인이 더 살 수 있는 주식 수입니다. ka10008에서는frgnr_limit(외국인한도)·limit_exh_rt(한도소진률)와 같이 나오는 필드라, 외국인 한도 업종이 아닌 종목의 값이 무엇을 뜻하는지는 운영 응답으로 확인이 필요합니다.- 예시 데이터 — 세 행 모두
005930인데 현재가가 74,800·183,500·4,115원입니다. 2행은 하락인데 매수호가 0, 3행은 거래량 0입니다. 형식 확인용 자리표시자이므로 값의 관계는 ④처럼 내부에서 맞는 것만 씁니다. - 몇 종목까지 오는지와
dt0을 장중에 불렀을 때 잠정치인지도 명세에 없습니다.
4. 최소 호출 코드
토큰은 환경변수에서 읽습니다. 연속조회는 공식 예제와 같이 최대 10페이지(MAX_PAGES = 10), 0.2초 간격(REQUEST_DELAY_SECONDS = 0.2)입니다. 허용값을 상수로 막아 ①·②를 코드에서 끝냅니다.
import os, time, requests
import pandas as pd
BASE = "https://api.kiwoom.com" # 모의: https://mockapi.kiwoom.com
TOKEN = os.environ["KIWOOM_TOKEN"]
DT_OK = {"0", "1", "5", "10", "20", "60"} # 명세 허용값 — 그 밖은 호출 전에 거절
NUM = ["netprps_qty", "trde_qty", "gain_pos_stkcnt", "sel_bid", "buy_bid"]
def ka10034(dt="20", trde_tp="2", mrkt_tp="000", stex_tp="1", max_pages=10, delay=0.2):
if str(dt) not in DT_OK:
raise ValueError(f"ka10034 dt={dt} 불가 — {sorted(DT_OK, key=int)}")
body = {"mrkt_tp": mrkt_tp, "trde_tp": trde_tp, "dt": str(dt), "stex_tp": stex_tp}
rows, cont_yn, next_key = [], None, None
for _ in range(max_pages):
h = {"Content-Type": "application/json;charset=UTF-8",
"authorization": f"Bearer {TOKEN}", "api-id": "ka10034"}
if cont_yn == "Y":
h.update({"cont-yn": "Y", "next-key": next_key})
r = requests.post(BASE + "/api/dostk/rkinfo", headers=h, json=body, timeout=10)
j = r.json()
if j.get("return_code") not in (None, 0):
raise RuntimeError(f"ka10034 {j.get('return_code')}: {j.get('return_msg')}")
rows += j.get("for_dt_trde_upper", [])
cont_yn, next_key = r.headers.get("cont-yn"), r.headers.get("next-key")
if cont_yn != "Y":
break
time.sleep(delay)
df = pd.DataFrame(rows)
df[NUM] = df[NUM].apply(pd.to_numeric, errors="coerce")
df[["sel_bid", "buy_bid"]] = df[["sel_bid", "buy_bid"]].abs().replace(0, pd.NA) # 0 = 호가 없음
df["price"] = pd.to_numeric(df["cur_prc"], errors="coerce").abs()
df["approx_krw"] = df["netprps_qty"] * df["price"] # dt가 길수록 오차 큼
df["dt"], df["asof"] = dt, pd.Timestamp.now().strftime("%Y-%m-%d")
return df
마지막 줄의 asof가 ①의 대책입니다. 매일 이 DataFrame을 날짜별 파일로 저장하면 그날부터 이력이 쌓입니다. 오류 코드는 키움 return_code 글, 연속조회 헤더는 cont-yn·next-key 글에 있습니다.
5. 봇에서 쓰는 순서
- 장 마감 뒤
dt5와 20으로 두 번 부르고asof를 붙여 저장한다. - 두 목록에 모두 있는 종목, 5일 쪽에만 새로 들어온 종목을 나눈다 — 짧은 창과 긴 창의 겹침을 보는 용도입니다.
- "며칠째"가 필요하면 ka10035·ka10131로, 정확한 금액이 필요하면 ka10131로 확인한다.
- 한국투자증권 계좌라면 KIS 외국인·기관 순매수 글의 TR을 쓴다.
여러 순위 TR을 연달아 부를 때의 호출 간격은 키움 429 레이트리밋 글을 보십시오. 외국인 수급은 해석이 갈리는 데이터라 이 목록 자체가 매매 근거가 되지는 않습니다. 이 글은 데이터를 정확히 받는 방법을 다루며 특정 종목이나 전략을 권하지 않습니다. 명세는 바뀔 수 있으니 키움 REST API 공식 문서로 현재 값을 확인하십시오.
6. 자주 묻는 질문
ka10034로 3일이나 15일 같은 기간을 조회할 수 있나요?
명세상 안 됩니다. dt에 넣을 수 있는 값은 0(당일)·1(전일)·5·10·20·60일 여섯 개뿐이고 시작일·종료일 필드도 없습니다. 임의 기간이 필요하면 ka10131(dt 0 + strt_dt·end_dt)이나 종목별 일자 데이터(ka10008)를 쓰십시오.
ka10034로 과거 날짜의 외국인 순매수 상위를 받아 백테스트할 수 있나요?
할 수 없습니다. 요청에 기준일이 없어 기간은 항상 호출 시점에서 거꾸로 셉니다. 백테스트용 이력이 필요하면 매일 장 마감 뒤 결과를 직접 저장해 쌓거나, 날짜를 지정할 수 있는 다른 TR로 만들어야 합니다.
ka10034와 ka10035는 무엇이 다른가요?
둘 다 /api/dostk/rkinfo의 외국인 순위 TR입니다. ka10035는 연속 순매매를 D-1·D-2·D-3 수량과 합계로 주고, ka10034는 당일~60일 기간 누적 순매수량 하나(netprps_qty)와 취득가능주식수, 매도·매수호가를 줍니다. ka10034에는 3:순매매 구분이 더 있습니다.
netprps_qty를 금액으로 바꿀 수 있나요?
현재가(cur_prc의 절댓값)를 곱하면 대략적인 금액은 됩니다. 다만 기간이 길수록 그 사이 가격이 움직였으므로 60일 기준에서는 오차가 커집니다. 정확한 금액이 필요하면 금액 구분이 있는 ka10131(amt_qty_tp 0)을 쓰십시오.