KIS 예약주문 API CTSC0008U — 체결 통보가 없다
한국투자증권 KIS API의 국내주식 예약주문은 CTSC0008U(접수)·CTSC0004R(조회)·CTSC0009U/CTSC0013U(취소/정정) 3개 엔드포인트로 이루어지고, 처리 결과가 통보되지 않습니다. 접수는 15:40 ~ 다음 영업일 07:30에만 되고(23:40~00:10 서버 초기화 시간 제외), 예약이 실제 주문으로 나갔는지·거부됐는지는 봇이 장 시작 전에 CTSC0004R로 직접 조회해야 압니다. 공식 저장소 legacy/README.md의 "모의투자 제공 여부" 칸은 세 API 모두 비어 있어, 모의 계좌에서는 리허설할 수 없습니다.
목차
- 예약주문 API 3종 — tr_id·URL·모의 지원
- 접수 가능 시간 — 하루 중 언제 되나
- 일반예약 vs 기간예약 —
RSVN_ORD_END_DT하나 차이 - 요청 실물 — 접수와 조회
- 함정 6가지
- 봇 설계 — 접수 → 장전 확인 → 재시도
- 자주 묻는 질문
1. 예약주문 API 3종 — tr_id·URL·모의 지원
아래 표는 한국투자증권 공식 GitHub 저장소 koreainvestment/open-trading-api의 examples_llm/domestic_stock/order_resv* 예제 코드와 legacy/README.md·legacy/postman/README.md의 제공 여부 표를 대조한 것입니다.
| 기능 | tr_id | 메서드 · URL | 모의 |
|---|---|---|---|
| 주식예약주문 (접수) | CTSC0008U | POST /uapi/domestic-stock/v1/trading/order-resv | 공란 |
| 주식예약주문조회 | CTSC0004R | GET /uapi/domestic-stock/v1/trading/order-resv-ccnl | 공란 |
| 주식예약주문정정취소 | 취소 CTSC0009U · 정정 CTSC0013U | POST /uapi/domestic-stock/v1/trading/order-resv-rvsecncl | 공란 |
Postman README의 "실전투자 제공 여부" 칸은 세 API 모두 ⭕입니다. 즉 실전 계좌 전용입니다. 모의투자에서 막히는 API 전체 목록은 허브 글 KIS 모의투자 한계 — 안 되는 API 10가지에 정리해 두었습니다.
2. 접수 가능 시간 — 하루 중 언제 되나
data.csv(공식 저장소 MCP 폴더의 API 메타)에 실린 CTSC0008U 유의사항 원문을 요약하면 다음과 같습니다.
- 접수 가능: 15시 40분 ~ 다음 영업일 7시 30분
- 접수 불가: 23시 40분 ~ 00시 10분(서버 초기화 작업)
- 기간예약 자동배치: 약 15시 35분 ~ 15시 55분 — 이 사이 예약주문 조회가 제한될 수 있음
장중(09:00~15:30)에는 예약주문 자체를 접수할 수 없습니다. 장중 주문은 일반 현금주문 TTTC0012U(매수·매도 공통, 구 TTTC0802U/TTTC0801U)로 내야 합니다 — 변경 내역은 TTTC0012U 글 참고. "영업일"이 기준이므로 연휴 직전 접수분은 연휴 다음 영업일에 나갑니다.
3. 일반예약 vs 기간예약 — RSVN_ORD_END_DT 하나 차이
| 구분 | RSVN_ORD_END_DT | 동작 |
|---|---|---|
| 일반예약주문 | 미입력 | 최초 도래하는 영업일에 1회 전송 |
| 기간예약주문 | YYYYMMDD 입력 | 최초 수량 중 미체결 수량을 종료일까지 매 영업일 다시 전송 |
- 종료일은 익영업일부터 달력일 기준 최대 30일(공휴일 포함)까지 입력 가능
- 기간예약주문은 계좌당 최대 1,000건
- 접수 처리 순서는 일반/기간 구분 없이 신청일자가 빠른 주문 우선(단, 자동배치 시간 중 접수분은 당일에 한해 순서와 무관하게 처리될 수 있음)
기간예약은 "목표가에 올 때까지 매일 걸어 두는" 지정가 매수에 맞습니다. 다만 매일 같은 가격으로 다시 나가므로, 봇이 이미 다른 경로로 같은 종목을 샀다면 중복 체결이 납니다. 봇 쪽 포지션 장부와 예약 목록을 매일 대조해야 합니다.
4. 요청 실물 — 접수와 조회
접수 (CTSC0008U) — 공식 Postman 컬렉션 본문
POST {{PROD}}/uapi/domestic-stock/v1/trading/order-resv
tr_id: CTSC0008U
{
"CANO": "{{CANO_REAL}}",
"ACNT_PRDT_CD": "01",
"PDNO": "005930",
"ORD_QTY": "1",
"ORD_UNPR": "55000",
"SLL_BUY_DVSN_CD": "02",
"ORD_DVSN_CD": "00",
"ORD_OBJT_CBLC_DVSN_CD": "10"
}
파라미터 값의 뜻: SLL_BUY_DVSN_CD 01 매도·02 매수 / ORD_DVSN_CD 00 지정가·01 시장가·02 조건부지정가·05 장전 시간외 / ORD_OBJT_CBLC_DVSN_CD 10 현금(12~28은 대출·상환 코드). 성공 시 output에 RSVN_ORD_SEQ(예약주문 순번)가 옵니다 — 정정·취소에 필요하니 반드시 저장하십시오.
조회 (CTSC0004R) — 공식 Postman 컬렉션 URL
GET {{PROD}}/uapi/domestic-stock/v1/trading/order-resv-ccnl
?RSVN_ORD_ORD_DT=20220729&RSVN_ORD_END_DT=20220810&RSVN_ORD_SEQ
&TMNL_MDIA_KIND_CD=00&CANO={{CANO_REAL}}&ACNT_PRDT_CD=01
&PRCS_DVSN_CD=0&CNCL_YN=Y&PDNO=&SLL_BUY_DVSN_CD
&CTX_AREA_FK200=&CTX_AREA_NK200=
tr_id: CTSC0004R
한 번에 최대 20건이고, 그 이상은 응답 헤더 tr_cont가 M/F일 때 ctx_area_fk200·ctx_area_nk200을 다음 요청에 넣어 연속조회합니다(요청 헤더 tr_cont: N). 응답의 주요 필드는 다음과 같습니다.
# CTSC0004R output — 공식 예제 column_mapping 발췌
rsvn_ord_seq 예약주문 순번
rsvn_ord_rcit_dt 예약주문접수일자
pdno / kor_item_shtn_name 종목코드 / 종목명
ord_rsvn_qty 주문예약수량
ord_rsvn_unpr 주문예약단가
tot_ccld_qty 총체결수량
tot_ccld_amt 총체결금액
odno 주문번호 ← 실제 주문으로 전환됐을 때
prcs_rslt 처리결과
rjct_rson2 거부사유2
rsvn_end_dt 예약종료일자
파이썬 — 접수 후 다음 날 아침 확인
import kis_auth as ka # 공식 저장소 examples_user/kis_auth.py
def place_resv(pdno, qty, price, side="02", end_dt=None):
body = {"CANO": ka.getTREnv().my_acct, "ACNT_PRDT_CD": ka.getTREnv().my_prod,
"PDNO": pdno, "ORD_QTY": str(qty), "ORD_UNPR": str(price),
"SLL_BUY_DVSN_CD": side, "ORD_DVSN_CD": "00",
"ORD_OBJT_CBLC_DVSN_CD": "10"} # 키는 전부 대문자
if end_dt:
body["RSVN_ORD_END_DT"] = end_dt # 기간예약
res = ka._url_fetch("/uapi/domestic-stock/v1/trading/order-resv",
"CTSC0008U", "", body, postFlag=True)
if not res.isOK():
res.printError(); return None
return res.getBody().output["RSVN_ORD_SEQ"] # 정정·취소용으로 저장
def check_resv(start_dt, end_dt):
params = {"RSVN_ORD_ORD_DT": start_dt, "RSVN_ORD_END_DT": end_dt,
"TMNL_MDIA_KIND_CD": "00", "CANO": ka.getTREnv().my_acct,
"ACNT_PRDT_CD": ka.getTREnv().my_prod, "PRCS_DVSN_CD": "0",
"CNCL_YN": "Y", "RSVN_ORD_SEQ": "", "PDNO": "", "SLL_BUY_DVSN_CD": "",
"CTX_AREA_FK200": "", "CTX_AREA_NK200": ""}
res = ka._url_fetch("/uapi/domestic-stock/v1/trading/order-resv-ccnl",
"CTSC0004R", "", params)
return res.getBody().output if res.isOK() else []
위 코드는 연속조회를 생략한 최소 형태입니다. 20건을 넘으면 공식 order_resv_ccnl.py처럼 tr_cont를 보고 재귀 호출하십시오.
5. 함정 6가지
① 처리 결과가 통보되지 않는다
원문: "예약주문 처리내역은 통보되지 않으므로 주문처리일 장 시작전에 반드시 주문처리 결과를 확인". 실시간 체결통보 웹소켓(H0STCNI0)만 믿고 있으면 거부된 예약을 모른 채 하루를 넘깁니다. 장 시작 전 CTSC0004R 조회는 선택이 아니라 필수 단계입니다.
② 모의 계좌에서는 tr_id가 엉뚱하게 바뀐다
공식 kis_auth.py의 _url_fetch()는 tr_id 첫 글자가 T·J·C이면 모의투자에서 "V" + ptr_id[1:]로 바꿉니다. CTSC0008U는 모의에서 VTSC0008U로 나가는데, 이 API는 모의 제공 목록에 없습니다. 모의에서 실패했다고 코드를 고치기 전에 애초에 모의로는 검증할 수 없는 API라는 점을 먼저 확인하십시오. 실전에서 1주·저가 종목으로 접수→조회→취소를 한 바퀴 돌리는 것이 유일한 리허설입니다.
③ POST 본문 키는 대문자
원문: "POST API의 경우 BODY값의 key값들을 대문자로 작성". cano·pdno처럼 소문자로 보내면 안 됩니다. 반대로 공식 파이썬 함수의 인자명은 소문자(cano, rsvn_ord_end_dt)라서 두 표기가 섞이기 쉽습니다.
④ 시장가·장전 시간외는 ORD_UNPR = "0"
ORD_DVSN_CD가 01(시장가)이나 05(장전 시간외)면 단가를 비우지 말고 "0"을 넣습니다. 신규상장처럼 시가가 형성되지 않은 종목의 시장가는 주문처리일에 거부 사유로 명시돼 있습니다.
⑤ 거부는 접수가 아니라 다음 날 아침에 난다
공식 유의사항이 꼽는 거부 사유: 매수가능금액 부족, 매도가능수량 부족, 주문수량/호가단위 오류, 상/하한폭 변경, 거래서비스 미신청 등. 특히 익일 예상 상·하한가는 조회 시점 현재가로 계산돼, 유·무상증자·배당락·감자·액면변경이 끼면 예약 가격이 가격제한폭 밖으로 밀려 거부될 수 있습니다. 매도 예약이라면 전날 밤에 매도가능수량(TTTC8408R)을 한 번 더 확인해 두면 거부를 줄일 수 있습니다.
⑥ 기간예약 취소는 이미 나간 주문을 취소하지 않는다
원문: 영업일 장 시작 후 기간예약 내역을 취소하면 "해당시점 이후의 예약주문이 취소되는 것으로, 일반주문으로 이미 전환된 주문에는 영향을 미치지 않습니다". 그날 아침 이미 시장에 나간 주문은 CTSC0009U가 아니라 일반 정정취소 TTTC0013U(조회의 odno를 원주문번호로)로 따로 취소해야 합니다 — 주문 취소·정정 글 참고.
덤 — 정정·취소 필수값이 예제마다 다르다. 최신 examples_llm/.../order_resv_rvsecncl.py는 RSVN_ORD_SEQ·RSVN_ORD_ORGNO·RSVN_ORD_ORD_DT를 모두 [필수]로 검사하는데, 구버전 legacy/Sample01/kis_domstk.py는 뒤의 두 개를 "[정정/취소] 입력불필요"로 비워 보냅니다. 조회 응답 매핑에도 rsvn_ord_orgno는 없습니다. 실전 호출 전에 KIS Developers 포털의 최신 명세를 확인하십시오.
6. 봇 설계 — 접수 → 장전 확인 → 재시도
- 15:40 이후: 종가로 신호 계산 →
CTSC0008U접수 →RSVN_ORD_SEQ를 DB에 저장 - 다음 영업일 장 시작 전:
CTSC0004R조회 →prcs_rslt·rjct_rson2로 거부 여부 판정 - 거부된 건: 사유를 텔레그램 등으로 알림 → 조건이 여전히 유효하면 장중
TTTC0012U로 일반주문 - 전환된 건:
odno로 일별 주문체결조회TTTC0081R에서 체결 추적(주문체결조회 글)
예약주문의 장점은 "봇 서버가 아침에 죽어도 주문은 나간다"는 점입니다. 단점은 위 2번을 빼먹으면 거부를 모른다는 점입니다. 둘 중 어느 쪽 위험이 큰지는 전략마다 다르므로, 시가 근처 체결이 중요하지 않은 스윙 전략이라면 예약주문, 장중 가격에 반응해야 하는 전략이라면 일반주문이 맞습니다. 에러 코드 대응은 KIS API 에러코드 정리를 함께 보십시오.
7. 자주 묻는 질문
KIS API 예약주문은 몇 시에 접수할 수 있나요?
공식 유의사항 기준 15시 40분부터 다음 영업일 7시 30분까지이며, 23시 40분~00시 10분은 서버 초기화로 접수할 수 없습니다. 장중에는 예약주문이 아니라 일반 현금주문 TTTC0012U를 써야 합니다.
예약주문이 체결됐는지 어떻게 알 수 있나요?
예약주문 처리내역은 통보되지 않습니다. 주문처리일 장 시작 전에 주식예약주문조회 CTSC0004R(GET /uapi/domestic-stock/v1/trading/order-resv-ccnl)로 처리결과(prcs_rslt)와 거부사유(rjct_rson2)를 확인하고, 실제 주문으로 전환된 건은 주문번호(odno)로 일별 주문체결조회에서 추적합니다.
모의투자 계좌로 예약주문을 테스트할 수 있나요?
공식 저장소 legacy/README.md의 모의투자 제공 여부 칸이 국내주식 예약주문·조회·정정취소 세 API 모두 비어 있습니다. 공식 kis_auth.py는 모의에서 CTSC0008U를 VTSC0008U로 바꿔 보내지만 이 API는 모의 제공 목록에 없으므로, 실전 계좌에서 소량으로 접수·조회·취소를 확인하는 방법밖에 없습니다.
기간예약주문은 언제까지 걸어 둘 수 있나요?
RSVN_ORD_END_DT에 종료일을 넣으면 기간예약주문이 되고, 익영업일부터 달력일 기준(공휴일 포함) 최대 30일까지 입력할 수 있습니다. 최초 수량 중 미체결분이 종료일까지 매 영업일 다시 전송되며, 계좌당 최대 1,000건으로 제한됩니다. 수치는 바뀔 수 있으니 KIS Developers 공식 문서를 확인하십시오.