AlgoLab Blog · 한국투자증권 주문 · 2026

KIS API 주문 tr_id 변경 — TTTC0802U는 TTTC0012U로, 정정취소는 TTTC0013U로

한국투자증권 · 주문 실무 2026-09-30 · 알고랩 AlgoLab
한 줄 요약

한국투자증권 KIS Developers 국내주식 주문의 현재 tr_id는 현금 매도 TTTC0011U, 현금 매수 TTTC0012U, 정정취소 TTTC0013U입니다(모의는 앞 글자만 V: VTTC0011U·VTTC0012U·VTTC0013U). 블로그·강의에 널리 퍼진 TTTC0801U·TTTC0802U·TTTC0803U는 구 TR입니다. URL은 그대로(/uapi/domestic-stock/v1/trading/order-cash·order-rvsecncl)이고, 신 규격 공식 예제는 바디에 EXCG_ID_DVSN_CD(KRX·NXT·SOR)를 필수로 넣습니다. 바꿀 것은 tr_id 문자열 3개와 바디 필드 1개입니다.

이 글은 KIS API 에러코드 11가지의 주문 TR 편입니다. 에러코드 허브가 "주문이 왜 거부되나"를 다룬다면, 여기서는 애초에 어떤 tr_id로 주문해야 하나 한 가지만 다룹니다. 근거는 한국투자증권 공식 GitHub 저장소 koreainvestment/open-trading-api의 현재 코드를 2026-09-30에 통째로 받아 대조한 결과입니다.

1. 신·구 tr_id 대조표

주문구 TR (실전 / 모의)신 TR (실전 / 모의)URL
현금 매도TTTC0801U / VTTC0801UTTTC0011U / VTTC0011UPOST /uapi/domestic-stock/v1/trading/order-cash
현금 매수TTTC0802U / VTTC0802UTTTC0012U / VTTC0012U
정정·취소TTTC0803U / VTTC0803UTTTC0013U / VTTC0013UPOST /uapi/domestic-stock/v1/trading/order-rvsecncl
정정취소가능주문조회—TTTC0084R (공식 예제에 모의 분기 없음)GET …/trading/inquire-psbl-rvsecncl

매도가 0011, 매수가 0012입니다. 구 TR에서 매도 0801·매수 0802였던 순서가 그대로 이어졌으니, 끝자리 1=매도·2=매수·3=정정취소로 외우면 됩니다. 모의투자 tr_id가 첫 글자만 V로 바뀌는 규칙도 신 TR에서 그대로입니다(모의→실전 전환 참고).

구 TR은 언제까지 되나? 여러 개발 저장소가 KIS Developers 포털 명세의 안내 — "구 TR은 사전고지 없이 막힐 수 있으니 신 TR로 변경해 이용" — 를 인용합니다. 지금 구 TR로 주문이 나가더라도 보장된 상태가 아닙니다. 종료 일정은 포털 공지에서 직접 확인하십시오. 무인 봇이 어느 날 아침 주문을 전부 거부당하는 것보다 오늘 문자열 세 개를 바꾸는 쪽이 쌉니다.

2. 함정 — 공식 저장소 안에서도 신·구가 섞여 있다

"공식 저장소 코드를 복사했으니 맞겠지"가 통하지 않습니다. 같은 저장소 안에서 폴더에 따라 tr_id가 다릅니다.

경로 (open-trading-api)매수 tr_idEXCG_ID_DVSN_CD
examples_llm/domestic_stock/order_cash/order_cash.pyTTTC0012U필수(없으면 ValueError)
examples_user/domestic_stock/domestic_stock_functions.pyTTTC0012U있음
legacy/Sample01/kis_domstk.pyTTTC0802U없음
backtester/kis_backtest/providers/kis/constants.pyTTTC0802U (CASH_BUY_REAL)없음 (brokerage.py 바디 6필드)

특히 backtester 모듈은 지금도 ORDER_CANCEL_REAL = "TTTC0803U"를 씁니다. 그리고 신 규격 order_cash.py의 docstring 자체에도 구 번호가 남아 있습니다.

# examples_llm/domestic_stock/order_cash/order_cash.py — docstring 원문
※ TTC0802U(현금매수) 사용하셔서 미수매수 가능합니다. 단, 거래하시는 계좌가
  증거금40%계좌로 신청이 되어있어야 가능합니다.

# 같은 파일 본문
if env_dv == "real":
    if ord_dv == "sell":
        tr_id = "TTTC0011U"
    elif ord_dv == "buy":
        tr_id = "TTTC0012U"

