AlgoLab Blog · 키움증권 REST · 수급 데이터 · 2026

키움 ka10131 기관·외국인 연속매매 — 함정 6가지

키움 REST · 수급 2026-10-12 · 약 7분 읽기 · 알고랩 AlgoLab
한 줄 요약

ka10131(기관외국인연속매매현황요청)은 코스피·코스닥 종목을 기관·외국인 "연속 순매수" 기준으로 한 번에 받는 키움 REST TR입니다. URL은 POST /api/dostk/frgnistt, 응답 리스트는 orgn_frgnr_cont_trde_prst입니다. 쓰기 전에 알아야 할 것은 여섯 가지입니다 — netslmt_tp가 2(순매수) 고정이라 연속 순매도 목록은 이 TR로 못 받고, mrkt_tp에 전체(000)가 없어 코스피·코스닥을 따로 불러야 하며, amt_qty_tp는 0이 금액입니다. 연속일수 필드 3개는 명세 설명이 "부호가 포함된 숫자" 한 줄뿐이고, 공식 응답 예시는 orgn +1일·frgnr +1일에 합계 +2일로 나와 있어 "둘 다 연속"과는 다른 값일 수 있습니다. (공식 명세 kiwoom_api_spec.json·예제 2026-10-12 main 기준)

목차

  1. 스펙 한 장 — 요청 8개, 응답 19개
  2. 요청·응답 실물
  3. 함정 6가지
  4. 최소 호출 코드 — 두 시장 합치기
  5. 봇에서 쓰는 법
  6. 자주 묻는 질문

1. 스펙 한 장 — 요청 8개, 응답 19개

어제 정리한 ka10059 투자자별 매매가 "종목 하나의 날짜별 수급"이라면, ka10131은 반대 방향입니다. 시장 전체에서 기관·외국인이 며칠째 사고 있는 종목을 목록으로 줍니다. 수급 필터를 만들 때 후보 종목을 먼저 좁히는 용도입니다.

항목값 (명세 원문)
API IDka10131 기관외국인연속매매현황요청
메뉴국내주식 > 기관/외국인
URLPOST /api/dostk/frgnistt — 운영 https://api.kiwoom.com · 모의 https://mockapi.kiwoom.com
같은 URL을 쓰는 TRka10008 주식외국인종목별매매동향 · ka52301 금현물투자자현황 — api-id 헤더로만 구분
연속조회요청·응답 헤더 cont-yn · next-key

요청 body는 8개입니다.

element한글명필수설명 원문
dt기간Y1:최근일, 3:3일, 5:5일, 10:10일, 20:20일, 120:120일, 0:시작일자/종료일자로 조회
strt_dt시작일자NYYYYMMDD
end_dt종료일자NYYYYMMDD
mrkt_tp장구분Y001:코스피, 101:코스닥
netslmt_tp순매도수구분Y2:순매수(고정값)
stk_inds_tp종목업종구분Y0:종목(주식),1:업종
amt_qty_tp금액수량구분Y0:금액, 1:수량
stex_tp거래소구분Y1:KRX, 2:NXT, 3:통합

응답은 리스트 하나에 19개 필드입니다. 기관(orgn_)·외국인(frgnr_)·합계(tot_) 세 묶음이 각각 "기간 순매매"와 "연속 순매수"를 갖는 구조입니다.

묶음필드단위 (명세)
공통rank · stk_cd(6) · stk_nm(40) · prid_stkpc_flu_rt 기간중주가등락률등락률은 %, 부호 포함 소수 둘째 자리
기관계orgn_nettrde_amt · orgn_nettrde_qty · orgn_cont_netprps_dys · orgn_cont_netprps_qty · orgn_cont_netprps_amt금액 백만원 · 수량 1주 · 일수 "부호가 포함된 숫자"
외국인frgnr_nettrde_qty · frgnr_nettrde_amt · frgnr_cont_netprps_dys · frgnr_cont_netprps_qty · frgnr_cont_netprps_amt위와 같음
합계nettrde_qty · nettrde_amt · tot_cont_netprps_dys · tot_cont_nettrde_qty · tot_cont_netprps_amt위와 같음

필드명이 묶음마다 조금씩 어긋나 있다는 점도 미리 봐 두십시오. 기관은 orgn_nettrde_amt인데 외국인은 frgnr_nettrde_amt, 합계는 접두사 없이 nettrde_amt이고, 합계 연속 수량만 tot_cont_nettrde_qty(netprps가 아니라 nettrde)입니다. 문자열 패턴으로 컬럼을 자동 매핑하면 하나가 빠집니다.

