AlgoLab Blog · 한국투자증권 해외주식 계좌 실무 · 2026

KIS API 해외주식 잔고 TTTS3012R 함정 6가지

한국투자증권 · 해외주식 잔고 2026-09-07 · 약 7분 읽기 · 알고랩 AlgoLab
한 줄 요약

한국투자증권 해외주식 잔고는 API가 하나가 아니라 넷입니다 — 잔고 TTTS3012R, 체결기준현재잔고 CTRP6504R, 결제기준잔고, 통화별 증거금 TTTC2101R. 가장 크게 걸리는 것은 WCRC_FRCR_DVSN_CD(원화외화구분)의 01·02 의미가 API마다 뒤집혀 있다는 점입니다 — 값이 뒤집혀도 호출은 성공하고 예외도 없습니다. 나머지 다섯은 ⑴ NASD가 실전에선 '미국전체', 모의에선 '나스닥', ⑵ TTTS3012R거래소 × 통화 조합마다 따로 호출, ⑶ 공식 예제 연속조회가 max_depth=10에서 경고만 남기고 정상 반환, ⑷ 미체결 TTTS3018R거래소코드를 비우면 다음 조회 불가, ⑸ 체결내역은 ODNO(주문번호)로 검색 불가이고 모의계좌는 필터가 전부 '전체'만 됩니다.

목차

  1. 잔고 API가 네 개인 이유
  2. 함정 1 — 원화·외화 구분이 API마다 뒤집힌다
  3. 함정 2 — NASD의 의미가 실전과 모의에서 다르다
  4. 함정 3 — 잔고는 거래소 × 통화별로 물어야 한다
  5. 함정 4 — 연속조회가 10페이지에서 조용히 끊긴다
  6. 함정 5 — 미체결은 거래소코드를 비울 수 없다
  7. 함정 6 — 체결내역은 주문번호로 못 찾는다
  8. 계좌 스냅샷을 만드는 순서
  9. 자주 묻는 질문

1. 잔고 API가 네 개인 이유

국내주식은 잔고 조회가 사실상 하나입니다(KIS 국내주식 잔고와 연속조회). 해외주식은 "언제 기준의 잔고냐"에 따라 엔드포인트가 갈립니다.

목적엔드포인트tr_id (실전 / 모의)
보유 종목·평가손익/uapi/overseas-stock/v1/trading/inquire-balanceTTTS3012R / VTTS3012R
체결 기준 현재잔고.../inquire-present-balanceCTRP6504R / VTRP6504R
특정 일자 결제 기준.../inquire-paymt-stdr-balance해외주식-064 (BASS_DT 필수)
통화별 증거금.../foreign-marginTTTC2101R
매수 여력.../inquire-psamountTTTS3007R / VTTS3007R

넷의 숫자가 어긋나도 오류가 아닙니다. 해외주식은 결제일이 뒤에 있어 체결 기준결제 기준이 며칠씩 벌어집니다. 봇에서 "잔고가 안 맞는다"고 판단하기 전에 어느 기준으로 물었는지부터 확인하십시오. 아래 스펙은 전부 한국투자증권 공식 저장소 koreainvestment/open-trading-apiexamples_llm/overseas_stock/ 예제 원문에서 확인한 값이며, 스펙은 공지에 따라 바뀌므로 KIS Developers 공식 문서를 함께 보십시오.

2. 함정 1 — 원화·외화 구분이 API마다 뒤집힌다

WCRC_FRCR_DVSN_CD는 같은 이름으로 세 API에 나옵니다. 그런데 값의 의미가 한 곳에서만 반대입니다.

API0102
체결기준현재잔고 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_CD000(전체)을 넣을 수 있는 CTRP6504R 쪽이 목적에 맞습니다. 다만 CTRP6504R은 필수 파라미터가 여섯이고 응답이 output1·output2·output3 셋으로 나뉩니다.

TTTS3012R (잔고) OVRS_EXCG_CD 필수 TR_CRCY_CD 필수 → 거래소 x 통화 조합마다 호출 N회 CTRP6504R (체결기준) NATN_CD 000 = 전체 TR_MKET_CD 00 = 전체 → 호출 1회, 대신 output 3개 파싱 vs
같은 계좌를 보는 두 경로 — 호출 횟수와 파싱 복잡도를 맞바꾼다

CTRP6504RINQR_DVSN_CD에는 00(전체)·01(일반해외주식)·02(미니스탁)가 있습니다. 소수점 거래 분이 별도 구분이라는 뜻이므로, 일반 주식만 세는 봇이라면 01로 좁히는 편이 명확합니다.

5. 함정 4 — 연속조회가 10페이지에서 조용히 끊긴다

공식 예제의 잔고 함수는 응답 헤더의 tr_contM 또는 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_SQNDS가 정순이고 그 외가 역순인데, tr_idTTTS3018R일 때는 공란으로 두라고 별도로 적혀 있습니다.

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_DVSN00/01/02"00"
CCLD_NCCS_DVSN00/01/02"00"
OVRS_EXCG_CD거래소 또는 %""(전체)만
SORT_SQNDS/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_CD01은 원화인가요?

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 공식 문서를 기준으로 삼으십시오.

해외주식 봇, 잔고가 어긋나지 않게 만들어 드립니다

기준 시점·통화·거래소 범위를 코드에 명시한 구조로 짜 드립니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기