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

키움 ka10058 투자자별일별매매종목 — 일별인데 기간 합계다

키움 REST · 종목정보 2026-10-29 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약

ka10058(투자자별일별매매종목요청)은 투자자 한 명(invsr_tp)을 골라 strt_dt~end_dt 동안 그 투자자가 많이 순매수·순매도한 종목을 순위 목록으로 주는 TR입니다. 이름은 "일별"이지만 응답에 날짜 필드가 없고 dt_trde_qty가 "기간거래량"이라, 하루치를 원하면 시작일과 종료일을 같은 날로 넣어야 합니다. trde_tp는 "2"가 순매수("1" 순매도), mrkt_tp에는 전체(000)가 없고, invsr_tp 8000(개인)·3100(사모펀드)은 337개 TR 중 이 TR에만 있습니다. 응답 예시를 검산하면 pre_rt(대비율)는 전일 대비가 아니라 추정평균가 대비이고, netslmt_amt는 명세의 "원"이 아니라 만원 단위에 맞습니다. 날짜를 못 넣는 ka10066 장마감후투자자별매매를 놓친 날, 과거 기간을 메우는 용도로 쓰기 좋습니다. (키움 공식 명세·공식 GitHub 예제 기준, 2026-10-29 확인)

목차

  1. ka10058은 무엇을 주나 — ka10066과 어떻게 나눠 쓰나
  2. 요청·응답 실물
  3. 함정 6가지
  4. 받아서 저장하는 코드
  5. 자주 묻는 질문

1. ka10058은 무엇을 주나 — ka10066과 어떻게 나눠 쓰나

어제 정리한 ka10066은 장 마감 후 시장 전체 종목의 투자자 12개 매매를 한 번에 주지만, 요청에 날짜가 없어 지난 날은 받을 수 없었습니다. ka10058은 반대 방향으로 설계된 TR입니다. 날짜(strt_dt·end_dt)는 넣을 수 있지만 투자자는 한 번에 하나만 고르고, 응답은 투자자별 열이 아니라 "그 투자자가 많이 산(판) 종목" 목록입니다. 메뉴 위치는 국내주식 > 종목정보, URL은 POST /api/dostk/stkinfo로 ka10059 종목별투자자기관별과 같은 경로를 씁니다.

TRURL날짜투자자 선택시장 전체순매수 값
ka10058 투자자별일별매매종목stkinfostrt_dt~end_dt1명 · 4자리 invsr_tp 12종✗ (001·101)"2"
ka10044 일별기관매매종목mrkcondstrt_dt~end_dt없음(기관 고정)✗ (001·101)"2"
ka10066 장마감후투자자별매매mrkcond없음12개 열 전부○ 000"0"
ka10065 장중투자자별매매상위rkinfo없음1명 · 4자리 orgn_tp 11종○ 000"1"
ka10063 장중투자자별매매mrkcond없음1명 · 1자리 invsr 8종○ 000—

구조가 가장 닮은 것은 ka10044(일별기관매매종목)입니다. 요청 필드 다섯 개(strt_dt·end_dt·trde_tp·mrkt_tp·stex_tp)가 같고, ka10058은 여기에 invsr_tp를 하나 더 받아 기관이 아닌 투자자도 고를 수 있게 한 형태입니다. 그런데 메뉴·응답 필드 이름·단위가 전부 다릅니다(3장 ③·④).

ka10066 오늘 하루 · 시장 전체(000) 투자자 12개가 열로 한 번에 날짜 지정 불가 → 놓치면 끝 ka10058 strt_dt ~ end_dt 기간 합계 투자자 1명 · 시장 1개 · 방향 1개 결과 = 종목 순위 목록 하루 전수 = 투자자 12 × 시장 2 × 방향 2 = 48호출 (+연속조회)
ka10066과 ka10058의 역할 분담 · 공식 명세(kiwoom-rest-api main 953e5db) 기준

2. 요청·응답 실물

헤더는 다른 키움 REST TR과 같습니다 — api-id: ka10058, authorization: Bearer ..., 연속조회 시 cont-yn·next-key. body 6개는 모두 필수(required: Y)입니다. 아래는 명세의 요청 예시 그대로입니다.

POST https://api.kiwoom.com/api/dostk/stkinfo
api-id: ka10058
Content-Type: application/json;charset=UTF-8

