AlgoLab Blog · 키움 데이터 실무 · 2026

키움 REST API 차트 데이터 — ka10081 수정주가 함정

키움증권 · 과거 데이터 2026-08-23 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약 키움 REST API에서 일봉은 ka10081, 분봉은 ka10080, 틱은 ka10079입니다. 셋 다 POST /api/dostk/chart로 같고 헤더 api-id 값 하나로만 갈립니다. 가장 많이 걸리는 지점은 수정주가입니다 — upd_stkpc_tp1로 바꾸는 것만으로는 부족하고, base_dt를 권리발생일 이후 날짜로 넣은 뒤 연속조회로 과거를 파고들어야 액면분할 이전 구간이 수정주가로 나옵니다. 그리고 응답 리스트 이름이 셋 다 다릅니다stk_dt_pole_chart_qry(일봉) · stk_min_pole_chart_qry(분봉) · stk_tic_chart_qry(틱).

키움 REST API로 주문을 내는 것보다 먼저 필요한 것이 있습니다. 전략을 검증할 과거 데이터입니다. 봇을 만들기로 했다면 순서는 대개 이렇게 흘러갑니다.

키움 REST API는 이 셋을 각각 ka10081·ka10080·ka10079로 제공합니다. 문제는 붙여 보면 숫자가 조용히 틀어진다는 것입니다. 에러가 나면 차라리 낫습니다. 액면분할 전 구간이 200만원대로 찍혀 있어도 rt_cd는 정상이고 백테스트는 그대로 돌아갑니다. 이 글은 그 조용한 어긋남 다섯 군데만 다룹니다.

목차

  1. 세 TR 한 장 대조 — 같은 URL, 다른 api-id
  2. 함정 ① 수정주가 — base_dt 순서를 지켜야 한다
  3. 함정 ② 응답 리스트 키가 셋 다 다르다
  4. 함정 ③ 분봉만 가격에 부호가 붙는다
  5. 함정 ④ 전부 String — 그대로 계산 금지
  6. 함정 ⑤ stk_cd 접미사와 거래소
  7. 실전 코드 — 일봉 N년치 수집기
  8. 자주 묻는 질문

1. 세 TR 한 장 대조 — 같은 URL, 다른 api-id

아래는 키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD 기준)의 국내주식 > 차트 항목을 대조한 것입니다. 셋 다 Method: POST, URL: /api/dostk/chart로 같습니다.

구분ka10079 틱ka10080 분봉ka10081 일봉
공식 이름주식틱차트조회요청주식분봉차트조회요청주식일봉차트조회요청
stk_cd필수필수필수
tic_scope필수 · 1/3/5/10/30 필수 · 1/3/5/10/15/30/45/60 없음
base_dt없음선택필수
upd_stkpc_tp필수 · 0 or 1필수 · 0 or 1필수 · 0 or 1
응답 리스트 키stk_tic_chart_qrystk_min_pole_chart_qrystk_dt_pole_chart_qry
시간 항목cntr_tm (YYYYMMDDHHmmss)cntr_tm (YYYYMMDDHHmmss)dt (YYYYMMDD)
일봉에만 있는 것trde_prica(거래대금·백만원)
trde_tern_rt(거래회전율·%)

표에서 먼저 눈에 담아야 할 것은 tic_scope가 두 TR에 이름만 같고 단위가 다르다는 점입니다. ka10079에서 tic_scope=55틱, ka10080에서 같은 값은 5분입니다. 그리고 ka10080에는 15·45·60이 있지만 ka10079에는 없습니다. 분봉 파서에서 상수만 옮겨 오면 틱차트 쪽에서 명세에 없는 값을 보내게 됩니다.

POST /api/dostk/chart Authorization: Bearer + api-id ka10079 틱차트 tic_scope = 틱 stk_tic_chart_qry ka10080 분봉 tic_scope = 분 stk_min_pole_chart_qry ka10081 일봉 base_dt 필수 stk_dt_pole_chart_qry 엔드포인트는 하나 · 갈라지는 것은 api-id 헤더와 응답 리스트 이름
키움 차트 3종 — URL은 같고 api-id로 갈린다

2. 함정 ① 수정주가 — base_dt 순서를 지켜야 한다

이것 하나만 알고 가도 이 글을 읽은 값은 합니다. 액면분할·병합 같은 권리 사건이 있었던 종목의 과거 일봉을 받을 때 벌어지는 일입니다.