설명문은 TTC0802U(글자도 하나 빠짐), 코드는 TTTC0012U입니다. docstring이 아니라 tr_id = 줄을 믿으십시오. ChatGPT 같은 생성형 AI가 구 TR을 자주 내놓는 이유도 학습 자료에 구 번호가 압도적으로 많기 때문입니다(AI가 짠 증권사 API 코드의 함정).

3. 바디 차이 — EXCG_ID_DVSN_CD 한 줄

tr_id만 바꾸고 끝나지 않는 이유가 이것입니다. 구 예제의 현금주문 바디는 5~6필드였고, 신 예제는 거래소 구분을 받습니다.

# 구 — legacy/Sample01/kis_domstk.py (TTTC0802U)
params = {
    "CANO": acct8, "ACNT_PRDT_CD": "01",
    "PDNO": "005930", "ORD_DVSN": "00",
    "ORD_QTY": "1", "ORD_UNPR": "70000",
}

# 신 — examples_llm/.../order_cash.py (TTTC0012U)
params = {
    "CANO": acct8, "ACNT_PRDT_CD": "01",
    "PDNO": "005930", "ORD_DVSN": "00",
    "ORD_QTY": "1", "ORD_UNPR": "70000",
    "EXCG_ID_DVSN_CD": "KRX",   # KRX / NXT / SOR
    "SLL_TYPE": "",              # 매도 시 01 일반매도 · 02 임의매매 · 05 대차매도
    "CNDT_PRIC": "",             # 스탑지정가호가 주문 시 조건가격
}

EXCG_ID_DVSN_CD는 대체거래소 NXT 이후 생긴 값입니다. KRX는 한국거래소, NXT는 넥스트레이드, SOR은 두 시장 중 유리한 쪽으로 보내는 자동주문전송입니다. 공식 테스트 파일 chk_order_cash.py는 실제로 excg_id_dvsn_cd="SOR"로 호출합니다. 어느 값을 기본으로 둘지는 봇의 거래 시간대와 체결 방식에 달린 문제라 NXT·KRX 주문 라우팅에 따로 정리했습니다. 정정취소 TTTC0013U 바디에도 같은 EXCG_ID_DVSN_CD가 필수이고, CNDT_PRIC는 선택입니다.

POST 바디의 키는 대문자, 값은 전부 문자열입니다(공식 설명: "ORD_QTY, ORD_UNPR 등을 String으로 전달"). 실전 주문이면 hashkey 처리도 기존대로 유지합니다.

4. 정정·취소 전에 TTTC0084R로 가능수량부터

TTTC0013U에 필요한 KRX_FWDG_ORD_ORGNO(한국거래소전송주문조직번호)와 ORGN_ODNO(원주문번호)는 주문 응답에서 받습니다. 그런데 공식 설명은 한 단계를 더 요구합니다 — "주식주문(정정취소) 호출 전에 반드시 주식정정취소가능주문조회 호출을 통해 정정취소가능수량(output > psbl_qty)을 확인". 이미 일부 체결된 주문을 전량 취소하려다 수량이 안 맞아 거부되는 일을 막는 절차입니다.

# TTTC0084R — 한 번에 최대 50건, 이후는 tr_cont "M"/"F" + CTX_AREA_FK100/NK100 연속조회
params = {
    "CANO": acct8, "ACNT_PRDT_CD": "01",
    "INQR_DVSN_1": "0",   # 0 주문 · 1 종목
    "INQR_DVSN_2": "0",   # 0 전체 · 1 매도 · 2 매수
    "CTX_AREA_FK100": "", "CTX_AREA_NK100": "",
}
# 응답 output[] 에서 쓰는 필드
#   odno · orgn_odno · psbl_qty(가능수량) · tot_ccld_qty(총체결수량)
#   excg_id_dvsn_cd(거래소ID구분코드) · ord_dvsn_cd · sll_buy_dvsn_cd

응답에 excg_id_dvsn_cd가 들어 있으니, 정정취소 바디의 EXCG_ID_DVSN_CD는 원주문이 나간 거래소 값을 그대로 넣으면 됩니다. 공식 예제에는 TTTC0084R의 모의용 V 분기가 없어서, 모의투자 봇이면 이 조회가 안 될 수 있다는 전제로 짜 두는 편이 안전합니다(모의 지원 여부는 포털 확인). 정정취소 흐름 전체는 KIS 정정·취소 가이드에 있습니다.

응답 키의 대소문자 — 공식 테스트 파일의 컬럼 매핑이 order-cash는 대문자(KRX_FWDG_ORD_ORGNO·ODNO·ORD_TMD), order-rvsecncl은 소문자(krx_fwdg_ord_orgno·odno·ord_tmd)로 적혀 있습니다. 봇에서는 {k.upper(): v for k, v in output.items()}로 한 번 정규화하고 읽으면 어느 쪽이 와도 깨지지 않습니다.

