키움 ka10035 외인연속순매매상위 — tot는 3일 합계다
ka10035(외인연속순매매상위요청)는 "외국인이 며칠째 사고 있나"를 알려 주지 않습니다. 응답 리스트 for_cont_nettrde_upper에는 dm1·dm2·dm3(D-1~D-3 순매매 수량)와 tot(합계)만 있고, 공식 응답 예시 4행 모두 tot = dm1+dm2+dm3였습니다. 대신 ka10131에 없는 두 가지가 있습니다 — 연속 순매도(trde_tp 1)와 시장 전체(mrkt_tp 000). 주의할 점은 trde_tp 방향이 같은 /api/dostk/rkinfo의 ka10037과 반대라는 것, 숫자가 부호 붙은 문자열이라 그대로 정렬하면 순매도 종목이 맨 위로 온다는 것입니다. (공식 명세 kiwoom_api_spec.json·예제, 2026-10-14 main 기준)
목차
- ka10131과 무엇이 다른가 — 한 장 비교
- 요청·응답 실물
- 함정 6가지
- 최소 호출 코드
- 봇에서 쓰는 순서
- 자주 묻는 질문
1. ka10131과 무엇이 다른가 — 한 장 비교
그제 정리한 ka10131 기관·외국인 연속매매에는 큰 빈칸이 둘 있었습니다. 연속 순매도 목록을 못 받고, 코스피·코스닥을 따로 불러야 합니다. 외국인에 한해서는 순위정보 메뉴의 ka10035가 그 빈칸을 메웁니다. 대신 잃는 것도 있습니다.
| 항목 | ka10035 외인연속순매매상위 | ka10131 기관외국인연속매매현황 |
|---|---|---|
| 메뉴 / URL | 순위정보 · POST /api/dostk/rkinfo | 기관/외국인 · POST /api/dostk/frgnistt |
| 주체 | 외국인만 | 기관계 · 외국인 · 합계 |
| 방향 | trde_tp 1:연속순매도, 2:연속순매수 | netslmt_tp 2:순매수(고정값) |
| 시장 | mrkt_tp 000:전체, 001:코스피, 101:코스닥 | 001·101뿐 |
| 기간 | base_dt_tp 0:당일기준, 1:전일기준 | dt 1·3·5·10·20·120일 또는 시작/종료일 |
| 연속일수 | 없음 — D-1·D-2·D-3·합계 | frgnr_cont_netprps_dys 등 3개 |
| 단위 | 수량(1주)뿐 | 금액(백만원)·수량 둘 다 |
| 거래소 | stex_tp 1:KRX, 2:NXT, 3:통합 (둘 다 필수) | |
요약하면 ka10035는 "최근 사흘 외국인 방향"을 시장 전체에서 한 번에 보는 TR이고, ka10131은 "며칠째, 얼마어치"를 시장별로 보는 TR입니다. 둘을 같이 쓰는 이유가 여기 있습니다.
2. 요청·응답 실물
요청 body는 네 개가 전부이며 모두 필수입니다. 명세의 request_example 원문이고, 공식 예제 examples/국내주식/순위정보/get_domestic_foreign_investor_net_buy_streak_top.py의 __main__도 같은 값을 씁니다.
{
"mrkt_tp": "000",
"trde_tp": "2",
"base_dt_tp": "1",
"stex_tp": "1"
}
헤더는 다른 키움 REST TR과 같습니다. api-id: ka10035, authorization: Bearer …, 연속조회 시 cont-yn·next-key. 응답은 리스트 하나에 13개 필드입니다. 명세의 response_example 첫 두 행입니다.
{
"for_cont_nettrde_upper": [
{
"stk_cd": "005930",
"stk_nm": "삼성전자",
"cur_prc": "10200",
"pred_pre_sig": "3",
"pred_pre": "0",
"dm1": "+33928250",
"dm2": "+234840",
"dm3": "+233891",
"tot": "+34396981",
"limit_exh_rt": "+71.53",
"pred_pre_1": "",
"pred_pre_2": "",
"pred_pre_3": ""
},
{
"stk_cd": "005930",
"stk_nm": "삼성전자",
"cur_prc": "-8540",
"pred_pre_sig": "5",
"pred_pre": "-140",
"dm1": "+4033818",
"dm2": "+12474308",
"dm3": "+13173262",
"tot": "+29681388",
"limit_exh_rt": "+0.10",
...
},
...
],
"return_code": 0,
"return_msg": "정상적으로 처리되었습니다"
}
| element | 한글명 | 명세 설명 |
|---|---|---|
stk_cd · stk_nm | 종목코드 · 종목명 | — |
cur_prc | 현재가 | 단위: 원, 부호가 포함된 숫자 |
pred_pre_sig | 전일대비기호 | 1:상한가, 2:상승, 3:보합, 4:하한가, 5:하락 |
pred_pre | 전일대비 | 단위: 원, 부호가 포함된 숫자 |
dm1 · dm2 · dm3 | D-1 · D-2 · D-3 | 단위: 1주, 부호가 포함된 숫자 |
tot | 합계 | 단위: 1주, 부호가 포함된 숫자 |
limit_exh_rt | 한도소진율 | 단위: %, 부호 포함 소수점 둘째 자리 |
pred_pre_1~3 | 전일대비1~3 | 설명 없음 (예시는 빈 문자열) |
3. 함정 6가지
① 연속일수가 없다 — tot는 사흘치 합계
TR 이름에 "연속"이 들어 있지만 응답에는 며칠째인지 나타내는 필드가 없습니다. tot의 한글명은 "합계"이고, 공식 예시 4행을 직접 더해 보면 전부 세 값의 합과 같습니다.
>>> n = df[["dm1","dm2","dm3","tot"]].apply(pd.to_numeric)
>>> (n.dm1 + n.dm2 + n.dm3 == n.tot).tolist()
[True, True, True, True]
그러니 tot가 큰 종목은 "오래 산 종목"이 아니라 최근 사흘 동안 많이 산 종목입니다. 예시 첫 행처럼 하루(dm1 3,392만 주)가 합계의 99%를 차지하는 경우도 그대로 상위에 옵니다. "5일 이상 연속 순매수" 같은 조건은 이 TR만으로는 만들 수 없고, ka10131의 frgnr_cont_netprps_dys나 종목별 일자 데이터(ka10059)로 직접 세야 합니다. 목록이 어떤 기준으로 정렬돼 오는지, 몇 종목까지 오는지도 명세에 없습니다.
② trde_tp 방향이 옆 TR과 반대다
같은 /api/dostk/rkinfo 경로에 외국인 관련 순위 TR이 셋 있는데, 매매구분 코드의 뜻이 서로 다릅니다.
| TR | 필드 | 1 | 2 | 3·4 |
|---|---|---|---|---|
| ka10035 외인연속순매매상위 | trde_tp | 연속순매도 | 연속순매수 | — |
| ka10034 외인기간별매매상위 | trde_tp | 순매도 | 순매수 | 3:순매매 |
| ka10037 외국계창구매매상위 | trde_tp | 순매수 | 순매도 | 3:매수, 4:매도 |
ka10037 요청을 복사해 api-id만 ka10035로 바꾸면 trde_tp: "1"이 그대로 남아 순매수를 원했는데 연속 순매도 목록이 옵니다. 오류는 나지 않습니다. 같은 경로에서 api-id로만 TR을 구분하는 구조의 다른 함정은 키움 순위조회 rkinfo 글에 모아 두었습니다. 방향 코드는 상수로 박고 TR별로 따로 두는 편이 안전합니다.
③ 수량뿐이라 저가주가 위로 온다
dm1~tot의 단위는 전부 "1주"입니다. 금액 필드가 없습니다. 1주 1만 원 종목 100만 주와 1주 50만 원 종목 2만 주는 같은 100억 원이지만 tot로 줄을 세우면 앞쪽이 50배 크게 보입니다. 종목 간 비교가 필요하면 cur_prc를 곱해 대략적인 금액으로 바꾸십시오. 사흘 동안 가격이 움직였으므로 정확한 금액은 아니고, 정확한 금액이 필요하면 ka10131(amt_qty_tp 0:금액)을 씁니다.
④ cur_prc의 마이너스는 가격이 아니다
예시 두 번째 행의 cur_prc는 "-8540"입니다. 주가가 음수일 수는 없으니 이 부호는 전일 대비 하락 표시입니다(같은 행의 pred_pre_sig가 5:하락, pred_pre가 −140). 금액 환산이나 주문 가격에 쓸 때는 반드시 abs()를 거칩니다. 키움 REST 시세 응답 전반에 있는 습관이라 키움 REST API 자동매매 가이드의 응답 정규화 함수에 한 번에 넣어 두는 것이 좋습니다.
⑤ 문자열 그대로 정렬하면 순매도가 1등
공식 예제의 NUMERIC_COLUMNS는 '한도소진율'과 '현재가' 두 개뿐이고, 이것도 화면 표시용 쉼표만 넣습니다. dm1~tot는 "+33928250" 같은 문자열로 남습니다. 이 상태로 sort_values를 하면 이렇게 됩니다(pandas 2.2.3).
>>> s = pd.Series(['+4033818', '+33928250', '+234840', '-1200000'])
>>> s.sort_values(ascending=False).tolist()
['-1200000', '+4033818', '+33928250', '+234840']
>>> pd.to_numeric(s).sort_values(ascending=False).tolist()
[33928250, 4033818, 234840, -1200000]
문자 코드상 -가 +보다 뒤라서 내림차순 맨 위에 순매도 종목이 오고, 나머지도 자릿수와 무관하게 첫 숫자로 줄을 섭니다. "상위 5개만 매수 후보"로 자르면 엉뚱한 종목이 들어갑니다. 변환은 pd.to_numeric으로 하고, 변환 뒤 isna().sum()을 로그로 남겨 두면 형식이 바뀌었을 때 바로 알 수 있습니다.
⑥ 명세가 비워 둔 것들
base_dt_tp0(당일기준) — 장중에 부르면 D-1이 오늘 잠정치인지 어제 확정치인지 설명이 없습니다. 외국인 순매매는 장중 잠정치와 장 마감 뒤 확정치가 다를 수 있으니, 신호 계산은 장 마감 뒤base_dt_tp1로 하고 두 값을 처음 한 번 비교해 두십시오.pred_pre_1~3— 한글명은 "전일대비1~3"이지만 설명이 없고 예시에서도 빈 문자열입니다. 로직에 쓰지 않습니다.- 예시 데이터 — 4행이 전부
005930삼성전자인데 현재가가 10,200원·8,540원·23,000원·195,800원으로 제각각입니다. 형식 확인용 자리표시자이므로 값의 관계는 믿지 않습니다.
4. 최소 호출 코드
토큰은 환경변수에서 읽고 코드에 앱키를 쓰지 않습니다. 연속조회는 공식 예제와 같이 최대 10페이지(MAX_PAGES = 10), 0.2초 간격(REQUEST_DELAY_SECONDS = 0.2)으로 받습니다. 헤더 처리는 cont-yn·next-key 글과 같습니다.
import os, time, requests
import pandas as pd
BASE = "https://api.kiwoom.com" # 모의: https://mockapi.kiwoom.com
TOKEN = os.environ["KIWOOM_TOKEN"]
SELL_STREAK, BUY_STREAK = "1", "2" # ka10035 전용 — ka10037과 반대
NUM = ["dm1", "dm2", "dm3", "tot", "limit_exh_rt"]
def ka10035(trde_tp=BUY_STREAK, mrkt_tp="000", base_dt_tp="1", stex_tp="1",
max_pages=10, delay=0.2):
body = {"mrkt_tp": mrkt_tp, "trde_tp": trde_tp,
"base_dt_tp": base_dt_tp, "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": "ka10035"}
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"ka10035 {j.get('return_code')}: {j.get('return_msg')}")
rows += j.get("for_cont_nettrde_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["price"] = pd.to_numeric(df["cur_prc"], errors="coerce").abs() # 부호 = 등락 표시
df["approx_krw"] = df["tot"] * df["price"] # 사흘치 대략 금액
return df
buy = ka10035(BUY_STREAK).sort_values("approx_krw", ascending=False)
print("NaN after parse:", int(buy[NUM].isna().sum().sum()))
return_code가 0이 아닐 때의 의미는 키움 return_code 글에, 호출이 몰렸을 때의 429 처리는 키움 429 레이트리밋 글에 있습니다.
5. 봇에서 쓰는 순서
ka10035는 매수 신호가 아니라 제외 목록과 후보 목록을 같이 만드는 체로 쓰는 것이 자연스럽습니다.
- 장 마감 뒤 ka10035를
trde_tp1(연속순매도)로 한 번 불러 제외 목록을 만든다 — ka10131로는 못 하는 부분입니다. - 같은 TR을
trde_tp2로 불러approx_krw기준 후보를 추린다. 시장 전체(000)라 한 번이면 됩니다. - 후보 중 "며칠째"가 필요한 종목만 ka10131(
frgnr_cont_netprps_dys)이나 ka10059로 확인한다. - 한국투자증권 계좌라면 같은 역할을 하는 KIS 쪽 TR이 다르니 KIS 외국인·기관 순매수 글을 보십시오.
외국인 수급은 해석이 갈리는 데이터라 이 목록 자체가 매매 근거가 되지는 않습니다. 이 글은 데이터를 정확히 받는 방법을 다루며 특정 종목이나 전략을 권하지 않습니다.
6. 자주 묻는 질문
ka10035로 외국인이 며칠째 연속 순매수했는지 알 수 있나요?
명세상으로는 알 수 없습니다. 응답에 연속일수 필드가 없고 D-1·D-2·D-3 세 날의 수량과 합계 tot만 있습니다. 공식 응답 예시 4행은 모두 tot가 세 값의 합과 정확히 같았습니다. 4일 이상 연속인지 세려면 ka10131의 frgnr_cont_netprps_dys를 쓰거나 종목별 일자 데이터로 직접 세야 합니다.
ka10035와 ka10131은 무엇이 다른가요?
ka10035는 순위정보(/api/dostk/rkinfo)에 있고 외국인만, 연속 순매도·순매수 둘 다, 시장 전체(000) 조회가 됩니다. 단위는 수량(1주)뿐입니다. ka10131은 기관/외국인(/api/dostk/frgnistt)에 있고 기관·외국인·합계를 다 주지만 순매수 고정이며 시장 전체가 없습니다. 연속일수와 금액은 ka10131에만 있습니다.
trde_tp에 1을 넣으면 순매수인가요?
ka10035에서는 아닙니다. 1이 연속순매도, 2가 연속순매수입니다. 같은 순위 경로의 ka10037(외국계창구매매상위)은 반대로 1이 순매수, 2가 순매도입니다. 요청 body를 복사해 api-id만 바꾸면 오류 없이 반대 방향 목록이 옵니다.
모의투자 서버에서도 ka10035를 호출할 수 있나요?
명세에는 모의투자 도메인 https://mockapi.kiwoom.com이 운영 도메인과 함께 적혀 있습니다. 다만 모의 서버의 순위 데이터가 운영과 같은지는 명세에 없으므로, 필터 기준값을 정할 때는 운영 응답으로 한 번 확인하는 것이 안전합니다.
외국인 수급 필터를 넣은 키움 봇, 만들어 드립니다
원하는 조건만 말로 알려 주시면 TR 선택과 데이터 정규화까지 맞춰 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기