키움 ka10043 거래원매물대분석 — 하락일이 NaN이 된다
ka10043(거래원매물대분석요청)은 "회원사 1곳 × 종목 1개"의 일별 매수·매도·순매수 수량을 sort_base 1이면 종가순으로 정렬해 주는 TR입니다. 경로는 /api/dostk/stkinfo, 요청 body 9개(stk_cd·strt_dt·end_dt·qry_dt_tp·pot_tp·dt·sort_base·mmcm_cd·stex_tp)가 전부 필수입니다. 가장 큰 함정은 하락일 전일대비 pred_pre가 "--1100"처럼 마이너스 두 개로 온다는 점입니다 — int()는 ValueError, pd.to_numeric(errors="coerce")는 하락일만 조용히 NaN으로 만듭니다(실측). 예시 요청의 회원사코드는 "36"(2자리)인데 ka10102 목록은 "001"(3자리)이고, 거래비중 trde_wght는 단위가 %인데 예시 값이 +19079.07입니다. (공식 명세 kiwoom_api_spec.json·예제, main 953e5db 2026-09-15 기준, 2026-10-21 확인)
목차
- ka10043은 무엇을 주나 — ka10078·ka10042와 비교
- 요청·응답 실물
- 함정 6가지
- 최소 호출 코드
- 자주 묻는 질문
1. ka10043은 무엇을 주나 — ka10078·ka10042와 비교
"매물대"는 가격대별로 거래가 얼마나 쌓였는지를 보는 개념입니다. ka10043은 이를 특정 증권사 창구 하나로 좁혀, 그 창구가 이 종목을 어느 가격(그날 종가)에서 얼마나 사고팔았는지를 날짜 행으로 줍니다. 가격대 구간으로 묶어 주는 것이 아니라 일별 행을 종가순으로 정렬해 주는 것이므로, 구간 집계는 받은 뒤 직접 해야 합니다.
같은 "회원사 + 종목" 축의 ka10078 증권사별종목매매동향과 거의 같은 데이터를 다른 모양으로 주고, 같은 기간 파라미터(qry_dt_tp·pot_tp·sort_base)를 쓰는 형제 TR이 ka10042 순매수거래원순위입니다.
| 항목 | ka10043 거래원매물대분석 | ka10078 증권사별종목매매동향 | ka10042 순매수거래원순위 |
|---|---|---|---|
| 경로 | /api/dostk/stkinfo | /api/dostk/mrkcond | /api/dostk/rkinfo |
| 고정하는 것 | 회원사 + 종목 | 회원사 + 종목 | 종목 (회원사 순위를 받음) |
| 필수 body | 9개 전부 Y | 4개 | strt_dt·end_dt·dt는 N |
| 거래소 | stex_tp 1·2·3 | 없음 — 종목코드 접미사 | 종목코드 접미사 |
| 정렬 | sort_base 1 종가순 · 2 날짜순 | 날짜 내림차순 | sort_base 1·2 |
| 목록 키 | trde_ori_prps_anly | sec_stk_trde_trend | netprps_trde_ori_rank |
명세 전체 337개 API 중 trde_wght(거래비중) 필드를 응답에 가진 TR은 ka10014(공매도추이)·ka10015(일별거래상세)·ka10043 세 개이고, qry_dt_tp를 받는 TR은 ka10042·ka10043·ka10098·ka90009 네 개입니다. 경로 stkinfo에는 회원사 리스트 ka10102와 거래원순간거래량 ka10052도 같이 있습니다.
2. 요청·응답 실물
명세의 request_example 원문입니다. 공식 예제 examples/국내주식/종목정보/get_domestic_broker_volume_profile_analysis.py의 __main__도 같은 값입니다.
POST https://api.kiwoom.com/api/dostk/stkinfo
api-id: ka10043
authorization: Bearer {접근토큰}
{
"stk_cd": "005930",
"strt_dt": "20241031",
"end_dt": "20241107",
"qry_dt_tp": "0",
"pot_tp": "0",
"dt": "5",
"sort_base": "1",
"mmcm_cd": "36",
"stex_tp": "3"
}
응답 response_example 원문(4행 중 3행)입니다. sort_base "1"이라 날짜가 아니라 종가 내림차순(135300 → 133600 → 132500 → 65100)으로 옵니다.
{
"trde_ori_prps_anly": [
{
"dt": "20241105",
"close_pric": "135300",
"pre_sig": "2",
"pred_pre": "+1700",
"sel_qty": "43",
"buy_qty": "1090",
"netprps_qty": "1047",
"trde_qty_sum": "1133",
"trde_wght": "+1317.44"
},
{
"dt": "20241106",
"close_pric": "132500",
"pre_sig": "5",
"pred_pre": "--1100",
"sel_qty": "117",
"buy_qty": "3459",
"netprps_qty": "3342",
"trde_qty_sum": "3576",
"trde_wght": "+4158.14"
},
{
"dt": "20241101",
"close_pric": "65100",
"pre_sig": "5",
"pred_pre": "--68500",
"sel_qty": "3728",
"buy_qty": "12680",
"netprps_qty": "8952",
"trde_qty_sum": "16408",
"trde_wght": "+19079.07"
}
],
"return_code": 0,
"return_msg": "정상적으로 처리되었습니다"
}
| element | 한글명 | 명세 설명 |
|---|---|---|
dt · close_pric | 일자 · 종가 | YYYYMMDD · 단위: 원 |
pre_sig | 대비기호 | 1: 상한가, 2:상승, 3:보합, 4:하한가, 5:하락 |
pred_pre | 전일대비 | 단위: 원, 부호가 포함된 숫자 |
sel_qty · buy_qty · netprps_qty | 매도량 · 매수량 · 순매수수량 | 단위: 1주 |
trde_qty_sum | 거래량합 | 단위: 1주 |
trde_wght | 거래비중 | 단위: %, 부호 포함 소수점 둘째 자리까지 포맷된 백분율 |
4행 모두 buy_qty − sel_qty = netprps_qty, buy_qty + sel_qty = trde_qty_sum이 정확히 맞습니다(예: 1090 − 43 = 1047, 1090 + 43 = 1133). 즉 trde_qty_sum은 종목 전체 거래량이 아니라 그 창구의 매수+매도 합입니다.
3. 함정 6가지
① 전일대비가 "--1100" — 하락일만 NaN이 된다
하락일(pre_sig "5") 두 행의 pred_pre가 "--1100"·"--68500"입니다. 마이너스가 두 개입니다. Python 3.10.4 · pandas 2.2.3으로 명세 예시를 그대로 넣어 본 결과입니다(실측).
>>> int("--1100")
ValueError: invalid literal for int() with base 10: '--1100'
>>> pd.to_numeric(df["pred_pre"], errors="coerce").tolist()
[1700.0, 0.0, nan, nan] # 상승·보합은 살고, 하락 2행만 사라진다
errors="coerce"는 오류 대신 NaN을 넣으니 프로그램은 멈추지 않고 하락일 데이터만 빠집니다. 이 상태로 평균 전일대비나 "하락일에 이 창구가 샀나"를 계산하면 결과가 한쪽으로 치우칩니다. 앞의 +·-를 전부 떼어 낸 뒤, 첫 글자가 -였으면 음수로 붙이는 변환이 필요합니다(4장 signed()). 실측 결과는 [1700.0, 0.0, -1100.0, -68500.0]입니다. 같은 성격의 부호 문제는 키움 순위 TR 글에서도 다뤘습니다.
② 필수 9개 — 기간을 두 방식으로 다 채워야 한다
qry_dt_tp는 "0:기간으로 조회, 1:시작일자, 종료일자로 조회"입니다. 그런데 ka10043은 strt_dt·end_dt·dt가 모두 필수(Y)라, 어느 방식을 고르든 셋을 다 보내야 합니다. 같은 파라미터를 쓰는 ka10042(순매수거래원순위)는 셋이 선택(N)이라, ka10042용 함수를 재사용하면 빠진 필드로 요청이 실패할 수 있습니다. dt는 5·10·20·40·60·120일 여섯 값뿐입니다. 공식 예제도 9개를 하나씩 ValueError로 검사합니다.
③ 회원사코드 "36" vs "001"
mmcm_cd 설명은 "회원사 코드는 ka10102 조회", 길이 3입니다. ka10102 응답 예시는 "001"(교보)·"003"(한국투자증권)·"012"(NH투자증권)처럼 3자리이고 ka10039·ka10078 예시 요청도 "001"을 씁니다. 그런데 ka10043 예시 요청은 "36", ka10042 응답 예시의 mmcm_cd는 "36"·"50"·"6"처럼 앞자리 0이 없습니다. 서버가 "36"과 "036"을 둘 다 받는지는 명세에 없습니다. ka10102에서 받은 값을 그대로 넣고, ka10042 결과와 대조할 때만 zfill(3)으로 맞추십시오.
④ trde_wght가 100%를 넘는다
단위가 %인데 예시 값이 +1317.44·+4158.14·+19079.07입니다. 같은 이름의 필드가 ka10014 예시에서는 +8.58·+4.69처럼 상식적인 비율입니다. ka10043의 분모가 무엇인지(종목 전체 거래량인지, 다른 기준인지)는 명세에 없고, 예시 값만으로는 역산되지 않습니다. 운영 호출 값을 ka10078·ka10086(일별주가)의 거래량과 대조해 의미를 확인하기 전에는 매매 조건에 넣지 마십시오.
⑤ 예시 값은 형식 확인용이다
종목이 005930인데 종가가 135,300원이고, 1105일 종가 135,300 다음 날 1106일이 "−1,100"인데 종가는 132,500원이라 앞뒤가 맞지 않습니다. 1101일은 --68500(전일 대비 −51%)으로 가격제한폭(±30%) 밖입니다. 요청 dt "5"에 4행만 오고 1104일이 빠진 것도 설명이 없습니다. 값의 의미가 아니라 필드 이름·형식만 참고하고, 행 수와 날짜 범위는 받은 뒤 검사하십시오.
⑥ 공식 예제의 숫자 목록에 매도량이 빠져 있다
pandas 2.2.3으로 예시를 DataFrame으로 만들면 9개 열이 전부 object(문자열)입니다(실측). 공식 예제의 NUMERIC_COLUMNS는 거래량합·거래비중·매수량·순매수수량·종가 5개뿐이라 매도량(sel_qty)과 전일대비(pred_pre)가 없습니다. 이 목록은 화면 표시용 _format_display에만 쓰이니, 계산하려면 직접 변환해야 합니다. 예제의 연속조회 상한은 MAX_PAGES = 10·REQUEST_DELAY_SECONDS = 0.2입니다.
명세가 비워 둔 것 — pot_tp(0:당일, 1:전일)가 기간 계산의 기준일을 바꾸는지, qry_dt_tp "0"일 때 보낸 strt_dt·end_dt를 서버가 무시하는지, 한 번에 받을 수 있는 최대 일수, stex_tp "2"(NXT) 응답 형태는 명세에 없습니다. 첫 운영 호출에서 행 수·날짜·trde_wght를 직접 확인하십시오.
4. 최소 호출 코드
토큰은 환경변수에서 읽고, ①~⑥을 코드에서 막습니다. cont-yn·next-key 처리는 연속조회 글과 같고, 오류 응답의 return_code는 키움 return_code 글에서 해석합니다.
import os, time, requests
import pandas as pd
BASE = "https://api.kiwoom.com" # 모의: https://mockapi.kiwoom.com
TOKEN = os.environ["KIWOOM_TOKEN"]
PATH = "/api/dostk/stkinfo" # ka10043·ka10102 공통
QTY = ["sel_qty", "buy_qty", "netprps_qty", "trde_qty_sum"]
def signed(s): # ① "--1100" → -1100.0
s = str(s).strip()
if s in ("", "None"):
return float("nan")
neg = s.startswith("-")
v = float(s.lstrip("+-") or 0)
return -v if neg else v
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, 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 ka10043(mmcm_cd, code6, start, end, days="20", by_date=True,
stex_tp="1", max_pages=10):
body = {"stk_cd": code6, "strt_dt": start, "end_dt": end, # ② 9개 전부
"qry_dt_tp": "1" if by_date else "0", "pot_tp": "0",
"dt": days, "sort_base": "2", "mmcm_cd": mmcm_cd, # ③ ka10102 값 그대로
"stex_tp": stex_tp}
rows, cy, nk = [], None, None
for _ in range(max_pages):
j, cy, nk = post("ka10043", body, cy, nk)
rows += j.get("trde_ori_prps_anly", [])
if cy != "Y":
break
time.sleep(0.2)
df = pd.DataFrame(rows)
if df.empty:
return df
df[QTY] = df[QTY].apply(pd.to_numeric, errors="coerce") # ⑥ 직접 변환
df["close"] = pd.to_numeric(df["close_pric"], errors="coerce").abs()
df["chg"] = df["pred_pre"].map(signed) # ① coerce 금지
df["trde_wght_raw"] = df["trde_wght"].map(signed) # ④ 의미 확인 전 원본명
if by_date:
df = df[(df["dt"] >= start) & (df["dt"] <= end)] # ⑤ 범위 재검사
df["mmcm_cd3"] = str(mmcm_cd).zfill(3) # ③ 비교용 정규화
return df.sort_values("dt")
def price_bins(df, step=1000): # 매물대 = 받은 뒤 직접 묶는다
b = (df["close"] // step) * step
return df.groupby(b)[["buy_qty", "sel_qty", "netprps_qty"]].sum()
sort_base는 일부러 "2"(날짜순)로 받았습니다. 종가순 정렬은 행 순서만 바꿀 뿐이라, 날짜순으로 받아 저장해 두고 매물대는 price_bins()처럼 원하는 가격 폭으로 직접 묶는 편이 다른 TR과 합치기 쉽습니다. 회원사 여러 곳 × 종목 여러 개를 돌면 호출 수가 곱으로 늘어 키움 429 호출 제한에 닿기 쉬우니, 장 마감 뒤 하루치만 쌓는 구성이 무난합니다. 거래소 구분은 NXT·KRX 주문 라우팅 글을 참고하십시오.
창구 데이터는 그 증권사 고객 전체의 합이라 누가 주문했는지를 알려 주지 않고 해석이 갈리는 정보입니다. 이 데이터만으로 매매 근거를 삼을 수는 없으며 이 글은 특정 종목이나 전략을 권하지 않습니다. 명세는 바뀔 수 있으니 키움 REST API 공식 문서로 현재 값을 확인하십시오.
5. 자주 묻는 질문
ka10043과 ka10078은 무엇이 다른가요?
둘 다 회원사 1곳과 종목 1개를 정해 일별 매수·매도·순매수 수량을 주지만, ka10043은 /api/dostk/stkinfo 경로이고 stex_tp·qry_dt_tp·pot_tp·dt·sort_base까지 9개가 전부 필수이며 sort_base 1이면 종가순으로 정렬해 줍니다. ka10078은 /api/dostk/mrkcond 경로에 필수 4개, stex_tp 없이 종목코드 접미사로 거래소를 고릅니다.
ka10043 pred_pre의 '--1100'은 무슨 뜻인가요?
명세 응답 예시에서 하락일(pre_sig 5)의 전일대비가 '--1100'처럼 마이너스 두 개로 옵니다. 1,100원 하락으로 읽어야 하며, int()는 ValueError를 내고 pd.to_numeric(errors='coerce')는 NaN으로 바꿔 하락일만 조용히 사라집니다. 앞의 +와 -를 떼어 낸 뒤 부호를 다시 붙이는 함수로 변환하십시오.
ka10043의 qry_dt_tp는 어떻게 쓰나요?
qry_dt_tp 0은 dt(5·10·20·40·60·120일) 기간으로, 1은 strt_dt~end_dt 날짜로 조회합니다. ka10043은 어느 쪽을 쓰든 strt_dt·end_dt·dt가 모두 필수(Y)라 세 값을 항상 채워 보내야 합니다. 같은 파라미터를 쓰는 ka10042는 이 셋이 선택(N)입니다.
ka10043 mmcm_cd는 '36'과 '036' 중 무엇을 넣나요?
명세 예시 요청은 '36'(2자리)을 쓰지만 ka10102 회원사 리스트 예시는 '001'처럼 3자리이고, ka10042 응답 예시의 mmcm_cd도 '36'·'6'처럼 앞자리 0이 없습니다. 서버가 두 형식을 모두 받는지는 명세에 없으므로, ka10102에서 받은 코드를 그대로 넣고 TR 간 비교할 때만 zfill(3)으로 맞추는 것이 안전합니다.
창구별 매물대를 쌓아 두는 키움 봇, 만들어 드립니다
보고 싶은 회원사와 종목만 알려 주시면 TR 경로·부호 정리·일별 이력 저장까지 맞춰 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기