미국주식 시세·잔고·체결 조회 — 그리고 '원장일'의 함정
미국주식 현재가·잔고·체결내역 조회. 한국 자정에 안 바뀌는 '원장일'과 체결 누락을 막는 catch-up.
토큰이 준비됐으니, 이제 매매 판단에 필요한 데이터를 읽어온다 — 현재가·잔고·체결내역. 전부 미국주식 전용 TR(/api/us/...)이다.
현재가 (usa20100)
종목코드와 거래소 구분(stex_tp)을 함께 보낸다. ND=나스닥, NY=NYSE, NA=아멕스.
def us_current_price(self, stk_cd, stex_tp):
d = self.call("usa20100", "/api/us/mrkcond",
{"stk_cd": stk_cd, "stex_tp": stex_tp})
raw = d.get("cur_prc", "") # "+88.3600" 처럼 부호가 붙어 온다
return abs(float(raw)) if raw else None
함정:
cur_prc는 등락 부호가 붙어서("+88.36") 온다.abs(float(...))로 가격만 빼내야 한다.
거래소를 매번 지정하기 번거로우면, 한 번 탐지해서 캐시하면 된다. ND→NY→NA 순서로 현재가를 시도해보고, 값이 나오는 거래소를 기억한다. (운영 종목은 그래도 exchange_map에 명시하는 게 안전하다 — 거래소가 틀리면 주문이 거부된다.)
잔고 (ust21070) · 예수금 (ust21160)
계좌번호를 넣을 필요가 없다(appkey에 연동). 응답의 result_list[]에 보유 종목이 들어온다.
def us_position_qty(self, stk_cd):
for row in self.us_balance().get("result_list", []):
if row.get("stk_cd") == stk_cd:
return int(row.get("poss_qty", "0") or 0) # 보유수량
return 0
주요 필드: poss_qty(보유수량), sell_alowq(매도가능), frgn_stk_book_uv(평균단가), now_pric(현재가), evlt_amt(평가금액), pl_amt/pl_rt(손익/손익률). 예수금은 ust21160의 d0_usd_fx_entr(USD 외화예수금).
체결내역 (ust21150) — 그리고 "원장일"
여기가 초보자가 가장 많이 틀리는 지점이다.
미국 장은 한국시간 밤 22:30~새벽 05:00에 열린다. 즉 한국 날짜로는 자정을 넘긴다. 만약 우리가 "오늘 날짜"로 체결을 조회하면, 자정 전 체결과 자정 후 체결이 다른 날짜로 갈려 절반을 놓친다.
키움은 이걸 원장일(미국 거래일 단위) 로 처리한다. 원장일은 한국 자정에 바뀌지 않는다. 그래서 조회할 때 날짜를 우리가 계산하지 말고, 빈 값으로 서버에 맡기는 게 안전하다.
# 기본(오늘 원장일): ord_dt 를 빈 문자열로 → 서버가 알아서 처리
rows = self._client.us_executions(ord_dt="")
catch-up(따라잡기): 봇이 밤사이 멈췄다가 다음 날 켜지면, 직전 원장일 체결을 놓친다. 그래서 "오늘 + 직전 N일"을 합쳐 조회하고 주문번호(ord_no)로 중복 제거한다.
# 지난 며칠치까지 합쳐 조회 (재시작 복구용)
history = controller.get_execution_history(lookback_days=3)
그리고 완전 체결만 잡는다 — 잔량(ord_remnq)이 0이고 체결량(cntr_qty)이 0보다 클 때. (부분체결은 전량 체결된 뒤 같은 주문번호로 한 번 잡힌다.)
미체결 (ust21050)
아직 안 붙은 주문. ord_remnq(잔량)가 0보다 큰 행만 유효하다. 취소나 상태 확인에 쓴다.
조회는 "실패해도 멈추지 않게" (fail-open)
시세·잔고 조회가 잠깐 실패한다고 봇이 죽으면 안 된다. 실전 코드는 조회 실패 시 에러를 던지지 않고 None/빈 리스트를 돌려주고, 그 사이클만 건너뛴다.
try:
return _result_list(self.call("ust21150", "/api/us/acnt", body))
except KiwoomRestError as e:
_log_query_failure("us_executions", e) # '결과 없음'(rc=20)은 debug 로만
return [] # 멈추지 않는다
📦 클라이언트에 조회 메서드 추가 — 실행
3편의 KiwoomRestClient 클래스 안에 아래 메서드를 추가하자.
# ── hero/kiwoom_rest.py 의 KiwoomRestClient 안에 추가 ──
def us_current_price(self, stk_cd, stex_tp):
d = self.call("usa20100", "/api/us/mrkcond",
{"stk_cd": stk_cd, "stex_tp": stex_tp})
raw = d.get("cur_prc", "") # "+88.3600" 형태
return abs(float(raw)) if raw else None
def us_balance(self):
return self.call("ust21070", "/api/us/acnt", {})
def us_executions(self, ord_dt=""):
body = {"ord_dt": ord_dt, "query_tp": "5", "slby_tp": "0",
"stex_tp": "", "stk_cd": "", "oppo_trde_tp": "", "fr_ord_no": ""}
d = self.call("ust21150", "/api/us/acnt", body)
return d.get("result_list") or d.get("result_lsit") or []
실행 스크립트(3편의 __main__을 이렇게 바꿔서):
if __name__ == "__main__":
import pathlib
cfg = json.loads(pathlib.Path("config/settings.json").read_text("utf-8"))["kiwoom_rest"]
c = KiwoomRestClient(cfg["appkey"], cfg["secretkey"], mock=cfg.get("mock", True))
print("QLD 현재가:", c.us_current_price("QLD", "NY")) # 예: 88.36
for row in c.us_balance().get("result_list", []):
print(f"보유: {row['stk_cd']} {row.get('poss_qty')}주 "
f"@ {row.get('frgn_stk_book_uv')}")
for f in c.us_executions(): # 오늘 원장일 체결
print("체결:", f.get("stk_cd"), f.get("slby_tp_nm"),
f.get("cntr_qty"), "@", f.get("cntr_uv"))
✅ 확인
python hero/kiwoom_rest.py
- 현재가가 숫자로 출력되면 시세 조회 성공. (거래소
stex_tp가 틀리면None이 나오니,QLD는NY,NVDA는ND처럼 맞춰보자.) - 보유 종목이 있으면 수량·평단이 출력된다. (없으면 빈 줄 — 정상)
- 체결내역은 오늘 원장일 기준. 오늘 매매가 없으면 안 나오는 게 정상이다.
다음 편 예고
5편에서는 드디어 주문을 낸다 — 매수·매도·취소. 그리고 실계좌에서 주문을 통째로 거부당하게 만드는 가격 포맷의 함정($1 경계에서 소수점 자릿수가 갈린다)을 짚습니다.
원장일 개념이 헷갈리면 댓글로 물어봐 주세요 — 자동매매에서 체결 누락은 치명적이라, 여기만 정확히 잡아도 절반은 한 거예요.
아래 자료에서 본인에게 적용되는 최신 조건을 다시 확인하세요. 링크는 특정 상품 가입을 권하는 제휴 링크가 아닙니다.
- 키움증권 REST API 포털 ↗토큰 발급·TR 호출·미국주식 주문/조회 스펙
- Python 표준 라이브러리 · urllib.request ↗외부 의존성 없는 HTTP 호출
이 글은 교육·정보 제공 목적이며 개인별 투자자문이나 특정 금융상품의 매수·매도 권유가 아닙니다.
이 글이 포함된 외벌이 재무 워크북(PDF)을 이메일로 무료로 받아보세요. 글과 도구는 계속 무료입니다.
함께 생각해요
이 글을 읽고 생긴 질문이나 직접 실행한 경험을 나눠주세요.
댓글을 불러오는 중입니다…