2. 요청·응답 실물

명세의 request_example 원문입니다. 공식 예제 examples/국내주식/기관_외국인/get_domestic_institution_foreign_investor_trade_streak.py의 __main__도 같은 값을 씁니다.

{
    "dt": "1",
    "strt_dt": "",
    "end_dt": "",
    "mrkt_tp": "001",
    "netslmt_tp": "2",
    "stk_inds_tp": "0",
    "amt_qty_tp": "0",
    "stex_tp": "1"
}

명세의 response_example 원문(첫 행)입니다. 아래 3장에서 이 예시의 이상한 점을 짚습니다.

{
    "orgn_frgnr_cont_trde_prst": [
        {
            "rank": "1",
            "stk_cd": "005930",
            "stk_nm": "삼성전자",
            "prid_stkpc_flu_rt": "-5.80",
            "orgn_nettrde_amt": "+48",
            "orgn_nettrde_qty": "+173",
            "orgn_cont_netprps_dys": "+1",
            "orgn_cont_netprps_qty": "+173",
            "orgn_cont_netprps_amt": "+48",
            "frgnr_nettrde_qty": "+0",
            "frgnr_nettrde_amt": "+0",
            "frgnr_cont_netprps_dys": "+1",
            "frgnr_cont_netprps_qty": "+1",
            "frgnr_cont_netprps_amt": "+0",
            "nettrde_qty": "+173",
            "nettrde_amt": "+48",
            "tot_cont_netprps_dys": "+2",
            "tot_cont_nettrde_qty": "+174",
            "tot_cont_netprps_amt": "+48"
        },
        ...
    ],
    "return_code": 0,
    "return_msg": "정상적으로 처리되었습니다"
}

3. 함정 6가지

① 연속 순매도는 이 TR로 못 받는다

netslmt_tp는 이름만 보면 순매도·순매수를 고르는 값 같지만 설명은 "2:순매수(고정값)" 하나뿐입니다. "외국인이 5일째 팔고 있는 종목은 매수 후보에서 뺀다" 같은 필터는 ka10131로 만들 수 없습니다. 외국인에 한해서는 순위정보의 ka10035(외인연속순매매상위요청)(/api/dostk/rkinfo)에 trde_tp 1:연속순매도, 2:연속순매수가 있습니다. 다만 응답이 dm1·dm2·dm3(D-1~D-3)·tot 구조라 며칠째인지 세는 방식이 다르고, 기관 쪽 연속 순매도는 이 TR에도 없습니다. 순위 TR의 경로 차이는 키움 순위조회 글에 정리했습니다.

② 시장 '전체'가 없다 — 두 번 불러야 한다

mrkt_tp는 001(코스피)·101(코스닥) 두 값뿐입니다. ka10035에는 있는 000:전체가 여기는 없습니다. 전 종목 후보를 만들려면 시장별로 따로 받아 합쳐야 하고, 그러면 rank는 시장 안에서의 순위라 합친 뒤에는 의미가 없어집니다. 4장 코드는 market 열을 붙여 합치고 정렬은 직접 합니다.

③ amt_qty_tp는 0이 금액

ka10059·ka10061은 1:금액, 2:수량인데 ka10131은 0:금액, 1:수량입니다. 형제 TR에서 쓰던 "1"을 그대로 넣으면 오류 없이 수량 기준 결과가 옵니다. 이 TR은 응답 필드가 금액(_amt)·수량(_qty)으로 나뉘어 있어 그나마 눈에 띄지만, 목록의 순서가 금액 기준이냐 수량 기준이냐에 따라 달라질 수 있다는 점은 남습니다(정렬 기준은 명세에 없습니다). TR별 코드 차이 전체 표는 ka10059 글 3장에 있습니다.

④ 연속일수 3개 필드의 뜻이 명세에 없다

orgn_cont_netprps_dys·frgnr_cont_netprps_dys·tot_cont_netprps_dys의 설명은 모두 "부호가 포함된 숫자"입니다. 순매수 고정 TR인데 부호가 있다는 것은 음수가 올 수 있다는 뜻으로 읽히지만, 음수가 "연속 순매도 일수"인지는 명세 어디에도 없습니다. 더 걸리는 것은 합계입니다. 공식 예시에서 기관 +1, 외국인 +1일 때 합계가 +2이고, 합계 연속 수량도 173 + 1 = 174로 두 값을 더한 숫자입니다.