5. 내 코드에서 구 TR 찾아 바꾸기

tr_id를 문자열로 여기저기 박아 둔 코드라면 한 곳만 바꾸고 나머지를 놓치기 쉽습니다. 아래는 프로젝트 폴더 전체에서 구 TR을 찾는 스크립트와, 앞으로 한 곳에서만 관리하도록 만든 매핑입니다.

from pathlib import Path
import re

OLD_TO_NEW = {
    "TTTC0801U": "TTTC0011U", "VTTC0801U": "VTTC0011U",   # 현금 매도
    "TTTC0802U": "TTTC0012U", "VTTC0802U": "VTTC0012U",   # 현금 매수
    "TTTC0803U": "TTTC0013U", "VTTC0803U": "VTTC0013U",   # 정정취소
}
pat = re.compile("|".join(OLD_TO_NEW))

for f in Path(".").rglob("*.py"):
    for n, line in enumerate(f.read_text(encoding="utf-8", errors="ignore").splitlines(), 1):
        for m in pat.findall(line):
            print(f"{f}:{n}  {m} -> {OLD_TO_NEW[m]}")

# 이후에는 문자열 대신 이 함수 하나만 쓴다
def order_tr_id(side: str, paper: bool) -> str:
    base = {"sell": "TTTC0011U", "buy": "TTTC0012U", "revise_cancel": "TTTC0013U"}[side]
    return ("V" + base[1:]) if paper else base
# 예시 프로젝트에서 실행한 출력 형태
$ python find_old_tr.py
bot/order.py:41  TTTC0802U -> TTTC0012U
bot/order.py:58  TTTC0801U -> TTTC0011U
bot/cancel.py:12  VTTC0803U -> VTTC0013U

바꾼 뒤에는 모의투자에서 1주 지정가 주문 → 조회 → 취소를 한 바퀴 돌려 봅니다. 응답의 rt_cd가 "0"이고 output에 ODNO가 오면 주문이 접수된 것입니다. rt_cd가 "0"이 아니면 msg_cd·msg1을 에러코드 표와 대조하십시오. 초당 호출 한도(EGW00201)는 신 TR에서도 똑같이 걸린다고 보고 설계하십시오(EGW00201 해결).

자주 묻는 것

KIS API 국내주식 매수 tr_id는 TTTC0802U인가요, TTTC0012U인가요?

현재 공식 저장소 예제 기준으로 현금 매수는 TTTC0012U(모의 VTTC0012U), 현금 매도는 TTTC0011U(모의 VTTC0011U)입니다. TTTC0802U·TTTC0801U는 구 TR이며, 사전고지 없이 막힐 수 있다는 안내가 인용되므로 신 TR로 바꾸는 것이 안전합니다.

정정·취소 tr_id도 바뀌었나요?

네. 구 TTTC0803U(모의 VTTC0803U)가 신 TTTC0013U(모의 VTTC0013U)입니다. URL /uapi/domestic-stock/v1/trading/order-rvsecncl은 같고, 신 규격 바디에는 EXCG_ID_DVSN_CD가 필수로 들어갑니다.

tr_id만 바꾸면 되나요?

신 규격 공식 예제는 주문 바디에 EXCG_ID_DVSN_CD(KRX·NXT·SOR)를 필수로 넣습니다. 구 예제 바디에는 이 필드가 없으므로 tr_id와 함께 바디에 한 줄을 추가하고, 모의투자에서 1주 주문·조회·취소를 한 바퀴 돌려 확인하십시오.

공식 저장소 코드를 그대로 쓰면 신 TR이 되나요?

폴더에 따라 다릅니다. examples_llm·examples_user는 신 TR(TTTC0012U)이지만 legacy/Sample01과 backtester 모듈은 구 TR(TTTC0802U·TTTC0803U)을 씁니다. order_cash.py의 설명문에도 구 번호가 남아 있으니 tr_id = 줄을 기준으로 판단하십시오.

고지. 이 글은 기술 자료이며 특정 종목·전략을 권유하지 않습니다. tr_id·필드는 한국투자증권 공식 GitHub 저장소(koreainvestment/open-trading-api)를 2026-09-30 기준으로 대조했지만, 증권사 API 스펙은 예고 없이 바뀌므로 실제 적용 전 KIS Developers 포털 공식 문서를 다시 확인하십시오. 구 TR 종료 일정은 포털 공지를 따릅니다. 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객이 지정한 규칙을 프로그램으로 구현해 드리는 도구 제공자입니다.

구 TR로 도는 봇, 점검이 필요하신가요?

기존 KIS 봇의 tr_id·거래소 구분·정정취소 흐름을 신 규격으로 옮겨 드립니다. 24시간 빠른 답변 가능합니다.

상담 문의하기