KIS 모의투자 한계 — API로 뭘 검증하고 뭘 못 하나
tr_id 세 가지가 동시에 바뀌고 그 위에 실전에 없는 제약이 얹힙니다 — 신용·예약주문을 포함해 API 10가지는 호출 자체가 막히고, 미국주식은 00 지정가만 되며, 주문체결조회는 한 번에 15건까지(실전 100건)만 나옵니다. 그래서 모의로 검증되는 것은 인증·주문 왕복·파싱·예외처리이고, 검증되지 않는 것은 주문유형·체결 품질·시세 정확도입니다.
"일단 모의투자로 돌려 보고 문제 없으면 실전으로 옮기죠." 맞는 순서이긴 한데, 모의투자가 무엇을 대신 검증해 주고 무엇은 대신해 주지 않는지를 모르고 넘어가면 실전 첫날에 처음 보는 오류가 쏟아집니다.
이 글은 한국투자증권 공식 저장소 koreainvestment/open-trading-api를 2026-09-09에 직접 받아 모의투자 관련 문구를 전수로 훑은 결과이며, 공식 문서·예제 코드에 적힌 문장만 옮겼습니다. 전환 절차 자체는 모의 → 실전 전환에서 바꿀 것 6가지에 있고, 이 글은 그 앞 단계인 "모의로 어디까지 확인되나"를 다룹니다. 발급부터 처음이라면 KIS API 발급 30분 완성을 먼저 보세요.
이 글에서 확인하는 것
- 모의로 바꾸면 동시에 바뀌는 세 가지
- 아예 호출이 안 되는 API 10가지
- 되는데 다르게 되는 것 4가지
- 시세 데이터 자체가 다르다 — 공식 경고
- 검증되는 것 / 안 되는 것 대조표
- 전환 전에 한 번 돌리는 점검 코드
1. 모의로 바꾸면 동시에 바뀌는 세 가지
모의투자 전환은 계좌번호 하나를 갈아 끼우는 일이 아닙니다. 접속 주소, 인증 키, 거래 코드가 한꺼번에 바뀝니다. 공식 설정 파일 kis_devlp.yaml과 인증 모듈 kis_auth.py를 기준으로 정리하면 이렇습니다.
| 바뀌는 것 | 실전 (prod) | 모의 (vps) |
|---|---|---|
| REST 도메인 | https://openapi.koreainvestment.com:9443 | https://openapivts.koreainvestment.com:29443 |
| 웹소켓 | ws://ops.koreainvestment.com:21000 | ws://ops.koreainvestment.com:31000 |
| 앱키 / 앱시크릿 | 실전 발급분 | 모의 발급분 (별도) |
| 계좌 항목 | my_acct_stock | my_paper_stock (8자리) |
| 라이브러리 인자 | svr="prod" | svr="vps" |
주문 tr_id | TTTC0802U | VTTC0802U |
웹소켓은 호스트가 같고 포트만 21000 → 31000으로 바뀐다는 점을 놓치기 쉽습니다. 도메인 문자열만 치환하는 코드는 REST는 바뀌는데 웹소켓은 실전에 그대로 붙습니다.
tr_id 치환은 "첫 글자만" 바뀐다 — 공식 코드 원문
공식 kis_auth.py의 헤더 생성부입니다.
tr_id = ptr_id
if ptr_id[0] in ("T", "J", "C"): # 실전투자용 TR id 체크
if isPaperTrading(): # 모의투자용 TR id 식별
tr_id = "V" + ptr_id[1:]
headers["tr_id"] = tr_id # 트랜젝션 TR id
headers["custtype"] = "P" # 일반(개인고객,법인고객) "P", 제휴사 "B"
여기서 읽어야 할 것 — 치환 대상은 첫 글자가 T·J·C인 TR뿐입니다. 그러니까 FHKST01010100(주식현재가), FHKST03010100(일봉), HHDFS00000300(해외현재가)처럼 F·H로 시작하는 시세 계열 TR은 모의와 실전이 같은 코드입니다. 이 사실이 4절의 함정으로 이어집니다.
2. 아예 호출이 안 되는 API 10가지
공식 저장소에서 "모의투자는 사용 불가" 또는 "실전계좌만 지원되며 모의투자는 미지원됩니다"로 명시된 것들입니다. 문구는 원문 그대로입니다.
| # | API | tr_id | 공식 문구 |
|---|---|---|---|
| 1 | 국내주식 신용매수 | TTTC0852U | ※ 모의투자는 사용 불가합니다. |
| 2 | 국내주식 신용매도 | TTTC0851U | |
| 3 | 주식 정정취소가능주문조회 | TTTC8036R | * 모의투자 사용 불가 |
| 4 | 국내주식 예약주문조회 | CTSC0004R | * 모의투자 사용 불가 |
| 5 | 국내주식 예약취소주문 | CTSC0009U | * 모의투자 사용 불가 |
| 6 | 국내주식 예약정정주문 | CTSC0013U | |
| 7 | 해외주식 예약주문조회 | order_resv_list | ※ 모의투자는 사용 불가합니다. |
| 8 | 해외주식 주문체결내역 | inquire_ccnl | 모의투자 미지원 |
| 9 | 지수선물·지수옵션 실시간체결가/호가 | 실시간-010·011·014·015 | 실전계좌만 지원되며 모의투자는 미지원됩니다. |
| 10 | 주식선물 실시간체결가/호가, 상품선물 실시간호가 | 실시간-023·029·030 |
봇 입장에서 이게 무슨 뜻인가
- 정정·취소 로직이 반쪽만 돈다.
TTTC8036R이 없으므로 "지금 취소 가능한 주문이 뭔지" 되묻는 경로가 모의에 없습니다.ODNO를 직접 보관했다가TTTC0803U로 취소하는 흐름만 시험되고, 원주문번호를 잃어버리는 문제는 실전에서 처음 만납니다. - 예약주문 전략은 리허설이 불가능하다.
CTSC0004R·CTSC0009U·CTSC0013U가 전부 막혀 있어, 장 시작 전에 예약을 걸어 두는 방식은 모의에서 한 줄도 검증되지 않습니다. - 파생 봇의 시세 수신부는 모의로 못 만든다. 국내선물옵션 실시간시세가 실전 전용이라 지수선물·주식선물 봇의 웹소켓 파싱은 실전 키로만 확인됩니다. 신용주문(
TTTC0852U)도 같은 이유로 경로 자체를 시험할 수 없습니다.
3. 되는데 다르게 되는 것 4가지
2절이 "막힌 문"이라면 여기는 열려 있는데 안쪽 구조가 다른 방입니다. 사고는 대체로 여기서 납니다.
① 주문유형 — 모의는 지정가(00)만
해외주식 주문 ord_dvsn 항목의 공식 설명 원문입니다.
[Header tr_id TTTT1002U(미국 매수 주문)]
00 : 지정가 32 : LOO(장개시지정가) 34 : LOC(장마감지정가)
* 모의투자 VTTT1002U(미국 매수 주문)로는 00:지정가만 가능
[Header tr_id TTTT1006U(미국 매도 주문)]
00 : 지정가 31 : MOO(장개시시장가) 32 : LOO 33 : MOC(장마감시장가) 34 : LOC
* 모의투자 VTTT1001U(미국 매도 주문)로는 00:지정가만 가능
[Header tr_id TTTS1001U(홍콩 매도 주문)]
00 : 지정가 50 : 단주지정가
* 모의투자 VTTS1001U(홍콩 매도 주문)로는 00:지정가만 가능
두 번 읽어야 하는 줄 — 미국 매도는 실전이 TTTT1006U인데 모의는 VTTT1001U입니다. 숫자까지 바뀝니다. 1절에서 본 "첫 글자만 V로" 규칙을 그대로 믿고 VTTT1006U를 보내면 거부됩니다. 미국 매수는 TTTT1002U → VTTT1002U로 숫자가 같아서, 매수만 테스트한 코드는 매도에서 처음 깨집니다.
더 아픈 쪽은 주문유형입니다. 장마감 지정가(LOC)나 장마감 시장가(MOC)로 청산하는 전략은 모의에서 단 한 번도 실행해 볼 수 없습니다. 미국주식 주문의 ORD_DVSN을 미리 정해 두고 실전 첫날은 지정가로만 시작하는 편이 안전합니다.
② 주문체결조회 — 실전 100건, 모의 15건
주식일별주문체결조회(inquire_daily_ccld) 공식 설명입니다.
이건 드물게 모의가 더 유리한 항목입니다. 15건에서 잘리므로 tr_cont·ctx_area_fk100 연속조회를 빼먹은 버그가 주문체결조회 단계에서 모의부터 드러납니다. 다만 하루 주문이 15건 미만이면 연속조회 코드가 한 번도 실행되지 않은 채 실전으로 갑니다.
③ 정렬순서 — 조용히 무시된다
sort_sqn : DS : 정순 AS : 역순 ※ 모의투자계좌의 경우 정렬순서 사용불가(Default : DS(정순))
최신 체결부터 보려고 AS를 넣어도 모의에서는 에러가 아니라 정순으로 그냥 나옵니다. 실패보다 위험한 것이 이런 조용한 무시입니다. "제일 위에 있는 게 최신"이라고 가정한 파싱 코드는 모의에서 멀쩡히 돌다가 실전에서 순서가 뒤집힙니다.
④ 호출 간격 — 모의가 10배 느리다
공식 백테스터의 kis_auth.py 레이트리미터입니다.
# Rate Limiting: 모의투자 0.5초, 실전 0.05초 최소 간격 보장
with _rate_lock:
now = time.monotonic()
elapsed = now - _last_api_call_time
if elapsed < _smartSleep:
wait_time = _smartSleep - elapsed
time.sleep(wait_time)
_last_api_call_time = time.monotonic()
모의 0.5초 = 초당 2회, 실전 0.05초 = 초당 20회. 공식 README도 같은 취지를 적어 두었습니다 — "모의투자 계좌는 REST API 호출 제한이 낮습니다. 단일 조회에는 문제없으나, 파라미터 최적화처럼 연속 호출이 많으면 실전투자 계좌를 권장합니다."
방향을 헷갈리지 마십시오. 모의에서 호출 제한에 안 걸렸다고 실전이 안전한 것이 아닙니다. 실전에서는 더 빠르게 돌릴 수 있게 되므로 종목 수를 늘리다가 EGW00201 초당 거래건수 초과가 그때 처음 나타납니다. 레이트리미터는 모의 기준이 아니라 실전 기준으로 설계해 두어야 합니다.
4. 시세 데이터 자체가 다르다 — 가장 안 알려진 것
한국투자증권이 공식 저장소에 함께 올려 둔 백테스터의 README에는 이런 경고가 있습니다.
공식 원문 — "모의투자보다 실전투자 API 키 사용을 권장합니다. 모의투자 환경은 체결 가능한 주문 수량(유동성)이 실제 시장보다 크게 제한되어, 백테스트 데이터 수집 시 일부 종목·기간에서 시세가 누락되거나 부정확하게 채워질 수 있습니다. 실전투자 API 키를 사용하면 실제 체결 기준의 가격·거래량 데이터로 더 정확한 백테스트 결과를 얻을 수 있습니다."
같은 저장소의 최적화 예제에도 "모의투자 계좌는 REST API 초당 호출 제한이 낮아 다수의 백테스트를 연속 실행하는 최적화에 적합하지 않습니다"라는 주석이 붙어 있습니다.
왜 이걸 놓치기 쉬운가. 1절에서 본 대로 시세 TR은 F로 시작해서 모의와 실전이 같은 코드입니다. FHKST03010100을 모의 키로 부르든 실전 키로 부르든 요청 자체는 똑같이 생겼습니다. 그래서 "같은 API니까 같은 데이터겠지"라고 생각하게 되는데, 다른 것은 TR이 아니라 그 뒤의 환경입니다. 모의 키로 모은 일봉으로 백테스트를 돌리면 검증한 적 없는 데이터 위에 결론을 세우는 셈입니다.
5. 대조표 — 모의로 검증되는 것 / 안 되는 것
지금까지의 근거를 봇 제작 관점의 체크리스트로 접었습니다.
| 검증 항목 | 모의로 되나 | 근거 |
|---|---|---|
앱키·앱시크릿 → access_token 발급, 24시간 만료 갱신 | 된다 | 인증 흐름 동일 |
국내 현금주문 왕복 (주문 → ODNO → 체결조회) | 된다 | VTTC0802U / VTTC0801U |
잔고·체결 연속조회 루프 (tr_cont) | 된다 (오히려 유리) | 15건에서 잘려 버그가 빨리 드러남 |
응답 파싱·예외처리 (rt_cd/msg_cd) | 된다 | 응답 구조 동일 |
| 국내주식 실시간 체결 웹소켓 수신 | 된다 | 포트 31000 |
| 주문유형별 동작 (LOC·MOC·LOO·시장가) | 안 된다 | 00 지정가만 |
| 정정·취소 가능 주문 조회 | 안 된다 | TTTC8036R 미지원 |
| 예약주문 (조회·정정·취소) | 안 된다 | CTSC 3종 미지원 |
| 신용 매수·매도 | 안 된다 | TTTC0851U·TTTC0852U 미지원 |
| 선물옵션 실시간 시세 수신 | 안 된다 | 실전계좌만 지원 |
| 체결 품질 (부분체결·미체결·슬리피지) | 안 된다 | 체결 가능 수량이 크게 제한 |
| 시세 정확도 / 백테스트 데이터 수집 | 안 된다 | 누락·부정확 가능(공식 경고) |
| 호출 한도 튜닝 (종목 수 확장) | 안 된다 | 0.5초 vs 0.05초 |
| 정렬순서 역순 처리 | 안 된다 | sort_sqn=AS 사용불가 |
가운데 붉은 칸이 모의로는 한 번도 실행되지 않는 코드입니다. 실전 전환 첫날 사고는 대부분 여기서 납니다.
실전 전환은 "코드를 고치는 일"보다 "무엇을 어떤 순서로 열어 볼지 정하는 일"에 가깝습니다. 지금 짜 둔 구조를 기준으로 어디부터 열지 같이 정해 드립니다.
전환 순서 상담하기 →6. 전환 전에 한 번 돌리는 점검 코드
지금까지의 제약을 한 파일에 모아 두면 모의에서 "왜 안 되지"로 시간을 쓰는 일이 사라집니다.
# kis_env.py — 모의/실전 전환에서 반드시 갈리는 것만 한곳에 모은다
# 근거: koreainvestment/open-trading-api 공식 저장소 (2026-09-09 확인)
# 스펙은 바뀔 수 있으므로 배포 전 KIS Developers 문서로 재확인할 것
REST = {
"prod": "https://openapi.koreainvestment.com:9443",
"vps": "https://openapivts.koreainvestment.com:29443",
}
WS = {
"prod": "ws://ops.koreainvestment.com:21000",
"vps": "ws://ops.koreainvestment.com:31000",
}
# 모의투자에서 호출 자체가 막히는 TR — 미리 걸러서 "왜 안 되지"를 없앤다
PAPER_UNSUPPORTED = {
"TTTC0852U": "국내주식 신용매수",
"TTTC0851U": "국내주식 신용매도",
"TTTC8036R": "주식 정정취소가능주문조회",
"CTSC0004R": "국내주식 예약주문조회",
"CTSC0009U": "국내주식 예약취소주문",
"CTSC0013U": "국내주식 예약정정주문",
}
# 첫 글자 치환 규칙(T/J/C -> V)으로 안 잡히는 예외
TR_PAPER_OVERRIDE = {
"TTTT1006U": "VTTT1001U", # 미국 매도: 숫자까지 바뀐다
}
# 모의에서 00(지정가) 외 주문유형이 거부되는 주문 TR
PAPER_LIMIT_ORDER_ONLY = {"VTTT1002U", "VTTT1001U", "VTTS1001U"}
def to_paper_tr(tr_id: str) -> str:
"""실전 tr_id를 모의 tr_id로."""
if tr_id in PAPER_UNSUPPORTED:
raise RuntimeError(f"{tr_id}({PAPER_UNSUPPORTED[tr_id]})는 모의투자 미지원입니다")
if tr_id in TR_PAPER_OVERRIDE:
return TR_PAPER_OVERRIDE[tr_id]
if tr_id[0] in ("T", "J", "C"):
return "V" + tr_id[1:]
return tr_id # FHKST/HHDFS 계열 시세 TR은 그대로
def check_ord_dvsn(tr_id: str, ord_dvsn: str) -> None:
if tr_id in PAPER_LIMIT_ORDER_ONLY and ord_dvsn != "00":
raise RuntimeError(
f"모의투자 {tr_id}는 00(지정가)만 가능 — 요청값 {ord_dvsn}"
)
if __name__ == "__main__":
print(to_paper_tr("TTTC0802U")) # VTTC0802U
print(to_paper_tr("TTTT1006U")) # VTTT1001U <- 첫 글자만 바꿨다면 VTTT1006U
print(to_paper_tr("FHKST03010100"))# FHKST03010100 (그대로)
check_ord_dvsn("VTTT1002U", "34") # RuntimeError: LOC는 모의 불가
이 코드의 역할은 "차단"이 아니라 "표시"입니다. RuntimeError가 나는 지점이 곧 실전에서 처음 실행될 코드의 목록이고, 그게 그대로 실전 첫날 점검표가 됩니다.
7. 그래서 순서는 이렇게
- 모의에서 끝낼 것 — 토큰 발급·갱신, 현금주문 왕복, 연속조회 루프, 예외 분기, 웹소켓 재접속.
- 모의에서 "막힘"으로 기록만 할 것 — 6절 코드가 뱉는 목록. 고치지 말고 남깁니다.
- 실전 최소 단위로 옮길 것 — 도메인·
tr_id·계좌를 한 번에 바꾸고 첫 주문은 1주 지정가로, 2번 목록만 확인합니다. - 그다음에 규모를 늘릴 것 — 종목 수를 늘리는 순간 호출 한도가 새 변수로 들어옵니다. 순서는 실전 투입 전 3단계 검증에 있고, 증권사를 아직 고르는 중이라면 증권사 자동매매 API 비교도 함께 보십시오.
자주 묻는 질문
KIS 모의투자로 자동매매를 어디까지 검증할 수 있나요?
인증과 주문 왕복, 응답 파싱, 예외처리까지는 모의투자로 그대로 검증됩니다. 앱키와 앱시크릿으로 access_token을 받아 24시간 만료를 처리하는 흐름, 국내주식 현금 매수 VTTC0802U와 매도 VTTC0801U로 주문을 내고 주문번호 ODNO를 받아 주식일별주문체결조회로 되짚는 왕복, 잔고 연속조회에서 tr_cont와 ctx_area_fk100을 넘기는 루프, rt_cd와 msg_cd로 실패를 분기하는 처리는 모의 환경에서 실전과 같은 모양으로 돌아갑니다. 반대로 체결 품질과 주문유형, 시세 정확도는 검증되지 않습니다. 한국투자증권 공식 저장소는 모의투자 환경의 체결 가능 수량이 실제 시장보다 크게 제한된다고 밝히고 있습니다. 스펙은 바뀔 수 있으므로 KIS Developers 공식 문서로 확인하십시오.
모의투자에서 아예 안 되는 KIS API는 무엇인가요?
공식 저장소에서 모의투자 미지원으로 표시된 것은 국내주식 신용주문(TTTC0852U 신용매수, TTTC0851U 신용매도), 정정취소가능주문조회(TTTC8036R), 국내주식 예약주문조회(CTSC0004R), 예약취소주문(CTSC0009U), 예약정정주문(CTSC0013U), 해외주식 예약주문조회, 해외주식 주문체결내역, 그리고 국내선물옵션 실시간시세 계열입니다. 실시간시세 쪽은 지수선물 체결가와 호가, 지수옵션 체결가와 호가, 주식선물 체결가와 호가, 상품선물 호가에 실전계좌만 지원되며 모의투자는 미지원됩니다라는 문구가 붙어 있습니다. 봇 관점에서 중요한 것은 정정취소가능주문조회가 없다는 점입니다. 취소 가능한 주문을 되묻는 경로가 모의에 없으므로, 정정과 취소 로직은 주문번호를 직접 보관하는 방식으로 짜야 하고 그 코드는 실전에서 처음 돌게 됩니다.
실전 tr_id 앞글자만 V로 바꾸면 되나요?
대부분은 그렇지만 예외가 있습니다. 공식 kis_auth.py는 tr_id 첫 글자가 T, J, C일 때만 V로 치환합니다. 그래서 TTTC0802U는 VTTC0802U가 되고 FHKST01010100처럼 F로 시작하는 시세 TR은 치환 대상이 아닙니다. 문제는 해외주식입니다. 미국 매도는 실전이 TTTT1006U인데 모의는 VTTT1006U가 아니라 VTTT1001U입니다. 숫자까지 달라지므로 첫 글자만 바꾸는 규칙으로는 잡히지 않습니다. 라이브러리를 쓰지 않고 헤더를 직접 만드는 코드라면 이 한 건을 예외 표로 따로 들고 있어야 합니다. 미국 매수는 TTTT1002U와 VTTT1002U로 숫자가 같아서 더 헷갈립니다.
모의투자에서 잘 돌던 봇이 실전에서 다르게 움직이는 이유는 무엇인가요?
주문유형과 호출 간격, 두 가지가 대표적입니다. 공식 문서는 모의투자 미국 매수 VTTT1002U와 매도 VTTT1001U로는 00 지정가만 가능하다고 적고 있습니다. 실전에서 쓸 수 있는 LOO(32), LOC(34), MOO(31), MOC(33)은 모의에서 시험할 수 없으므로 장 마감 지정가로 청산하는 전략은 실전에서 처음 돌아갑니다. 호출 간격은 반대 방향입니다. 공식 백테스터 코드는 모의투자 0.5초, 실전 0.05초를 최소 간격으로 두고 있어 모의가 열 배 느립니다. 모의에서 호출 제한에 걸리지 않았다고 실전이 안전한 것이 아니라, 실전에서 더 빠르게 돌릴 수 있게 되면서 초당 거래건수 초과 EGW00201이 새로 나타날 수 있습니다.
모의투자 계좌로 받은 시세로 백테스트해도 되나요?
권장되지 않습니다. 한국투자증권 공식 저장소의 백테스터 문서는 모의투자보다 실전투자 API 키 사용을 권장하며, 모의투자 환경은 체결 가능한 주문 수량인 유동성이 실제 시장보다 크게 제한되어 백테스트 데이터 수집 시 일부 종목과 기간에서 시세가 누락되거나 부정확하게 채워질 수 있다고 밝히고 있습니다. 혼동하기 쉬운 지점은 시세 조회 TR 자체는 모의와 실전이 같다는 것입니다. FHKST03010100 같은 F 계열 TR은 V로 치환되지 않으므로 같은 TR을 부르게 되고, 그래서 같은 데이터가 오리라고 생각하기 쉽습니다. 다른 것은 TR이 아니라 그 뒤의 환경입니다. 과거 데이터는 미래 성과를 보장하지 않으며 수치는 공식 자료로 확인하십시오.
고지 — 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객의 규칙을 코드로 옮기는 도구 제공만 합니다. 이 글은 특정 종목·전략을 추천하지 않고 수익을 보장하지 않습니다. 본문의 도메인·tr_id·건수 제한·호출 간격은 2026-09-09에 한국투자증권 공식 저장소 koreainvestment/open-trading-api에서 확인한 내용이며, 증권사 정책과 API 스펙은 예고 없이 바뀔 수 있으므로 실제 적용 전 KIS Developers 공식 문서로 반드시 재확인하십시오.
모의에서 막힌 부분, 실전에서 안 터지게 만들어 드립니다
미지원 TR 우회, 주문유형 설계, 호출 한도까지 실계좌 기준으로 잡습니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기