KIS 해외주식 현재가 API — EXCD 14개와 소수점 함정
GET /uapi/overseas-price/v1/quotations/price, tr_id는 HHDFS00000300입니다.
필수 파라미터는 AUTH·EXCD·SYMB 세 개뿐이고 AUTH는 빈 문자열을 넣습니다.
국내 시세와 달리 FID_ 접두어가 없고, 나스닥은 EXCD="NAS"·티커는 SYMB="AAPL"입니다.
현재가는 output.last에 문자열로 오고, 소수점 자리수는 가격이 아니라 zdiv라는 별도 필드로 옵니다.
국내주식 봇을 먼저 만든 분이 해외주식으로 넘어올 때 막히는 자리는 인증이 아닙니다.
토큰도 appkey도 국내와 똑같이 쓰는데 시세 호출만 통째로 다른 문법이라는 점입니다.
이름만 다른 게 아니라 거래소를 가리키는 코드 체계가 조회용과 주문용으로 따로 있습니다.
이 글은 그 어긋나는 지점만 모았습니다.
스펙은 2026-08-28 시점 한국투자증권 공식 저장소(koreainvestment/open-trading-api)의
해외주식 기본시세 예제 코드와 컬럼 매핑 파일에서 확인한 항목명·설명을 기준으로 했습니다.
목차
- 국내 시세 코드를 복사하면 깨지는 3곳
- EXCD — 거래소 코드 14개, 그리고 주문 코드와의 불일치
- 응답 필드 11개 — 뭐가 오고 뭐가 안 오나
- zdiv — 소수점이 가격에 안 들어 있다
- 현재가만으로는 주문을 못 낸다 — price-detail
- 과거 시세는 dailyprice, 실시간은 웹소켓
- 실제로 도는 파이썬 코드
- 자주 막히는 자리 5곳
1. 국내 시세 코드를 복사하면 깨지는 3곳
KIS API의 국내주식과 해외주식은 같은 계정·같은 access_token·같은 도메인을 씁니다.
그래서 인증 부분은 그대로 재사용됩니다. 깨지는 건 그 아래 세 층입니다.
| 층 | 국내주식 현재가 | 해외주식 현재가 |
|---|---|---|
| 경로 | /uapi/domestic-stock/v1/quotations/inquire-price | /uapi/overseas-price/v1/quotations/price |
| tr_id | FHKST01010100 | HHDFS00000300 |
| 파라미터 | FID_COND_MRKT_DIV_CODE, FID_INPUT_ISCD | AUTH, EXCD, SYMB |
| 종목 지정 | 여섯 자리 숫자 005930 | 거래소 + 티커 NAS + AAPL |
| 모의투자 | tr_id 동일 | tr_id 동일 (실전·모의 공통) |
특히 마지막 줄이 중요합니다. 공식 예제 코드는 env_dv가 real이든 demo든
tr_id = "HHDFS00000300" 하나를 쓰고, 주석에도 "실전투자, 모의투자 공통 TR ID"라고 적혀 있습니다.
주문 계열처럼 앞글자가 T와 V로 갈리지 않으므로,
tr_id 앞글자를 보고 환경을 판정하던 코드는 여기서 조용히 틀립니다.
환경 구분은 tr_id가 아니라 도메인으로 하십시오. 토큰 발급과 환경 분리는
모의투자에서 실전으로 넘길 때 갈리는 것들에 정리해 두었습니다.
2. EXCD — 거래소 코드 14개, 그리고 주문 코드와의 불일치
EXCD는 세 글자입니다. 공식 예제의 파라미터 설명에 나열된 값은 다음과 같습니다.
| EXCD | 시장 | EXCD | 시장 |
|---|---|---|---|
NAS | 나스닥 | SHS | 상해 |
NYS | 뉴욕 | SZS | 심천 |
AMS | 아멕스 | SHI | 상해지수 |
HKS | 홍콩 | SZI | 심천지수 |
TSE | 도쿄 | HSX | 호치민 |
BAQ | 나스닥(주간) | HNX | 하노이 |
BAY | 뉴욕(주간) | BAA | 아멕스(주간) |
가장 흔한 실수 — EXCD="NASD".
NASD·NYSE·AMEX는 주문 파라미터 OVRS_EXCG_CD의 값이고,
시세 파라미터 EXCD의 값은 NAS·NYS·AMS입니다.
즉 같은 나스닥을 가리키는 코드가 조회 축과 주문 축에서 다릅니다.
상수 하나로 두 곳에 쓰면 조회는 되는데 주문이 실패하거나 그 반대가 됩니다.
실무에서는 브로커 어댑터 안에 매핑 표를 하나 두고 전략 코드는 시장을 "NASDAQ" 같은
내부 이름으로만 부르게 하십시오. 증권사별 지원 범위 차이는
키움 해외주식 API 되나 — REST 미국주식 지원 범위에서 비교해 두었습니다.
표에서 눈에 띄는 건 BAQ·BAY·BAA 세 줄입니다.
이건 미국 주간거래(한국 시간 낮 시간대) 구간의 시세를 보는 코드입니다.
정규장 시세를 NAS로 받던 봇이 낮에 NAS를 그대로 호출하면 전일 종가에서 멈춘 값을 보게 됩니다.
주간거래는 주문 tr_id도 따로 있어서 축이 통째로 갈리는데, 그 부분은
미국주식 주간거래 API 자동매매 — TTTS6036U와 BAQ에서 따로 다뤘습니다.
3. 응답 필드 11개 — 뭐가 오고 뭐가 안 오나
HHDFS00000300의 output은 객체 하나이고 필드는 열한 개입니다.
공식 예제의 컬럼 매핑 파일에 적힌 이름과 뜻은 다음과 같습니다.
| 필드 | 뜻 | 봇에서 쓰는 자리 |
|---|---|---|
rsym | 실시간조회종목코드 | 웹소켓 구독 키로 이어 붙일 때 |
zdiv | 소수점자리수 | 모든 가격 반올림의 기준 |
base | 전일종가 | 등락률 자체 계산·갭 판정 |
pvol | 전일거래량 | 거래량 급증 필터의 분모 |
last | 현재가 | 진입·청산 조건 |
sign | 대비기호 | 상승·하락 방향 |
diff | 대비 | 전일 대비 금액 |
rate | 등락율 | 퍼센트 조건 |
tvol | 거래량 | 유동성 필터 |
tamt | 거래대금 | 슬리피지 추정 |
ordy | 매수가능여부 | 주문 직전 차단 조건 |
없는 것을 먼저 보는 게 빠릅니다. 시가·고가·저가가 없습니다. 호가단위도 없고 상하한가도 없습니다.
통화도 환율도 없습니다. 즉 이 응답은 "지금 얼마인가"만 답하는 가벼운 조회이고,
주문 단가를 만들려면 5장의 price-detail이 필요합니다.
{
"output": {
"rsym": "DNASAAPL",
"zdiv": "2",
"base": "228.02",
"pvol": "42137905",
"last": "231.59",
"sign": "2",
"diff": "3.57",
"rate": "1.57",
"tvol": "38914221",
"tamt": "8985412300",
"ordy": "매수불가"
},
"rt_cd": "0",
"msg_cd": "MCA00000",
"msg1": "정상처리 되었습니다."
}
※ 위 응답은 필드 구조를 보여주기 위한 예시이고, 값 자체는 실제 시세가 아닙니다.
ordy는 숫자가 아니라 문자열 안내문으로 옵니다.
Y/N 플래그로 가정하고 if ordy == "Y"로 짜면 항상 거짓입니다.
값의 형태를 로그로 먼저 확인한 뒤 조건을 만드십시오.
4. zdiv — 소수점이 가격에 안 들어 있다
국내주식은 가격이 정수라 int() 한 번이면 끝났습니다. 해외주식은 다릅니다.
last는 문자열이고, 소수점 몇 자리까지 유효한지는 zdiv가 따로 알려줍니다.
미국 주식은 보통 zdiv="2"지만 시장과 종목에 따라 달라질 수 있습니다.
무시하면 두 가지가 터집니다. 문자열끼리 비교하면 사전순이 되어 "9.50" > "231.59"가 참이 되고
— 매수 조건이 조용히 뒤집히는데 예외는 안 납니다 —
주문 단가를 float 그대로 넣으면 231.58999999999997 같은 값이 만들어져 거절될 수 있습니다.
from decimal import Decimal, ROUND_HALF_UP
def to_price(raw: str, zdiv: str) -> Decimal:
"""해외주식 시세 문자열을 zdiv 자리수로 정규화한다."""
q = Decimal(1).scaleb(-int(zdiv)) # zdiv=2 -> Decimal('0.01')
return Decimal(raw).quantize(q, rounding=ROUND_HALF_UP)
out = res["output"]
last = to_price(out["last"], out["zdiv"]) # Decimal('231.59')
base = to_price(out["base"], out["zdiv"]) # Decimal('228.02')
rate = Decimal(out["rate"]) # 등락율은 그대로 Decimal
# 주문 단가로 넘길 때는 문자열로 되돌린다
ovrs_ord_unpr = f"{last}" # "231.59"
가격이 지나가는 통로를 하나로 묶으십시오.
시세 파싱·주문 단가 생성·로그 출력이 전부 to_price()를 거치면 소수점 세 자리 시장을 붙여도 고칠 곳이 한 군데뿐입니다.
pandas로 받아도 dtype이 object라 같은 변환이 필요합니다.
5. 현재가만으로는 주문을 못 낸다 — price-detail
지정가 주문을 내려면 호가단위가 필요합니다. 미국 주식이라고 전부 0.01달러 단위인 것도 아니고,
아시아 시장은 종목마다 다릅니다. 이 정보는 현재가가 아니라
현재가상세 tr_id = HHDFS76200200, 경로 /uapi/overseas-price/v1/quotations/price-detail에 있습니다.
파라미터는 현재가와 똑같이 AUTH·EXCD·SYMB 세 개입니다.
| 필드 | 뜻 | 왜 필요한가 |
|---|---|---|
e_hogau | 호가단위 | 지정가를 이 배수로 맞춰야 한다 |
vnit | 매매단위 | 수량 최소 단위 (1주가 아닐 수 있다) |
uplp / dnlp | 상한가 / 하한가 | 범위를 벗어난 지정가는 거절 |
curr | 통화 | USD·HKD·JPY 구분 |
t_rate / p_rate | 당일환율 / 전일환율 | 원화 기준 포지션 한도 계산 |
t_xprc | 원환산당일가격 | 원화로 손익을 보는 대시보드 |
e_ordyn | 거래가능여부 | 주문 직전 차단 |
open/high/low | 시가/고가/저가 | 당일 레인지 전략 |
h52p·perx·tomv·etyp_nm | 52주최고·PER·시가총액·ETP분류명 | 유니버스 필터 |
호출 구조는 2단이 좋습니다. 유니버스 편입 시 price-detail을 한 번 호출해
e_hogau·vnit·curr을 캐시하고, 장중에는 가벼운 HHDFS00000300만 반복합니다.
6. 과거 시세는 dailyprice, 실시간은 웹소켓
백테스트용 일·주·월봉은 tr_id = HHDFS76240000, 경로 /uapi/overseas-price/v1/quotations/dailyprice입니다.
여기는 파라미터가 여섯 개로 늘어납니다.
| 파라미터 | 값 |
|---|---|
GUBN | 0 일 / 1 주 / 2 월 |
BYMD | 조회기준일자 YYYYMMDD — 공란이면 오늘 |
MODP | 0 수정주가 미반영 / 1 반영 |
AUTH·EXCD·SYMB | 현재가와 동일 |
MODP는 국내 일봉의 FID_ORG_ADJ_PRC와 값이 반대입니다.
국내는 0이 수정주가인데 해외 MODP는 1이 반영이라, 두 시장을 한 함수로 감싸면 조용히 어긋납니다.
응답은 output1(종목 요약)과 output2(일자별 배열)로 갈리고
output2는 xymd·clos·open·high·low·tvol·tamt입니다.
긴 기간은 KIS API 일봉 데이터 — FHKST03010100 100건 제한과
같은 방식으로 BYMD를 뒤로 밀며 반복합니다.
체결가를 계속 받아야 한다면 REST를 반복하지 말고 웹소켓 실시간지연체결가 tr_id = HDFSCNT0를 씁니다.
공식 예제 설명에 따르면 기본은 무료 지연시세이고, HTS 시세신청 화면에서 유료 서비스를 신청하면
API로도 유료 실시간체결가를 받을 수 있습니다. 지연 시간은 시장별로 다릅니다.
| 시장 | 무료 시세 지연 |
|---|---|
| 미국 | 0분 지연 (장중 당일 시가는 다를 수 있고 익일 정정) |
| 홍콩 · 베트남 · 중국 · 일본 | 15분 지연 (중국은 실시간시세 신청 시 무료 실시간 제공) |
구독 키는 티커만 넣는 게 아닙니다. 공식 예제는 tr_key로 DNASAAPL 같은 값을 예시로 드는데,
앞 한 글자 + 시장 구분 + 티커가 붙은 형태입니다.
미국 주간거래 구간을 실시간으로 보려면 앞 글자를 R로 두고 시장 구분에 BAQ·BAY·BAA를 넣습니다.
웹소켓 접속키(approval_key) 발급과 구독 프레임 구조는
KIS 웹소켓 실시간 시세 연결에 국내 기준으로 정리해 두었고,
해외는 tr_id와 tr_key만 위 규칙으로 바꾸면 됩니다.
7. 실제로 도는 파이썬 코드
토큰 발급은 국내와 동일합니다. 아래는 현재가 → 현재가상세 2단 호출을 requests로만 짠 최소 코드입니다.
import requests
from decimal import Decimal, ROUND_HALF_UP
BASE = "https://openapi.koreainvestment.com:9443" # 모의는 별도 도메인
HEAD = {
"content-type": "application/json; charset=utf-8",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appkey": APP_KEY,
"appsecret": APP_SECRET,
"custtype": "P",
}
def overseas_price(excd: str, symb: str) -> dict:
"""해외주식 현재가 — HHDFS00000300"""
r = requests.get(
f"{BASE}/uapi/overseas-price/v1/quotations/price",
headers={**HEAD, "tr_id": "HHDFS00000300"},
params={"AUTH": "", "EXCD": excd, "SYMB": symb}, # AUTH는 빈 문자열
timeout=5,
)
r.raise_for_status()
body = r.json()
if body.get("rt_cd") != "0": # 0이 아니면 실패
raise RuntimeError(f'{body.get("msg_cd")} {body.get("msg1")}')
return body["output"]
def overseas_detail(excd: str, symb: str) -> dict:
"""해외주식 현재가상세 — HHDFS76200200 (호가단위·매매단위·환율)"""
r = requests.get(
f"{BASE}/uapi/overseas-price/v1/quotations/price-detail",
headers={**HEAD, "tr_id": "HHDFS76200200"},
params={"AUTH": "", "EXCD": excd, "SYMB": symb},
timeout=5,
)
r.raise_for_status()
return r.json()["output"]
def limit_price(excd: str, symb: str, target: Decimal) -> str:
"""호가단위에 맞춘 지정가 문자열을 만든다."""
d = overseas_detail(excd, symb)
tick = Decimal(d["e_hogau"]) # 예: '0.01'
zdiv = int(d["zdiv"])
px = (target / tick).quantize(Decimal(1), ROUND_HALF_UP) * tick
return f"{px.quantize(Decimal(1).scaleb(-zdiv))}"
out = overseas_price("NAS", "AAPL")
print(out["last"], out["zdiv"], out["ordy"])
print(limit_price("NAS", "AAPL", Decimal(out["last"]) * Decimal("0.995")))
rt_cd를 안 보면 실패가 성공처럼 지나갑니다.
KIS API는 파라미터가 틀려도 HTTP 200으로 응답하고 본문의 rt_cd·msg_cd에 결과를 담습니다.
raise_for_status()만 믿으면 output이 비어 있는 채로 다음 단계가 돌아
가격 0으로 주문이 나가는 사고가 됩니다. 응답 코드 체계는
KIS API 에러코드 정리에 모아 두었습니다.
8. 자주 막히는 자리 5곳
① 초당 호출 제한에 먼저 걸린다
감시 종목이 30개인데 현재가를 1초마다 도는 루프를 짜면 유량 제한에 걸려 EGW00201이 돌아옵니다.
해외주식이라고 별도 한도가 따로 열리지 않습니다.
해결은 호출을 빠르게 하는 게 아니라 구조를 바꾸는 것입니다 —
감시는 웹소켓, 확인은 REST. 대처법은 EGW00201 초당 호출 제한 해결에 있습니다.
② 티커 표기가 시장마다 다르다
미국은 SYMB="AAPL"처럼 그대로지만 홍콩·일본은 숫자 코드이고 자릿수 채움 규칙이 있습니다.
내부 유니버스 테이블에 EXCD+SYMB 쌍으로 저장해 두고 전략은 그 테이블만 참조하게 하십시오.
③ 장 시간과 서머타임
미국 정규장은 서머타임 적용 여부에 따라 한국 시간 기준 시작이 한 시간 밀립니다.
HHDFS00000300 응답만 봐서는 "지금 값"인지 "마지막 값"인지 구분되지 않습니다.
last가 안 바뀐다고 API를 의심하기 전에 장 상태와 미국 휴장일 달력을 먼저 확인하십시오.
④ 원화 환산은 시세가 아니라 환율 문제
원화 환산이 필요하면 price-detail의 t_rate(당일환율)·t_xprc(원환산당일가격)를 쓰십시오.
외부 환율 API를 붙이면 증권사가 쓰는 환율과 값이 달라 잔고 대조가 계속 어긋납니다.
⑤ 모의투자에서 되던 게 실전에서 안 된다
시세 tr_id는 공통이지만 모의투자에서 지원하지 않는 TR이 해외 쪽에 더 많습니다.
주문 쪽 실전·모의 차이는 KIS 해외주식 주문 API — tr_id와 ORD_DVSN에 정리해 두었습니다.
자주 묻는 질문
AUTH에 뭘 넣어야 하나요?
공식 예제의 파라미터 설명은 auth를 "사용자권한정보"라고 적으면서
실제 호출 예시에서는 빈 문자열을 넘깁니다(price(auth="", excd="NAS", symb="AAPL")).
인증은 헤더의 authorization·appkey·appsecret이 담당하므로
AUTH는 자리만 채우는 필드로 보시면 됩니다. 단, 키를 빼면 안 됩니다 — 필수 파라미터입니다.
현재가와 현재가상세를 매번 둘 다 부르면 안 되나요?
되지만 낭비입니다. e_hogau·vnit·curr은 하루 안에 잘 안 바뀌므로
유니버스 편입 시 한 번 받아 캐시하고, 장중 반복은 현재가만 하는 2단 구조가 호출 예산을 아낍니다.
지수는 어떻게 보나요?
EXCD 목록에 SHI(상해지수)·SZI(심천지수)가 있듯 일부 지수는 같은 API로 조회됩니다.
미국 지수는 별도 지수 계열 TR을 쓰는 편이 맞고, 지수 코드 체계는 종목과 다르므로 개발자 포털 명세로 확인하십시오.
왜 output이 리스트가 아닌가요?
현재가는 한 종목의 스냅샷이라 행이 하나이기 때문입니다.
공식 예제도 output이 리스트가 아니면 리스트로 감싼 뒤 pandas 데이터프레임을 만듭니다.
for row in body["output"]처럼 반복하면 딕셔너리 키 문자열만 순회하게 되어 엉뚱한 값이 나옵니다.
반면 dailyprice는 output2가 배열이라 반복이 맞습니다.
키움 REST API에도 같은 게 있나요?
증권사마다 해외주식 지원 범위와 시세 제공 방식이 다릅니다. 같은 "해외주식 API"라는 이름 아래 미국만 되는 곳과 아시아까지 되는 곳이 갈리고, 환전 처리 방식도 다릅니다. 증권사별 차이는 국내 증권사 API 비교 — 자동매매로 쓸 수 있는 곳에서 대조해 보시기 바랍니다.
2026-08-28 시점 한국투자증권 공식 저장소(koreainvestment/open-trading-api)의
해외주식 기본시세 예제 코드(price·price_detail·dailyprice·delayed_ccnl)와
컬럼 매핑 파일에서 확인한 경로·tr_id·파라미터명·응답 필드명을 기준으로 작성했습니다.
본문의 응답 예시 값은 구조 이해를 돕기 위한 것이고 실제 시세가 아닙니다.
증권사 명세·시세 정책·호출 한도는 예고 없이 바뀝니다.
운영에 넣기 전 개발자 포털의 현재 명세로 대조하시고,
도메인·tr_id·거래소 코드는 코드에 상수로 박지 말고 설정으로 빼 두시기 바랍니다.
이 글은 수익이나 시장 방향을 예측하지 않습니다.
해외주식 봇, 시세부터 주문까지 붙여 드립니다
거래소 코드 매핑, 호가단위 반올림, 환율 환산, 주간거래 구간 전환까지 묶어서 실제로 돌아가는 자동매매 프로그램으로 만들어 드립니다. 24시간 빠른 답변 가능합니다.
무료 상담 시작하기