KIS API 해외주식 잔고 TTTS3012R 함정 6가지
한국투자증권 해외주식 잔고는 API가 하나가 아니라 넷입니다 — 잔고 TTTS3012R, 체결기준현재잔고 CTRP6504R, 결제기준잔고, 통화별 증거금 TTTC2101R. 가장 크게 걸리는 것은 WCRC_FRCR_DVSN_CD(원화외화구분)의 01·02 의미가 API마다 뒤집혀 있다는 점입니다 — 값이 뒤집혀도 호출은 성공하고 예외도 없습니다. 나머지 다섯은 ⑴ NASD가 실전에선 '미국전체', 모의에선 '나스닥', ⑵ TTTS3012R은 거래소 × 통화 조합마다 따로 호출, ⑶ 공식 예제 연속조회가 max_depth=10에서 경고만 남기고 정상 반환, ⑷ 미체결 TTTS3018R은 거래소코드를 비우면 다음 조회 불가, ⑸ 체결내역은 ODNO(주문번호)로 검색 불가이고 모의계좌는 필터가 전부 '전체'만 됩니다.
목차
- 잔고 API가 네 개인 이유
- 함정 1 — 원화·외화 구분이 API마다 뒤집힌다
- 함정 2 —
NASD의 의미가 실전과 모의에서 다르다 - 함정 3 — 잔고는 거래소 × 통화별로 물어야 한다
- 함정 4 — 연속조회가 10페이지에서 조용히 끊긴다
- 함정 5 — 미체결은 거래소코드를 비울 수 없다
- 함정 6 — 체결내역은 주문번호로 못 찾는다
- 계좌 스냅샷을 만드는 순서
- 자주 묻는 질문
1. 잔고 API가 네 개인 이유
국내주식은 잔고 조회가 사실상 하나입니다(KIS 국내주식 잔고와 연속조회). 해외주식은 "언제 기준의 잔고냐"에 따라 엔드포인트가 갈립니다.
| 목적 | 엔드포인트 | tr_id (실전 / 모의) |
|---|---|---|
| 보유 종목·평가손익 | /uapi/overseas-stock/v1/trading/inquire-balance | TTTS3012R / VTTS3012R |
| 체결 기준 현재잔고 | .../inquire-present-balance | CTRP6504R / VTRP6504R |
| 특정 일자 결제 기준 | .../inquire-paymt-stdr-balance | 해외주식-064 (BASS_DT 필수) |
| 통화별 증거금 | .../foreign-margin | TTTC2101R |
| 매수 여력 | .../inquire-psamount | TTTS3007R / VTTS3007R |
넷의 숫자가 어긋나도 오류가 아닙니다. 해외주식은 결제일이 뒤에 있어 체결 기준과 결제 기준이 며칠씩 벌어집니다. 봇에서 "잔고가 안 맞는다"고 판단하기 전에 어느 기준으로 물었는지부터 확인하십시오. 아래 스펙은 전부 한국투자증권 공식 저장소 koreainvestment/open-trading-api의 examples_llm/overseas_stock/ 예제 원문에서 확인한 값이며, 스펙은 공지에 따라 바뀌므로 KIS Developers 공식 문서를 함께 보십시오.
2. 함정 1 — 원화·외화 구분이 API마다 뒤집힌다
WCRC_FRCR_DVSN_CD는 같은 이름으로 세 API에 나옵니다. 그런데 값의 의미가 한 곳에서만 반대입니다.
| API | 01 | 02 |
|---|---|---|
체결기준현재잔고 CTRP6504R | 원화 | 외화 |
| 결제기준잔고 (해외주식-064) | 원화기준 | 외화기준 |
기간손익 inquire-period-profit | 외화 | 원화 |
공식 예제의 설명 원문을 그대로 옮기면 이렇습니다.
# inquire_present_balance.py
wcrc_frcr_dvsn_cd (str): 01 : 원화 02 : 외화
# inquire_paymt_stdr_balance.py
wcrc_frcr_dvsn_cd (str): 원화외화구분코드 (01: 원화기준, 02: 외화기준)
# inquire_period_profit.py
wcrc_frcr_dvsn_cd (str): 01 : 외화, 02 : 원화 <-- 반대
이 함정이 위험한 이유는 실패하지 않기 때문입니다. 값을 반대로 넣어도 HTTP는 정상이고 응답도 정상입니다 — 숫자만 통화가 다른 채로 돌아옵니다. 원화 기준 평가금액을 기대한 자리에 달러 값이 들어오면 대략 천 배 이상 차이가 나므로 손익 계산이나 비중 계산이 통째로 어긋납니다. WCRC = "01" 같은 상수를 모듈 최상단에 하나 두고 돌려 쓰는 코드가 특히 위험합니다.
# 상수를 공유하지 말고 API별로 따로 정의한다
class Wcrc:
PRESENT_KRW = "01"; PRESENT_FCY = "02" # CTRP6504R
PAYMT_KRW = "01"; PAYMT_FCY = "02" # 해외주식-064
PROFIT_FCY = "01"; PROFIT_KRW = "02" # 기간손익 — 반대
# 호출 후에도 응답의 통화 필드로 한 번 더 확인한다
assert row.get("crcy_cd") in ("USD", "KRW")
3. 함정 2 — NASD의 의미가 실전과 모의에서 다르다
잔고 조회의 OVRS_EXCG_CD 설명은 실전과 모의를 나눠서 적혀 있습니다.
[모의] NASD : 나스닥 NYSE : 뉴욕 AMEX : 아멕스
[실전] NASD : 미국전체 NAS : 나스닥 NYSE : 뉴욕 AMEX : 아멕스
[모의/실전 공통] SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천
TKSE : 일본 HASE : 하노이 VNSE : 호치민
모의에서 NASD로 짜고 실전으로 옮기면 조회 범위가 나스닥에서 미국전체로 넓어집니다. 뉴욕·아멕스 보유분이 갑자기 함께 들어오면서 포지션 집계와 비중 계산이 달라집니다. 반대로 실전에서 나스닥만 보려면 NAS를 써야 하는데, 이 값은 모의에 없습니다.
미체결내역 쪽 설명도 같은 이야기를 다른 문장으로 합니다 — "NASD인 경우만 미국전체로 조회되며 나머지 거래소 코드는 해당 거래소만 조회됨". 모의·실전 전환 시 바뀌는 것들의 전체 목록은 KIS 모의투자 실전 전환 6가지에 있습니다.
4. 함정 3 — 잔고는 거래소 × 통화별로 물어야 한다
TTTS3012R의 필수 파라미터는 넷이고, 그중 거래소코드와 통화코드가 둘 다 필수입니다.
params = {
"CANO": cano, "ACNT_PRDT_CD": acnt_prdt_cd,
"OVRS_EXCG_CD": "NASD", # 필수
"TR_CRCY_CD": "USD", # 필수 (HKD/CNY/JPY/VND)
"CTX_AREA_FK200": "", "CTX_AREA_NK200": "",
}
즉 미국·홍콩·일본을 함께 보유한 계좌는 조합마다 따로 호출해야 합니다. 계좌 하나를 한 번에 보고 싶다면 NATN_CD에 000(전체)을 넣을 수 있는 CTRP6504R 쪽이 목적에 맞습니다. 다만 CTRP6504R은 필수 파라미터가 여섯이고 응답이 output1·output2·output3 셋으로 나뉩니다.
CTRP6504R의 INQR_DVSN_CD에는 00(전체)·01(일반해외주식)·02(미니스탁)가 있습니다. 소수점 거래 분이 별도 구분이라는 뜻이므로, 일반 주식만 세는 봇이라면 01로 좁히는 편이 명확합니다.
5. 함정 4 — 연속조회가 10페이지에서 조용히 끊긴다
공식 예제의 잔고 함수는 응답 헤더의 tr_cont가 M 또는 F면 자기 자신을 재귀 호출합니다. 그리고 상단에 깊이 제한이 있습니다.
if depth >= max_depth:
logger.warning("Maximum recursion depth (%d) reached. "
"Stopping further requests.", max_depth)
return dataframe1 if dataframe1 is not None else pd.DataFrame(), ...
예외를 던지지 않고 그때까지 모은 DataFrame을 정상 반환합니다. 호출부에서는 "조회가 끝난 것"과 "10페이지에서 잘린 것"을 구분할 수 없습니다. 키움 REST의 MAX_PAGES=10과 같은 구조이고(키움 잔고 kt00018), 국내 KIS 조회 계열에도 같은 패턴이 있습니다.
대응 — 예제를 복사했다면 max_depth를 올리는 것으로 끝내지 말고, 마지막 tr_cont 값을 함께 반환해 호출부가 절단 여부를 판정하게 하십시오. 연속조회 키는 CTX_AREA_FK200·CTX_AREA_NK200이며 최초 조회에서는 공란, 두 번째부터 직전 응답 값을 그대로 실어 보냅니다.
6. 함정 5 — 미체결은 거래소코드를 비울 수 없다
해외주식 미체결내역(/uapi/overseas-stock/v1/trading/inquire-nccs)의 OVRS_EXCG_CD 설명에 이렇게 적혀 있습니다.
* 공백 입력 시 다음조회가 불가능하므로,
반드시 거래소코드 입력해야 함
첫 페이지는 나오는데 연속조회만 안 되는 형태라 증상이 헷갈립니다. 미체결이 10건 아래인 계좌에서는 문제가 드러나지 않다가, 분할 주문을 쓰는 봇에서 갑자기 목록이 잘립니다. SORT_SQN은 DS가 정순이고 그 외가 역순인데, tr_id가 TTTS3018R일 때는 공란으로 두라고 별도로 적혀 있습니다.
7. 함정 6 — 체결내역은 주문번호로 못 찾는다
주문 결과를 확인하려면 해외주식 주문체결내역 TTTS3035R(/uapi/overseas-stock/v1/trading/inquire-ccnl)을 씁니다. 파라미터에 ODNO(주문번호)가 있는데, 설명이 이렇습니다.
odno (str): "" (Null 값 설정)
※ 주문번호로 검색 불가능합니다. 반드시 ""(Null 값 설정) 바랍니다.
ord_dt (str): "" (Null 값 설정)
ord_gno_brno (str): "" (Null 값 설정)
pdno (str): 전종목일 경우 "%" 입력
있는데 못 씁니다. 특정 주문의 결과를 보려면 주문일자 구간(ORD_STRT_DT·ORD_END_DT, 현지시각 기준)으로 조회한 뒤 응답에서 주문번호를 맞춰 찾아야 합니다. 전종목 표기가 PDNO는 %, OVRS_EXCG_CD도 %인 것도 국내 API와 다른 부분입니다.
모의계좌에는 제약이 더 붙습니다.
| 파라미터 | 실전 | 모의투자 계좌 |
|---|---|---|
PDNO | 종목코드 또는 % | ""(전체)만 |
SLL_BUY_DVSN | 00/01/02 | "00"만 |
CCLD_NCCS_DVSN | 00/01/02 | "00"만 |
OVRS_EXCG_CD | 거래소 또는 % | ""(전체)만 |
SORT_SQN | DS/AS | 사용불가(기본 DS) |
모의에서 만든 필터링 코드가 실전에서만 동작하는 구조입니다. 모의로 검증했다는 말이 여기서는 절반만 맞습니다.
8. 계좌 스냅샷을 만드는 순서
여섯을 반영하면 봇이 아침마다 도는 스냅샷 함수는 이렇게 정리됩니다.
MARKETS = [("NASD", "USD"), ("SEHK", "HKD"), ("TKSE", "JPY")]
def snapshot(env="real"):
rows = []
for excg, crcy in MARKETS: # 조합마다 따로 (함정 3)
d1, d2 = inquire_balance(cano, prdt, excg, crcy,
env_dv=env, max_depth=50) # (함정 4)
rows.append((excg, crcy, d1, d2))
# 통화별 증거금은 별도 (TTTC2101R)
margin = foreign_margin(cano, prdt)
# 오늘 나간 주문 확인은 일자 구간으로 (함정 6)
ccnl = inquire_ccnl(cano, prdt, pdno="%", ovrs_excg_cd="%",
ord_strt_dt=today, ord_end_dt=today,
sll_buy_dvsn="00", ccld_nccs_dvsn="00",
sort_sqn="DS", ord_dt="", ord_gno_brno="",
odno="", env_dv=env) # odno 는 반드시 ""
return rows, margin, ccnl
계좌번호를 CANO 8자리와 ACNT_PRDT_CD 2자리로 나누는 규칙은 CANO·ACNT_PRDT_CD 계좌번호 분리에, 조회가 실패했을 때 돌아오는 코드 해석은 KIS API 에러코드에 정리돼 있습니다. 이 잔고 값을 실제 매매 판단에 쓰는 예는 무한매수법 자동매매 — KIS API 함정 7가지에서 다뤘습니다.
자주 묻는 질문
어떤 잔고 API를 써야 하나요?
보유 종목·평가손익은 TTTS3012R, 체결 기준 현재잔고는 CTRP6504R, 특정 일자 결제 기준은 결제기준잔고(BASS_DT 필수), 통화별 증거금은 TTTC2101R입니다. 넷의 숫자가 어긋나도 오류가 아닙니다.
WCRC_FRCR_DVSN_CD의 01은 원화인가요?
API마다 다릅니다. CTRP6504R과 결제기준잔고는 01=원화이지만, 기간손익은 01=외화로 반대입니다. 값이 뒤집혀도 호출은 성공하므로 상수를 공유하지 말고 API별로 따로 정의하십시오.
NASD를 넣으면 나스닥만 나오나요?
실전에서는 미국전체이고 나스닥만 보려면 NAS를 씁니다. 모의에서는 NASD가 나스닥이고 NAS가 없습니다.
계좌 전체를 한 번에 가져올 수 있나요?
TTTS3012R은 거래소·통화가 둘 다 필수라 조합마다 호출해야 합니다. 한 번에 보려면 NATN_CD=000이 되는 CTRP6504R을 쓰되 output 셋을 파싱해야 합니다.
연속조회를 돌렸는데 잔고가 다 안 나옵니다.
공식 예제의 max_depth=10일 수 있습니다. 깊이에 도달하면 경고 로그만 남기고 정상 반환하므로 절단을 알 수 없습니다. 마지막 tr_cont를 함께 반환하도록 고치십시오.
마무리
해외주식 잔고에서 실제로 사고가 나는 자리는 필드 파싱이 아니라 "내가 무엇을 물었는지"였습니다. 기준 시점(체결·결제)과 통화 기준, 거래소 범위 — 이 셋을 코드에 명시적으로 적어 두면 잔고가 안 맞는다는 신고의 대부분이 사라집니다.
고지 — 알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객의 규칙을 코드로 옮기는 도구 제공만 합니다. 이 글은 특정 종목·전략을 추천하지 않고 수익을 보장하지 않습니다. 본문의 tr_id·파라미터·제약은 2026-09-07 기준 한국투자증권 공식 저장소 예제 원문에서 확인한 값이며, API 스펙은 공지에 따라 바뀌므로 KIS Developers 공식 문서를 기준으로 삼으십시오.