{
    "strt_dt": "20241106",
    "end_dt": "20241107",
    "trde_tp": "2",
    "mrkt_tp": "101",
    "invsr_tp": "8000",
    "stex_tp": "3"
}
필드한글명값 (명세 원문)
strt_dt · end_dt시작일자 · 종료일자YYYYMMDD (최대 기간 제한은 명세에 없음)
trde_tp매매구분원문 "순매도:1, 순매수:2" → 2가 순매수
mrkt_tp시장구분001 코스피 · 101 코스닥 (000 없음)
invsr_tp투자자구분8000 개인 · 9000 외국인 · 1000 금융투자 · 3000 투신 · 3100 사모펀드 · 5000 기타금융 · 4000 은행 · 2000 보험 · 6000 연기금 · 7000 국가 · 7100 기타법인 · 9999 기관계
stex_tp거래소구분1 KRX · 2 NXT · 3 통합 (원문 "3.통합")

명세의 응답 예시입니다(리스트 키 invsr_daly_trde_stk, 3행 중 2행).

{
    "invsr_daly_trde_stk": [
        {"stk_cd": "005930", "stk_nm": "삼성전자",
         "netslmt_qty": "+4464", "netslmt_amt": "+25467", "prsm_avg_pric": "57056",
         "cur_prc": "+61300", "pre_sig": "2", "pred_pre": "+4000",
         "avg_pric_pre": "+4244", "pre_rt": "+7.43", "dt_trde_qty": "1554171"},
        {"stk_cd": "005930", "stk_nm": "삼성전자",
         "netslmt_qty": "+46", "netslmt_amt": "+75", "prsm_avg_pric": "16320",
         "cur_prc": "15985", "pre_sig": "3", "pred_pre": "0",
         "avg_pric_pre": "--335", "pre_rt": "-2.05", "dt_trde_qty": "4770"}
    ],
    "return_code": 0,
    "return_msg": "정상적으로 처리되었습니다"
}

응답 필드는 11개입니다 — stk_cd 종목코드, stk_nm 종목명, netslmt_qty 순매도수량(1주), netslmt_amt 순매도금액(명세상 원), prsm_avg_pric 추정평균가, cur_prc 현재가, pre_sig 대비기호, pred_pre 전일대비, avg_pric_pre 평균가대비, pre_rt 대비율, dt_trde_qty 기간거래량. 투자자별 열이 아니라 고른 투자자 한 명의 순매매가 종목마다 한 줄입니다.

3. 함정 6가지

① 이름은 "일별"인데 응답은 기간 합계다

API 이름과 개요("투자자별 일별 매매 종목 정보를 조회합니다")만 보면 날짜별 행이 올 것 같지만, 응답 11개 필드 어디에도 dt가 없습니다. 거래량 필드 이름이 dt_trde_qty이고 한글명이 "기간거래량"이라는 점, 예시 요청이 이틀(20241106~20241107)인데 종목당 한 줄씩 온다는 점을 보면 기간 합계를 종목 순위로 주는 구조로 읽는 것이 맞습니다. 날짜별 시계열이 필요하면 strt_dt=end_dt로 하루씩 부르고, 받은 쪽에서 날짜를 붙여야 합니다(4장 코드). 기간 최대 범위는 명세에 없습니다. ka10034 외인기간별매매상위는 기간을 정해진 값으로만 받지만 ka10058은 자유 입력이니, 긴 기간은 실계좌에서 먼저 확인하세요.

② trde_tp는 2가 순매수 — 원문 표기 순서도 거꾸로다

명세 원문이 "순매도:1, 순매수:2"입니다. 337개 TR의 trde_tp 설명 가운데 "값:이름"이 아니라 "이름:값" 순서로 적힌 것은 ka10058 하나뿐이라, 눈으로 훑으면 "1이 순매수"로 잘못 읽기 쉽습니다. 투자자 TR끼리 비교하면 순매수 값이 세 갈래로 갈립니다.

ka10066에서 쓰던 NET_BUY = "0"을 그대로 넣으면 ka10058은 정의되지 않은 값을 받게 됩니다. ka10065의 "1"을 가져오면 오류 없이 순매도 순위가 옵니다. ka10062 동일순매매순위도 "1"이 순매수이므로, 상수는 TR 이름을 붙여 따로 두는 것이 안전합니다.

③ 필드 이름은 "순매도"인데 순매수 요청의 값은 +

응답 필드가 netslmt_qty(순매도수량)·netslmt_amt(순매도금액)입니다. 그런데 명세 예시 요청은 trde_tp "2"(순매수)이고, 응답 값은 "+4464"처럼 모두 양수입니다. 즉 이 필드는 "순매도"가 아니라 trde_tp로 고른 방향의 순매매로 읽어야 하며, 순매도 요청("1")일 때 부호가 +인지 -인지는 명세가 정하지 않습니다. 337개 TR 중 netslmt_qty·netslmt_amt를 쓰는 것은 ka10058뿐이고, 구조가 같은 ka10044는 같은 자리에 netprps_qty(순매수수량)·netprps_amt를 씁니다. 두 TR 결과를 한 테이블에 합칠 때는 필드명이 아니라 요청한 trde_tp로 방향을 기록하세요.