"합계 연속 2일"을 "기관·외국인이 2일 연속 함께 샀다"로 읽으면 안 된다 — 예시만 보면 합계 일수는 두 일수를 더한 값처럼 보입니다. 그렇다면 기관 3일 + 외국인 0일도 합계 3일이 됩니다. "기관과 외국인이 같은 날 동시에 순매수한 연속 일수"가 필요하다면 이 필드에 기대지 말고, 후보 종목을 ka10059로 날짜별로 받아 직접 세는 편이 확실합니다. 예시값이 실제 서버 계산과 같은지는 실계좌 응답으로 한 번 대조해 보십시오.

⑤ 공식 응답 예시는 형식만 믿는다

위 예시에는 앞뒤가 맞지 않는 곳이 있습니다. 외국인 순매매량 frgnr_nettrde_qty는 +0인데 외국인 연속 순매수 일수는 +1, 연속 수량은 +1입니다. 또 예시의 1위와 2위가 둘 다 005930 삼성전자입니다. 필드 이름과 문자열 형식(부호가 붙은 숫자 문자열)을 확인하는 용도로만 쓰고, 값의 관계로 로직을 짜는 근거로 쓰지 않는 것이 안전합니다. 같은 이유로 dt(기간)를 1로 줄 때와 20으로 줄 때 연속일수 값이 달라지는지도 명세에 없습니다 — 처음 붙일 때 두 값으로 한 번씩 불러 비교해 두십시오.

⑥ 숫자가 전부 부호 붙은 문자열이다

모든 숫자가 "+48", "-5.80"처럼 옵니다. 공식 예제의 _format_display_value는 화면 표시용이라 6자리 이상 숫자에만 천 단위 쉼표를 넣고 "+"로 시작하는 값은 손대지 않습니다. 계산에 쓰려면 직접 변환해야 합니다. pd.to_numeric은 "+48"을 48.0으로 읽지만, errors="coerce"를 주면 빈 문자열이나 쉼표가 섞인 값은 조용히 NaN이 됩니다.

>>> pd.to_numeric(pd.Series(['+48', '-5.80', '+0', '', '+1,234']), errors='coerce').tolist()
[48.0, -5.8, 0.0, nan, nan]

변환 뒤 isna().sum()을 로그로 남겨 두면 형식이 바뀌었을 때 바로 알 수 있습니다. 응답 return_code가 0이 아닐 때의 처리는 키움 return_code 글을 보십시오.

ka10131 × 2회 001 코스피 · 101 코스닥 정규화 · 1차 필터 부호 문자열 → 숫자, 단위 통일 ka10059 종목별 '동시 연속'은 직접 센다 순매수만 · 전체(000) 없음 합계 일수 = 합산값일 수 있음 amt_qty_tp 1이 금액 넓게 받아서(ka10131) 좁게 확인한다(ka10059)
ka10131은 후보 목록용, 판정은 종목별 TR로

4. 최소 호출 코드 — 두 시장 합치기

토큰은 환경변수에서 읽고 코드에 앱키를 쓰지 않습니다. 연속조회는 공식 예제와 같이 최대 10페이지, 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"]
NUM   = ["prid_stkpc_flu_rt",
         "orgn_nettrde_amt", "orgn_cont_netprps_dys", "orgn_cont_netprps_amt",
         "frgnr_nettrde_amt", "frgnr_cont_netprps_dys", "frgnr_cont_netprps_amt",
         "nettrde_amt", "tot_cont_netprps_dys", "tot_cont_netprps_amt"]

def ka10131(mrkt_tp, dt="5", stex_tp="1", max_pages=10, delay=0.2):
    body = {"dt": dt, "strt_dt": "", "end_dt": "", "mrkt_tp": mrkt_tp,
            "netslmt_tp": "2",                   # 고정값
            "stk_inds_tp": "0", "amt_qty_tp": "0",   # 0 = 금액 (ka10059와 반대)
            "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": "ka10131"}
        if cont_yn == "Y":
            h.update({"cont-yn": "Y", "next-key": next_key})
        r = requests.post(BASE + "/api/dostk/frgnistt", headers=h, json=body, timeout=10)
        j = r.json()
        if j.get("return_code") not in (None, 0):
            raise RuntimeError(f"ka10131 {j.get('return_code')}: {j.get('return_msg')}")
        rows += j.get("orgn_frgnr_cont_trde_prst", [])
        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["market"] = {"001": "KOSPI", "101": "KOSDAQ"}[mrkt_tp]
    return df