upd_stkpc_tp(수정주가구분)는 0 또는 1을 받습니다. 직관적으로는 1을 넣으면 수정주가로 나올 것 같습니다. 그런데 키움 공식 명세의 upd_stkpc_tp 설명은 값이 아니라 base_dt에 대한 조건을 붙여 놓았습니다.

키움 공식 명세 — upd_stkpc_tp(ka10081)

"수정주가 적용을 원하시는 경우, 권리발생일 이후 일자를 base_dt에 넣어 조회 또는 연속조회해주시기 바랍니다. 예를 들어 삼성전자 2018년 05월 04일(액면분할 권리발생일)을 base_dt에 넣고 수정주가적용(1)로 조회하면 2018년 05월 04일 이전 데이터는 수정주가 적용된 3만원대 가격이 나옵니다. (…) 권리 발생일 이전인 2018년 05월 03일 이전 일자로 base_dt 조회 시 수정주가 적용되지 않아 200만원 이상 가격대가 나옵니다."

삼성전자는 2018년 액면분할로 주가 자릿수 자체가 바뀐 종목이라 눈에 띄지만, 덜 유명한 종목에서 이 일이 벌어지면 아무도 못 알아챕니다. 분할 시점에 -95% 짜리 봉 하나가 꽂히고, 변동성 돌파나 이동평균 교차 전략은 거기서 거대한 가짜 신호를 만들어 냅니다. 백테스트와 실전이 벌어지는 이유 중에 가장 허무한 유형이 이것입니다 — 전략이 아니라 데이터가 틀린 것입니다.

실무 규칙 — 과거 구간을 받을 때는 base_dt오늘(또는 최근 영업일)로 두고 upd_stkpc_tp="1"로 첫 호출을 한 뒤, 연속조회로 과거를 향해 파고드는 방향만 씁니다. "2015년부터 받아야지" 하고 base_dt="20150101"로 시작하면 그 사이의 모든 권리 사건이 미반영 상태로 들어옵니다.

3. 함정 ② 응답 리스트 키가 셋 다 다르다

세 TR은 URL이 같아서 공통 파서를 하나 쓰고 싶어집니다. 그런데 봉 배열이 담긴 키 이름이 전부 다릅니다. 아래는 공식 명세의 응답 항목으로 구성한 ka10081 응답 형태입니다.

{
  "stk_cd": "005930",
  "stk_dt_pole_chart_qry": [
    {
      "cur_prc":    "71800",      // 현재가(종가) · 단위 원
      "trde_qty":   "12345678",   // 거래량 · 단위 1주
      "trde_prica": "886543",     // 거래대금 · 단위 백만원  ← 원이 아님
      "dt":         "20260821",   // 일자 YYYYMMDD
      "open_pric":  "71200",
      "high_pric":  "72400",
      "low_pric":   "70900",
      "pred_pre":   "+600",       // 전일대비 = 현재가 - 전일종가
      "pred_pre_sig": "2",        // 1 상한 2 상승 3 보합 4 하한 5 하락
      "trde_tern_rt": "0.21"      // 거래회전율 · %
    }
  ],
  "return_code": 0,
  "return_msg":  "정상적으로 처리되었습니다"
}

파서를 만들 때 키를 상수로 묶어 두면 세 TR을 한 함수로 처리할 수 있습니다.

CHART_TR = {
    "tick": ("ka10079", "stk_tic_chart_qry", "cntr_tm"),
    "min":  ("ka10080", "stk_min_pole_chart_qry", "cntr_tm"),
    "day":  ("ka10081", "stk_dt_pole_chart_qry", "dt"),
}

단위 함정 하나 더 — 일봉에만 있는 trde_prica(거래대금)는 단위가 백만원입니다. 원 단위로 착각하고 유동성 필터를 걸면 기준이 100만 배 어긋납니다. 거래대금으로 종목을 거르는 로직은 종목 스캐너 만들기에서 다룬 것과 같은 계열의 실수가 나오는 자리입니다.

4. 함정 ③ 분봉만 가격에 부호가 붙는다

공식 명세의 응답 항목 설명을 나란히 놓으면 이렇게 갈립니다.

항목ka10081 일봉ka10080 분봉
cur_prc단위: 원단위: 원, 부호가 포함된 숫자
open_pric단위: 원단위: 원, 부호가 포함된 숫자
high_pric / low_pric단위: 원단위: 원, 부호가 포함된 숫자