④ 금액 단위 "원"은 예시 검산과 맞지 않는다

명세는 netslmt_amt를 "단위: 원"으로 적었습니다. 예시 세 행을 수량 × 추정평균가로 검산하면 4,464주 × 57,056원 = 254,697,984원인데 값은 25467이고, 46주 × 16,320원 = 750,720원인데 값은 75입니다. 1 단위가 약 10,001원·9,810원·10,010원으로, 만원 단위에 맞습니다. 같은 구조의 ka10044는 수량 100주·금액 백만원 단위로 적혀 있어 세 TR(ka10044·ka10058·ka10066)이 금액 단위를 모두 다르게 표기합니다. 예시는 테스트값일 수 있으니 단정하지 말고, 첫 실호출에서 같은 비율을 계산해 단위를 확정하는 함수(4장 check_units)를 넣어 두면 됩니다.

⑤ pre_rt(대비율)는 전일 대비가 아니라 추정평균가 대비

예시 세 행 모두 avg_pric_pre = cur_prc − prsm_avg_pric가 정확히 맞고(61,300 − 57,056 = 4,244), pre_rt는 avg_pric_pre ÷ prsm_avg_pric입니다 — 4,244 ÷ 57,056 = 7.438% → +7.43, 13,542 ÷ 86,658 = 15.627% → +15.62, −335 ÷ 16,320 = −2.053% → -2.05. 전일대비(pred_pre 4,000)로 계산한 등락률은 6.98%라 맞지 않습니다. 즉 pre_rt는 "그 투자자가 산 평균가 대비 지금 몇 % 위에 있나"이고, 반올림이 아니라 버림으로 보입니다(7.438이 7.44가 아니라 7.43). 또 셋째 행의 "--335"처럼 음수에 부호가 한 번 더 붙은 이중 부호가 나옵니다. 공식 예제 get_domestic_investor_daily_trade_stocks.py의 표시 함수는 "--335"를 숫자로 인식하지 못해 그대로 두고, int()나 float()에 바로 넣으면 ValueError가 납니다. 이중 부호 예시는 ka10058 외에도 ka10044·ka10061·ka10086 등 12개 TR에 있습니다.

⑥ invsr_tp 4자리 코드는 이 TR에만 있다 — 그리고 시장 전체가 없다

invsr_tp라는 필드 자체가 337개 중 ka10058에만 있고, 8000(개인)·3100(사모펀드)도 이 TR에서만 쓰입니다. 비슷해 보이는 ka10065의 orgn_tp는 같은 4자리 체계지만 개인·사모펀드가 없고 대신 9100(외국계)이 있으며, ka10063의 invsr는 6=외국인·7=기관계 같은 1자리 체계입니다. 코드표를 TR끼리 공유하면 안 됩니다. 또 mrkt_tp는 001·101뿐이라 ka10066·ka10065처럼 000을 넣던 코드는 고쳐야 합니다. 하루 전체를 받으려면 투자자 12 × 시장 2 × 방향 2 = 48호출이고, 페이지마다 cont-yn이 Y면 더 늘어나니 429 호출 제한과 연속조회 next-key 처리를 같이 봐야 합니다. 공식 예제는 MAX_PAGES = 10, REQUEST_DELAY_SECONDS = 0.2입니다. 명세 예시는 코스닥(101) 요청에 코스피 종목 005930이 세 번 나오는 테스트값입니다.

4. 받아서 저장하는 코드

숫자 변환(이중 부호 포함), 응답에 없는 기간·투자자·방향·시장 붙이기, 금액 단위와 pre_rt 기준 검산까지 합니다. 토큰·호출부는 생략했습니다.

import pandas as pd

TRDE_TP = {"순매도": "1", "순매수": "2"}          # ka10058 전용 (ka10066은 "0"이 순매수)
MARKETS = ["001", "101"]                          # 000(전체)이 없다 — 코스피·코스닥 따로
INVSR_TP = {"8000": "개인", "9000": "외국인", "9999": "기관계", "1000": "금융투자",
            "2000": "보험", "3000": "투신", "3100": "사모펀드", "4000": "은행",
            "5000": "기타금융", "6000": "연기금", "7000": "국가", "7100": "기타법인"}
NUM_COLS = ["netslmt_qty", "netslmt_amt", "prsm_avg_pric", "cur_prc",
            "pred_pre", "avg_pric_pre", "pre_rt", "dt_trde_qty"]

def to_num(v):
    s = str(v).strip().replace(",", "")
    neg = s.startswith("-")
    s = s.lstrip("+-")                            # "--335" 같은 이중 부호도 한 번에
    return -float(s or 0) if neg else float(s or 0)