df = pd.concat([ka10131("001"), ka10131("101")], ignore_index=True)
df[NUM] = df[NUM].apply(pd.to_numeric, errors="coerce")
print("NaN after parse:", int(df[NUM].isna().sum().sum()))

stex_tp는 필수입니다. ka10059처럼 종목코드에 _NX·_AL을 붙이는 방식이 아니라 별도 파라미터로 KRX(1)·NXT(2)·통합(3)을 고릅니다. 같은 봇 안에서 두 방식이 섞이면 어느 거래소 기준 수급인지 헷갈리기 쉬우니 로그에 남겨 두십시오.

5. 봇에서 쓰는 법

ka10131은 매수 신호가 아니라 후보를 좁히는 체로 씁니다. 예를 들면 이런 순서입니다.

  1. 장 마감 후 ka10131을 코스피·코스닥 두 번 호출해 후보를 받는다.
  2. frgnr_cont_netprps_dys ≥ 3, frgnr_cont_netprps_amt(백만원) ≥ 기준값으로 1차 필터.
  3. 남은 종목만 ka10059로 날짜별 기관·외국인 순매수를 받아 "같은 날 동시 순매수" 일수를 직접 센다. 기간 합계로 "둘 다 산 종목" 순위만 필요하면 ka10062(동일순매매순위)가 한 번에 줍니다.
  4. 관리종목·거래정지 같은 제외 조건, 가격·거래대금 조건과 합쳐 다음 날 매매 대상 목록을 만든다.

장 마감 후에 부르는 것을 기본으로 둡니다. ka10131 명세에는 장중 값이 확정치인지에 대한 설명이 없고, 한국투자증권 쪽 같은 종류의 데이터가 장중에는 가집계라는 점은 KIS 외국인 순매수 글에 정리했고, 체결강도·거래원·프로그램매매와 묶은 필터 설계는 수급 필터 글에 있습니다. 키움 REST 전반은 키움 REST API 자동매매 가이드에서 시작하십시오.

수급 연속성은 예측 지표가 아니다 — 기관·외국인이 며칠째 사고 있다는 사실이 다음 날 주가 방향을 알려 주지는 않습니다. 이 글은 데이터를 정확히 받는 방법을 다루며, 특정 종목이나 수급 전략의 수익을 시사하지 않습니다. 필터를 쓰기 전에는 백테스트로 검증하고, 과거 성과가 미래를 보장하지 않는다는 점을 전제로 하십시오.

6. 자주 묻는 질문

ka10131로 외국인 연속 순매도 종목도 받을 수 있나요?

받을 수 없습니다. netslmt_tp가 2(순매수) 고정값입니다. 외국인 연속 순매도는 순위정보 TR ka10035(외인연속순매매상위요청)의 trde_tp 1로 받을 수 있고, 기관 연속 순매도는 이 두 TR에 없으므로 종목별 TR로 직접 세야 합니다.

tot_cont_netprps_dys(합계연속순매수일수)는 무슨 뜻인가요?

명세 설명은 "부호가 포함된 숫자"뿐입니다. 공식 응답 예시에서는 기관 +1일, 외국인 +1일일 때 합계가 +2일이어서 두 값을 더한 숫자로 보입니다. 기관과 외국인이 같은 날 함께 순매수한 연속 일수가 필요하다면 ka10059로 날짜별 데이터를 받아 직접 세는 것이 확실합니다.

코스피와 코스닥을 한 번에 조회할 수 있나요?

없습니다. mrkt_tp는 001(코스피)과 101(코스닥) 두 값뿐이라 두 번 호출해 합쳐야 합니다. 합친 뒤에는 rank가 시장 안 순위라 의미가 없으므로 필요한 기준으로 직접 정렬합니다.

금액 단위는 무엇인가요?

명세상 금액 필드(orgn_nettrde_amt, frgnr_cont_netprps_amt 등)는 백만원, 수량 필드는 1주입니다. 요청의 amt_qty_tp는 0이 금액, 1이 수량으로 ka10059(1이 금액)와 반대이니 주의하십시오.

수급 필터까지 넣은 키움 봇, 만들어 드립니다

원하는 조건만 말로 알려 주시면 됩니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기