즉 분봉 응답에서는 "open_pric": "-71200" 같은 값이 정상적으로 올 수 있습니다. 그대로 int()를 태우면 캔들이 음수 영역으로 뒤집히고, 이동평균이나 RSI 계산이 통째로 망가집니다. 지표 라이브러리끼리 값이 다른 문제를 한참 파다가, 알고 보니 입력 데이터의 부호였던 경우가 실제로 있습니다.

def to_price(v: str) -> int:
    """키움 차트 가격 항목 → 정수 원.
    분봉(ka10080)은 명세상 부호가 포함되므로 절댓값으로 정규화한다."""
    if v is None or str(v).strip() == "":
        return 0
    return abs(int(str(v).replace(",", "").strip()))

반대로 pred_pre(전일대비)는 부호가 의미를 갖는 항목이므로 여기에 절댓값을 씌우면 안 됩니다. 방향은 pred_pre_sig (1 상한가 · 2 상승 · 3 보합 · 4 하한가 · 5 하락)로도 읽을 수 있습니다.

5. 함정 ④ 전부 String — 그대로 계산 금지

키움 REST API 차트 3종의 응답 항목 타입은 예외 없이 String입니다. cur_prc도, trde_qty도, trde_tern_rt도 문자열입니다. 파이썬에서 문자열끼리 빼면 TypeError가 나고, 더하면 숫자가 아니라 이어 붙습니다. pandasDataFrame을 만들어도 dtype이 object로 잡혀 rolling().mean()이 조용히 이상하게 동작합니다. 이 성질은 잔고조회 kt00018이나 미체결 조회 ka10075에서도 똑같습니다 — 키움 REST API 전반의 규칙이라고 보는 편이 맞습니다.

권하는 구조 — 응답을 받은 직후 변환 계층을 한 번 두고, 그 아래 전략 코드는 숫자만 다루게 합니다. 문자열이 전략 로직까지 흘러 들어가면 어디서 틀어졌는지 추적이 거의 불가능해집니다.

6. 함정 ⑤ stk_cd 접미사와 거래소

차트 3종의 stk_cd 설명에는 거래소별 종목코드라는 단서가 붙어 있고, 예시로 KRX:039490, NXT:039490_NX, SOR:039490_AL가 적혀 있습니다. 같은 종목이라도 어느 거래소 기준의 봉을 받느냐가 코드 접미사로 갈린다는 뜻입니다. 대체거래소가 생긴 뒤 생긴 축이라 KRX·NXT·SOR 주문 라우팅과 같이 읽어야 "백테스트는 통합 기준으로 했는데 주문은 KRX로만 나가더라" 같은 어긋남을 피할 수 있습니다.

7. 실전 코드 — 일봉 N년치 수집기

지금까지의 함정을 전부 반영한 최소 수집기입니다. 토큰 발급은 접근토큰 유효기간·폐기에, 연속조회 헤더 규칙은 cont-yn·next-key에 따로 정리해 두었습니다.

import time, requests, pandas as pd

BASE = "https://api.kiwoom.com"          # 운영 도메인
URL  = BASE + "/api/dostk/chart"

def fetch_daily(token: str, code: str, base_dt: str, until: str) -> pd.DataFrame:
    """base_dt(최근일)에서 시작해 until(YYYYMMDD)까지 과거로 연속조회."""
    rows, cont, nkey = [], "N", ""
    while True:
        headers = {
            "Content-Type":  "application/json;charset=UTF-8",
            "authorization": f"Bearer {token}",
            "api-id":        "ka10081",
        }
        if cont == "Y":                      # 2회차부터만 채운다
            headers["cont-yn"]  = "Y"
            headers["next-key"] = nkey

        body = {
            "stk_cd":       code,            # 예: "005930" (NXT는 005930_NX)
            "base_dt":      base_dt,         # 권리발생일 '이후'에서 출발할 것
            "upd_stkpc_tp": "1",             # 수정주가 적용
        }
        r = requests.post(URL, headers=headers, json=body, timeout=10)
        r.raise_for_status()
        data = r.json()
        if data.get("return_code") not in (0, "0"):
            raise RuntimeError(data.get("return_msg"))

        chunk = data.get("stk_dt_pole_chart_qry") or []
        rows.extend(chunk)

        cont = r.headers.get("cont-yn", "N")
        nkey = r.headers.get("next-key", "")
        oldest = chunk[-1]["dt"] if chunk else ""
        if cont != "Y" or not chunk or oldest <= until:
            break
        time.sleep(0.3)                      # 호출 제한 여유

    df = pd.DataFrame(rows)
    for c in ("open_pric", "high_pric", "low_pric", "cur_prc"):
        df[c] = df[c].map(to_price)          # 부호·문자열 정규화
    df["trde_qty"] = df["trde_qty"].astype("int64")
    df["dt"] = pd.to_datetime(df["dt"], format="%Y%m%d")
    return (df.rename(columns={"cur_prc": "close"})
              .drop_duplicates(subset="dt")
              .sort_values("dt")
              .reset_index(drop=True))

