키움 ka10078 증권사별종목매매동향 — 금액이 없다
ka10078(증권사별종목매매동향요청)은 "회원사 1곳 × 종목 1개"를 정해 strt_dt~end_dt 기간의 일별 매수·매도·순매수 수량을 받는 TR입니다. 요청 body는 mmcm_cd·stk_cd·strt_dt·end_dt 네 개가 전부 필수이고, 경로는 같은 거래원 계열인 ka10039의 rkinfo가 아니라 /api/dostk/mrkcond입니다. stex_tp가 없어 거래소는 종목코드 접미사(_NX·_AL)로 고르고, 응답에는 금액 필드가 없어 수량만 옵니다. 현재가 cur_prc는 "-5880"처럼 부호가 붙어 오고, 명세 응답 예시에는 요청 기간 밖의 날짜가 섞여 있습니다. (공식 명세 kiwoom_api_spec.json·예제, main 953e5db 2026-09-15 기준, 2026-10-20 확인)
목차
- ka10078은 무엇을 주나 — 거래원 TR 4종 비교
- 요청·응답 실물
- 함정 6가지
- 최소 호출 코드
- 자주 묻는 질문
1. ka10078은 무엇을 주나 — 거래원 TR 4종 비교
키움 REST에서 증권사 창구(회원사) 데이터를 주는 TR은 질문 방향이 다릅니다. ka10039 증권사별매매상위는 "이 증권사 창구로 무엇이 많이 사졌나"(순위), ka10040 당일주요거래원은 "이 종목을 어느 창구가 샀나"(당일 상위 5곳)입니다. ka10078은 둘을 교차시켜 "이 증권사 창구가 이 종목을 날짜별로 얼마나 샀다 팔았나"를 기간으로 줍니다.
| 항목 | ka10078 증권사별종목매매동향 | ka10039 증권사별매매상위 | ka10043 거래원매물대분석 |
|---|---|---|---|
| 경로 | /api/dostk/mrkcond (시세) | /api/dostk/rkinfo (순위정보) | /api/dostk/stkinfo (종목정보) |
| 고정하는 것 | 회원사 + 종목 | 회원사 | 회원사 + 종목 |
| 기간 | strt_dt·end_dt (YYYYMMDD) | dt 0·1·5·10·60 | strt_dt·end_dt + qry_dt_tp·dt |
| 거래소 선택 | stex_tp 없음 — 종목코드 접미사 | stex_tp 1·2·3 | stex_tp 1·2·3 |
| 금액 | 없음 (수량만) | netprps_amt 천원 | 없음 (trde_wght 비중) |
| 목록 키 | sec_stk_trde_trend | sec_trde_upper | trde_ori_prps_anly |
명세 전체 337개 API 중 mmcm_cd를 받는 TR은 ka10039·ka10043·ka10052·ka10078 네 개이고, 그중 기간(strt_dt)까지 받는 것은 ka10043과 ka10078 둘입니다. 같은 mrkcond 경로에는 ka10004(호가)·ka10086(일별주가)·ka90005(프로그램매매추이) 등 25개가 있습니다.
2. 요청·응답 실물
명세의 request_example 원문입니다. 공식 예제 examples/국내주식/시세/get_domestic_broker_stock_trade_trend.py의 __main__도 같은 값입니다.
POST https://api.kiwoom.com/api/dostk/mrkcond
api-id: ka10078
authorization: Bearer {접근토큰}
{
"mmcm_cd": "001",
"stk_cd": "005930",
"strt_dt": "20241106",
"end_dt": "20241107"
}
응답 response_example 원문(4행 중 2행)입니다.
{
"sec_stk_trde_trend": [
{
"dt": "20241106",
"cur_prc": "10240",
"pre_sig": "0",
"pred_pre": "0",
"flu_rt": "0.00",
"acc_trde_qty": "0",
"netprps_qty": "-1016",
"buy_qty": "951",
"sell_qty": "1967"
},
{
"dt": "20241101",
"cur_prc": "-5880",
"pre_sig": "4",
"pred_pre": "-2520",
"flu_rt": "-30.00",
"acc_trde_qty": "16139969",
"netprps_qty": "-532",
"buy_qty": "2454",
"sell_qty": "2986"
}
],
"return_code": 0,
"return_msg": "정상적으로 처리되었습니다"
}
| element | 한글명 | 명세 설명 |
|---|---|---|
dt | 일자 | YYYYMMDD |
cur_prc · pred_pre | 현재가 · 전일대비 | 단위: 원, 부호가 포함된 숫자 |
pre_sig | 대비기호 | 1: 상한가, 2:상승, 3:보합, 4:하한가, 5:하락 |
flu_rt | 등락율 | 단위: %, 부호 포함 소수점 둘째 자리 |
acc_trde_qty | 누적거래량 | 단위: 1주 |
netprps_qty · buy_qty · sell_qty | 순매수수량 · 매수수량 · 매도수량 | 단위: 1주 |
4행 모두 buy_qty − sell_qty = netprps_qty가 정확히 맞습니다(예: 951 − 1967 = −1016, 2454 − 2986 = −532). ka10039는 매도 수량에 -를 붙여 주는데, ka10078은 buy_qty·sell_qty가 부호 없는 양수이고 netprps_qty만 부호를 가집니다. 두 TR을 같은 파서로 처리하면 매도 수량의 부호가 뒤집힙니다.
3. 함정 6가지
① 경로가 mrkcond다 — 거래원 TR끼리도 셋으로 갈린다
키움 REST는 경로(URL)와 헤더 api-id를 둘 다 맞춰야 합니다. 거래원 계열이라고 ka10039의 /api/dostk/rkinfo 래퍼에 api-id: ka10078만 바꿔 넣으면 경로가 틀립니다. 위 표처럼 ka10039 rkinfo · ka10043·ka10102 stkinfo · ka10078 mrkcond로 셋이 다르니, TR 코드 → 경로 매핑을 명세에서 뽑아 상수로 두십시오. 오류 응답의 return_code 해석은 키움 return_code 글에 있습니다.
② stex_tp가 없다 — 거래소는 종목코드 접미사로
ka10039·ka10043은 stex_tp(1:KRX, 2:NXT, 3:통합)를 받지만 ka10078 요청에는 없습니다. 대신 stk_cd 설명이 "거래소별 종목코드 (KRX:039490, NXT:039490_NX, SOR:039490_AL)"라, 거래소를 종목코드로 고르는 구조입니다. 공통 함수로 stex_tp="3"을 같이 넘겨도 명세에 없는 필드라 서버가 어떻게 처리할지 보장되지 않습니다 — 통합을 원하면 005930_AL처럼 종목코드로 지정하십시오. 접미사별 실제 응답 차이는 명세 예시가 KRX뿐이라 운영 호출로 확인이 필요합니다. NXT·SOR 개념은 NXT·KRX 주문 라우팅 글에 있습니다.
③ 금액 필드가 없다
ka10039는 netprps_amt(천원)를 주지만 ka10078은 수량뿐입니다. "교보 창구가 이 종목을 며칠간 얼마어치 샀나"를 보려면 netprps_qty × 가격으로 추정해야 하는데, 이건 창구의 실제 체결 평균가가 아니라 그날 가격 기준 근사치입니다. 저장할 때 열 이름을 netprps_won_est처럼 추정임을 드러내 두지 않으면, 나중에 ka10039의 실제 금액과 섞어 비교하는 실수가 납니다.
④ cur_prc에 부호가 붙는다
예시 마지막 행은 "cur_prc": "-5880", pre_sig "4"(하한가), flu_rt "-30.00"입니다. 명세 설명 그대로 "부호가 포함된 숫자"라 하락한 날의 가격이 음수 문자열로 옵니다. pd.to_numeric을 그대로 쓰면 −5,880원이 되어 ③의 금액 추정이 부호째 뒤집히니 가격은 abs()를 취하십시오. 같은 성격의 부호 처리는 키움 순위 TR 글에서도 다뤘습니다.
⑤ 예시 응답에 요청 기간 밖의 날짜가 있다
요청은 20241106~20241107인데 응답 예시는 20241107·20241106·20241105·20241101 4행입니다(내림차순). 서버가 실제로 범위 밖 날짜를 돌려주는지, 예시만 잘못 만든 것인지는 명세에 없습니다. 어느 쪽이든 받은 뒤 dt로 한 번 더 자르는 것이 안전합니다. 예시의 다른 값도 형식 확인용입니다 — 종목이 005930인데 cur_prc가 10,050원, pre_sig "0"은 설명(1~5)에 없는 값, 매수·매도가 있는 날의 acc_trde_qty가 "0"입니다.
⑥ 공식 예제의 숫자 변환은 표시용이다
명세 예시는 ka10039와 달리 json.loads로 바로 읽힙니다. 다만 pandas 2.2.3으로 DataFrame을 만들면 9개 열이 전부 object(문자열)입니다(실측). 공식 예제의 NUMERIC_COLUMNS(누적거래량·등락율·매도수량·매수수량·순매수수량·현재가 6개)는 화면에 쉼표를 찍는 _format_display에만 쓰이고, 그 목록에도 전일대비(pred_pre)는 빠져 있습니다. 계산하려면 직접 변환해야 합니다.
명세가 비워 둔 것 — 한 번에 조회할 수 있는 최대 기간, 한 페이지에 오는 일수, 당일(end_dt=오늘) 조회 시 장중 몇 시 기준인지, 휴장일 행이 오는지는 명세에 없습니다. 공식 예제의 연속조회 상한은 MAX_PAGES = 10·REQUEST_DELAY_SECONDS = 0.2입니다. 긴 기간은 몇 달씩 끊어 받고, 첫 운영 호출에서 실제 행 수를 확인하십시오.
4. 최소 호출 코드
토큰은 환경변수에서 읽고, ①~⑥을 코드에서 막습니다. cont-yn·next-key 처리는 연속조회 글과 같습니다.
import os, re, time, requests
import pandas as pd
BASE = "https://api.kiwoom.com" # 모의: https://mockapi.kiwoom.com
TOKEN = os.environ["KIWOOM_TOKEN"]
PATH = {"ka10078": "/api/dostk/mrkcond", # ① TR마다 경로가 다르다
"ka10102": "/api/dostk/stkinfo"}
QTY = ["acc_trde_qty", "netprps_qty", "buy_qty", "sell_qty"]
def post(api_id, body, cont_yn=None, next_key=None):
h = {"Content-Type": "application/json;charset=UTF-8",
"authorization": f"Bearer {TOKEN}", "api-id": api_id}
if cont_yn == "Y":
h.update({"cont-yn": "Y", "next-key": next_key})
r = requests.post(BASE + PATH[api_id], headers=h, json=body, timeout=10)
j = r.json()
if j.get("return_code") not in (None, 0):
raise RuntimeError(f"{api_id} {j.get('return_code')}: {j.get('return_msg')}")
return j, r.headers.get("cont-yn"), r.headers.get("next-key")
def broker_codes(): # ka10102 — 하루 1번 캐시
j, _, _ = post("ka10102", {})
return {re.sub(r"\s+", "", x["name"]): x["code"] for x in j["list"]}
def ka10078(mmcm_cd, code6, start, end, exch="KRX", max_pages=10):
stk = code6 + {"KRX": "", "NXT": "_NX", "SOR": "_AL"}[exch] # ② 접미사로 거래소
body = {"mmcm_cd": mmcm_cd, "stk_cd": stk, "strt_dt": start, "end_dt": end}
rows, cy, nk = [], None, None
for _ in range(max_pages):
j, cy, nk = post("ka10078", body, cy, nk)
rows += j.get("sec_stk_trde_trend", [])
if cy != "Y":
break
time.sleep(0.2)
df = pd.DataFrame(rows)
if df.empty:
return df
df = df[(df["dt"] >= start) & (df["dt"] <= end)].copy() # ⑤ 기간 다시 자르기
df[QTY] = df[QTY].apply(pd.to_numeric, errors="coerce") # ⑥ 직접 변환
df["price"] = pd.to_numeric(df["cur_prc"], errors="coerce").abs() # ④ 부호 제거
df["netprps_won_est"] = df["netprps_qty"] * df["price"] # ③ 추정치임을 이름에
df["mmcm_cd"], df["code6"] = mmcm_cd, code6
return df.sort_values("dt")
codes = broker_codes()
df = ka10078(codes["한국투자증권"], "005930", "20260901", "20260930")
회원사 여러 곳 × 종목 여러 개를 돌면 호출 수가 곱으로 늘어납니다. 회원사 5곳 × 종목 20개면 기간 하나에 100회라, 0.2초 간격이어도 20초 이상 걸리고 키움 429 호출 제한에 닿기 쉽습니다. 장 마감 뒤 하루치(strt_dt=end_dt=오늘)만 받아 쌓고, 과거 구간은 한 번만 채워 두는 구성이 무난합니다.
창구 데이터는 그 증권사 고객 전체의 합이라 누가 주문했는지를 알려 주지 않고 해석이 갈리는 정보입니다. 이 목록만으로 매매 근거를 삼을 수는 없으며 이 글은 특정 종목이나 전략을 권하지 않습니다. 명세는 바뀔 수 있으니 키움 REST API 공식 문서로 현재 값을 확인하십시오.
5. 자주 묻는 질문
ka10078과 ka10039는 무엇이 다른가요?
ka10039는 회원사 1곳을 정하면 그 창구로 많이 순매수·순매도된 종목 순위를 주고, ka10078은 회원사 1곳과 종목 1개를 정하면 strt_dt부터 end_dt까지 일별 매수·매도·순매수 수량을 줍니다. ka10039는 /api/dostk/rkinfo, ka10078은 /api/dostk/mrkcond로 경로도 다르고, ka10078에는 금액 필드와 stex_tp가 없습니다.
ka10078에서 NXT나 통합 데이터를 보려면 어떻게 하나요?
ka10078 요청에는 stex_tp가 없고, stk_cd 설명이 거래소별 종목코드(KRX:039490, NXT:039490_NX, SOR:039490_AL)입니다. 종목코드 접미사로 거래소를 고르는 방식이므로 NXT는 _NX, 통합은 _AL을 붙여 보냅니다. 실제 응답 차이는 운영 호출로 확인하십시오.
ka10078 순매수 금액은 어떻게 구하나요?
응답에 금액 필드가 없습니다. 수량 필드(netprps_qty·buy_qty·sell_qty, 단위 1주)뿐이라 금액이 필요하면 수량에 가격을 곱한 추정치를 써야 합니다. 이때 cur_prc가 '-5880'처럼 부호가 붙어 오므로 절대값을 취해야 하고, 창구의 실제 체결 평균가가 아니라 그날 가격 기준 추정이라는 점을 표시해 두십시오.
ka10078 mmcm_cd 001은 어느 증권사인가요?
ka10102(회원사 리스트) 명세 예시 기준으로 001은 교보입니다. 같은 예시에 003 한국투자증권, 012 NH투자증권 등이 있고, 이름에 '교 보'처럼 공백이 끼어 있습니다. 코드는 하드코딩하지 말고 ka10102 결과를 받아 캐시해 쓰는 것이 안전합니다.
창구별 수급을 쌓아 두는 키움 봇, 만들어 드립니다
보고 싶은 회원사와 종목만 알려 주시면 TR 경로·코드 정규화·일별 이력 저장까지 맞춰 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기