def parse_ka10058(rows, strt_dt, end_dt, invsr_tp, trde_tp, mrkt_tp):
    """rows = 모든 페이지의 invsr_daly_trde_stk를 이어 붙인 것"""
    df = pd.DataFrame(rows)
    if df.empty:
        return df
    for c in NUM_COLS:
        df[c] = df[c].map(to_num)
    df["cur_prc"] = df["cur_prc"].abs()           # 부호는 전일 대비 방향
    # 응답에 날짜가 없다 — 요청한 기간과 조건을 직접 붙인다
    df.insert(0, "strt_dt", strt_dt)
    df.insert(1, "end_dt", end_dt)
    df.insert(2, "invsr", INVSR_TP[invsr_tp])
    df.insert(3, "side", {v: k for k, v in TRDE_TP.items()}[trde_tp])
    df.insert(4, "mrkt_tp", mrkt_tp)
    return df

def check_units(df):
    """금액 1 단위가 몇 원인지 수량 x 추정평균가로 역산한다"""
    ok = df[df["netslmt_amt"] != 0]
    ratio = (ok["netslmt_qty"].abs() * ok["prsm_avg_pric"]) / ok["netslmt_amt"].abs()
    print(f"금액 1 = 약 {ratio.median():,.0f}원 (행별 {', '.join(f'{r:,.0f}' for r in ratio)})")
    base = (df["avg_pric_pre"] / df["prsm_avg_pric"] * 100)
    print("pre_rt 기준 = 추정평균가:", bool(((base - df["pre_rt"]).abs() < 0.01).all()))

명세 응답 예시(3행)를 parse_ka10058(rows, "20241106", "20241107", "8000", "2", "101")에 넣고 check_units까지 돌린 출력입니다.

    strt_dt    end_dt invsr side  stk_cd  netslmt_qty  netslmt_amt  prsm_avg_pric  avg_pric_pre  pre_rt
0  20241106  20241107    개인  순매수  005930       4464.0      25467.0        57056.0        4244.0    7.43
1  20241106  20241107    개인  순매수  005930         12.0        106.0        86658.0       13542.0   15.62
2  20241106  20241107    개인  순매수  005930         46.0         75.0        16320.0        -335.0   -2.05
금액 1 = 약 10,001원 (행별 10,001, 9,810, 10,010)
pre_rt 기준 = 추정평균가: True
중복 종목코드: ['005930']

실계좌에서는 장 마감 뒤 strt_dt=end_dt=오늘로 필요한 투자자만 부르고, (strt_dt, end_dt, invsr, side, mrkt_tp, stk_cd)를 키로 저장하면 됩니다. 휴장일에 부르지 않도록 휴장일 판정을 앞에 두세요.

5. 자주 묻는 질문

ka10058로 하루치 투자자별 매매만 받으려면 어떻게 하나요?

strt_dt와 end_dt에 같은 날짜를 넣으면 됩니다. 두 값을 다르게 주면 그 기간의 합계가 종목별로 한 줄씩 오고, 응답에는 날짜 필드가 없으므로 요청한 기간을 직접 붙여 저장해야 합니다.

ka10058의 trde_tp는 몇이 순매수인가요?

2가 순매수, 1이 순매도입니다. 같은 투자자 데이터라도 ka10066·ka10059는 0이 순매수, ka10065는 1이 순매수라서 상수를 TR마다 따로 두는 것이 안전합니다.

ka10058로 개인 순매수 종목을 받을 수 있나요?

받을 수 있습니다. invsr_tp에 8000을 넣으면 개인입니다. 공식 명세 337개 TR 가운데 8000(개인)과 3100(사모펀드) 코드를 쓰는 것은 ka10058 하나뿐이고, 비슷한 4자리 코드를 쓰는 ka10065에는 개인이 없습니다.

ka10058로 코스피와 코스닥을 한 번에 받을 수 있나요?

없습니다. mrkt_tp 값이 001(코스피)과 101(코스닥) 두 개뿐이고 000(전체)이 없습니다. ka10066·ka10065처럼 000을 넣던 코드를 그대로 쓰지 말고 시장마다 따로 호출하세요.

ka10058의 netslmt_amt는 원 단위인가요?

명세에는 원으로 적혀 있지만 응답 예시를 수량과 추정평균가로 검산하면 만원 단위에 맞습니다. 예시가 테스트값일 수 있으니 실계좌 첫 호출에서 netslmt_qty × prsm_avg_pric과 netslmt_amt의 비율로 확인하세요.

투자자별 수급을 쌓는 봇, 만들어 드립니다

ka10058·ka10066·ka10059로 개인·외국인·기관 순매매를 매일 받아 저장하고, 매매 필터로 쓰는 키움 REST 봇을 맞춤 제작합니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기