세 군데가 핵심입니다. ① cont-yn은 2회차부터만 채웁니다(첫 요청에 넣으면 안 됩니다). ② time.sleep()이 없으면 반복 호출이 429 호출 제한에 걸립니다. ③ drop_duplicates — 연속조회 경계에서 같은 날짜가 겹쳐 들어오는 경우가 있어 저장 전에 한 번 걸러 둡니다.

받은 데이터는 파일로 남기십시오. 3년치 일봉을 매번 API로 다시 받는 구조는 호출 제한에도 걸리고 재현성도 없습니다. 종목별 parquet·CSV로 적재해 두고 증분만 갱신하는 편이 낫습니다. 데이터 출처를 여러 곳에서 섞어 쓰는 방법은 pykrx·FinanceDataReader·증권사 API 비교에 정리돼 있습니다.

자주 묻는 질문

Q. 분봉은 얼마나 과거까지 받을 수 있나요?

보관 기간은 명세의 요청·응답 항목표에 숫자로 명시돼 있지 않고, 정책은 바뀔 수 있습니다. 실무에서는 연속조회를 끝까지 돌려 실제로 어디서 cont-ynN으로 바뀌는지를 종목·주기별로 한 번 측정해 두는 편이 확실합니다. 필요한 구간이 API 보관 범위를 넘어간다면 별도 데이터 소스를 병행해야 하며, 정확한 보관 정책은 키움 REST API 공식 가이드에서 확인하십시오.

Q. 모의투자에서도 차트 TR이 되나요?

공식 Postman 컬렉션에는 운영(PRD)과 모의투자(MOCK) 두 벌이 있고, 차트 3종은 양쪽 모두에 들어 있습니다. 다만 호출 도메인이 다르므로 코드에서 BASE를 설정값으로 빼 두어야 합니다. 실전 전환 시 도메인만 바꾸면 되도록 만드는 것이 실전 투입 전 검증 3단계의 기본입니다.

Q. 한국투자증권(KIS)과 비교하면 어떤가요?

KIS는 분봉·일봉이 각각 다른 엔드포인트와 tr_id로 나뉘고, 키움은 /api/dostk/chart 하나에 api-id로 갈립니다. KIS 쪽 분봉의 건수 제약은 분봉 데이터 수집 — 당일 30건·과거 120건에 정리해 두었고, 두 증권사의 전반적인 차이는 키움 vs 한국투자증권 API 비교를 참고하십시오.

Q. 이 데이터로 바로 백테스트를 돌려도 되나요?

수정주가와 부호를 정리했다면 입력 데이터로서는 준비된 셈입니다. 다만 가격만으로 나온 수익률은 실제 결과와 다릅니다 — 수수료·세금·슬리피지를 반영해야 합니다. 반영 방법은 백테스트 거래비용 반영에 있습니다. 과거 성과는 미래를 보장하지 않으며, 여기서 다룬 것은 수익 예측이 아니라 데이터 정합성 문제입니다.

마무리

키움 REST API 차트 3종에서 실제로 사람을 잡는 것은 인증도 엔드포인트도 아니었습니다. upd_stkpc_tpbase_dt의 순서, 세 개로 갈리는 응답 리스트 키, 분봉에만 붙는 부호, 전부 String인 타입, 거래소 접미사 — 다섯 가지입니다. 이 중 어느 것도 에러를 내지 않고, 전부 숫자만 조용히 틀리게 만듭니다. 전체 그림은 키움 REST API 자동매매 완전 가이드에 있습니다.

※ 본문의 요청·응답 항목명과 코드값은 키움증권이 배포하는 공식 Postman 컬렉션(운영 PRD) 기준이며 2026-08-23 확인한 내용입니다. 증권사 API 스펙과 정책은 변경될 수 있으므로 제작 직전에는 키움 REST API 공식 가이드를 다시 확인하십시오.

키움 데이터 수집부터 통째로 맡기시겠어요?

수정주가·연속조회·호출 제한까지 정리된 수집기와 전략 봇을 함께 제작합니다.
24시간 빠른 답변 가능합니다.

제작 상담하기