Open API 적용예제
API 샘플코드 목록
대표적인 API 예제 코드를 통해 쉽게 API 활용 방법을 확인해 보세요.
- 예제는 Python 3 와 requests 패키지만 사용합니다. 실시간 예제는 websocket-client 패키지가 더 필요합니다. 01. 공통부 코드를 먼저 실행하거나 같은 파일에 넣은 뒤 각 예제를 실행하세요.
- APP KEY 에 연결된 계좌는 모의계좌가 아니라 실제 계좌입니다. 주문 예제를 실행하면 주문이 실제로 나가므로 종목, 수량, 가격을 꼭 확인하고 실행하세요.
- API 별 파라미터와 응답 항목은 API 문서를 기준으로 합니다.
-
01. 공통부 코드
접속 정보, 접근 토큰 발급, 공통 헤더, 호출 함수를 정의합니다. 아래 예제는 모두 이 코드가 먼저 실행된 상태에서 동작합니다.
import time import uuid import requests # 접속 정보 BASE_URL = "https://openapi.imeritz.com:9443" # REST API WS_URL = "wss://openapi.imeritz.com:29443/websocket" # 실시간(WebSocket). 접속점은 이것 하나입니다 APP_KEY = "발급받은 APP KEY" # 신청현황에서 발급받은 값. 계좌가 APP KEY 에 묶여 있어 요청에 계좌번호는 넣지 않습니다 APP_SECRET = "발급받은 APP SECRET" # 접근 토큰. 만료 전까지 재사용하고, 호출할 때마다 새로 발급하지 않습니다 _token, _token_expires = None, 0.0 def get_token(): global _token, _token_expires if _token and time.time() < _token_expires: return _token r = requests.post(BASE_URL + "/oauth2/token", headers={"content-type": "application/x-www-form-urlencoded"}, data={"grant_type": "client_credentials", "client_id": APP_KEY, "client_secret": APP_SECRET, "scope": "login"}, timeout=15) if r.status_code != 200: # 예: {"rsp_cd": "EGW00103", "rsp_msg": "유효하지 않은 client_id입니다."} raise RuntimeError(f"토큰 발급 실패 HTTP {r.status_code}: {r.text}") d = r.json() _token = d["access_token"] _token_expires = time.time() + int(d["expires_in"]) - 60 # 만료 60초 전에 재발급 return _token # 공통 헤더. mac_address 에는 호출 단말의 MAC 주소를 12자리 대문자 16진수로 넣습니다. 이 헤더가 없으면 요청이 거부됩니다 def mac_address(): n = uuid.getnode() return "".join(f"{(n >> s) & 0xFF:02X}" for s in range(40, -1, -8)) def headers(): return {"authorization": f"Bearer {get_token()}", "mac_address": mac_address()} # 호출 함수. 조회 API 는 GET + 쿼리스트링, 주문 API 는 POST + JSON 본문입니다 OK_CODES = {"0000", "0001", "5762", "5766", "5820", "5822"} # 성공으로 보는 응답 코드 (계열마다 다릅니다) def call(method, path, params=None, body=None): if method == "GET": r = requests.get(BASE_URL + path, params=params, headers=headers(), timeout=15) else: r = requests.post(BASE_URL + path, json=body, headers={**headers(), "content-type": "application/json; charset=utf-8"}, timeout=15) res = r.json() # 오류도 HTTP 500 + JSON 본문으로 오므로, 상태코드가 아니라 rsp_cd 로 판정합니다 code = res.get("rsp_cd") readable = isinstance(code, str) and code.isprintable() and code.strip() != "" if readable and code not in OK_CODES: raise RuntimeError(f"{code} {res.get('rsp_msg')} {res.get('rsp_sub_msg', '')}") if not readable and not res.get("data"): # 응답 코드를 읽을 수 없으면 자료 유무(data)로 판정합니다 raise RuntimeError(f"응답을 확인할 수 없습니다: {r.text[:200]}") return res- 성공 응답 코드(rsp_cd)는 계열마다 다릅니다. 자료가 있는지 없는지는 rsp_cd 가 아니라 data로 판단하시길 바랍니다.
- 오류는 종류와 관계없이 HTTP 500 으로 옵니다. 원인은 rsp_cd 와 rsp_msg 에, 구체적인 조치 사항은 rsp_sub_msg 에 담겨 옵니다.
- 초당 호출 한도는 공지사항에 기재하였습니다.
-
02. 주식 현재가, 일봉 조회
국내주식 현재가(GET /market/v1/prices)와 일봉(GET /market/v1/candles/days)을 조회하는 예제입니다. 시장구분코드 mrkt_div_code 에는 KRX 는 "J", NXT 는 "NJ", KRX 와 NXT 통합은 "TJ" 를 넣고, 종목코드 iscd 에는 6자리 코드(138040)를 넣습니다.
# 현재가 조회: GET /market/v1/prices res = call("GET", "/market/v1/prices", {"mrkt_div_code": "J", "iscd": "138040"}) p = res["data"] print(p["kor_isnm"], p["stck_prpr"], "전일대비", p["prdy_vrss"], f"({p['prdy_ctrt']}%)", "누적거래량", p["acml_vol"]) print("시가", p["oprc"], "고가", p["hprc"], "저가", p["lprc"], "상한가", p["mxpr"], "하한가", p["llam"], "PER", p["per"]) # 일봉 조회: GET /market/v1/candles/days. 최근 일자부터 내려오며, 장 마감 후에는 당일 봉도 포함됩니다 res = call("GET", "/market/v1/candles/days", {"mrkt_div_code": "J", "iscd": "138040"}) for row in res["data"][:5]: print(row["date"], "시가", row["oprc"], "고가", row["hprc"], "저가", row["lprc"], "종가", row["stck_clpr"], "거래량", row["acml_vol"])결과값 예시 1: 현재가 조회 응답입니다. 숫자는 임의의 값입니다.
메리츠금융지주 125500 전일대비 -3400 (-2.64%) 누적거래량 148894 시가 128500 고가 128900 저가 124700 상한가 167500 하한가 90300 PER 8.91
결과값 예시 2: 일봉 조회 응답입니다. 숫자는 임의의 값입니다.
20260923 시가 128500 고가 128900 저가 124700 종가 125500 거래량 148894 20260922 시가 130400 고가 130700 저가 127300 종가 128700 거래량 169345 20260921 시가 125200 고가 129900 저가 125200 종가 129700 거래량 168539 20260918 시가 127300 고가 127700 저가 124900 종가 126700 거래량 235852 20260917 시가 126300 고가 130500 저가 123000 종가 126800 거래량 218004
-
03. 주식 주문
국내주식 매수 주문(POST /trading/v1/orders/buy)을 보내고 주문내역(GET /trading/v1/orders/history)으로 체결 여부를 확인하는 예제입니다. 매도는 POST /trading/v1/orders/sell 을 쓰며 본문은 같습니다. 주문할 때 종목코드는 앞에 "A" 를 붙인 7자리(A138040)로 넣고, 계좌번호는 넣지 않습니다.
import datetime # 매수 주문: POST /trading/v1/orders/buy. 매도는 /trading/v1/orders/sell 이고 본문은 같습니다 body = { "iscd": "A138040", # 종목코드. 주문할 때는 "A" + 6자리 "odqt": 1, # 주문수량 "oder_unpr": 50000, # 주문단가. 시장가("05")는 0 "oder_cls_code": "01", # 주문구분. 01 지정가, 05 시장가, 06 조건부지정가, 12 최유리, 13 최우선 "oder_cond_cls_code": "0", # 주문조건. 0 일반, 3 IOC, 4 FOK "orgl_oder_no": 0, # 원주문번호. 신규 주문은 0 "whol_rctf_cncl_yn": "N", # 전체정정취소여부. 신규 주문은 "N" "warn_cnfr_yn": "N", # 경고확인여부. 경고 응답을 확인하고 다시 보낼 때 "A" "exch_kind_code": "01", # 거래소. 01 KRX, 02 NXT, 03 SOR } res = call("POST", "/trading/v1/orders/buy", body=body) d = res["data"] if d["warn_cls_code"] != "0": # 접수되지 않은 상태입니다. warn_msg 를 확인하고 동의한 뒤 warn_cnfr_yn="A" 로 같은 주문을 다시 보내면 접수됩니다 print("경고", d["warn_cls_code"], d["warn_msg"]) else: print("주문 접수 완료, 주문번호", d["oder_no"]) # rsp_cd "0001" 은 접수가 됐다는 뜻이지 체결 확정이 아닙니다 # 주문내역 조회: GET /trading/v1/orders/history. 체결 수량과 미체결 수량을 확인합니다 today = datetime.date.today().strftime("%Y%m%d") res = call("GET", "/trading/v1/orders/history", {"oder_date": today, # 주문일자 YYYYMMDD "deal_cls_code": "0", # 매매구분. 0 전체, 1 매도, 2 매수 "cntg_cls_code": "0"}) # 체결구분. 0 전체, 1 체결 있음, 2 미체결 있음 for o in res["data"]: print(o["oder_no"], o["kor_issu_abwr_name"], "매수" if o["deal_cls_code"] == "2" else "매도", o["odqt"], "주 @", o["oder_unpr"], "체결", o["total_cntg_qty"], "미체결", o["ncnt_qty"])- 주문 응답의 rsp_cd "0001"(주문이 완료되었습니다)은 접수가 됐다는 뜻이지 체결됐다는 뜻은 아닙니다. 체결 여부는 주문내역 조회로 확인하세요. rsp_cd 가 "0001" 이어도 data.warn_cls_code 가 "0" 이 아니면 아직 접수되지 않은 상태입니다.
- 주문 단가는 종목의 호가단위(현재가 조회의 aspr_unit)에 맞아야 합니다. 정정과 취소는 POST /trading/v1/orders/modify 와 cancel 에 원주문번호(orgl_oder_no)를 넣어 요청합니다.
- 주문내역이 많으면 rsp_cd "5762"(다음 페이지 있음)와 함께 tr_cont_key 가 옵니다. 이어서 조회하려면 응답의 tr_cont_key 를 공백까지 그대로 tr_cont=1 과 함께 쿼리스트링에 넣습니다.
결과값 예시: 매수 주문 응답입니다. API 문서에 있는 예시이며, 이 페이지를 만들면서 실제 주문을 내지는 않았습니다.
{ "data": { "oder_no": 100, "warn_cls_code": "0", "warn_msg": "" }, "rsp_cd": "0001", "rsp_msg": "주문이 완료되었습니다.", "tr_cont": "0" }결과값 예시: 주문내역 조회 응답입니다. 당일 주문이 없으면 이렇게 rsp_cd "5820" 과 빈 배열이 오는데, 이것도 정상 응답입니다.
{ "data": [], "rsp_cd": "5820", "rsp_msg": "조회할 내역(자료)이 없습니다.", "tr_cont": "0" }결과값 예시: 주문내역 조회 응답입니다. 주문이 있을 때의 모습으로 API 문서에 있는 예시입니다. data 는 배열이며 일부 항목만 표시했습니다.
{ "data": [ { "oder_no": 10, "iscd": "A138040", "kor_issu_abwr_name": "메리츠금융지주", "deal_cls_code": "2", "odqt": 100, "oder_unpr": 100000, "total_cntg_qty": 1, "cntg_unpr": 100000, "ncnt_qty": 0, "oder_pros_stat_cls_code": "1", "orgl_oder_no": 0, "rctf_cncl_cls_code": "1", "oder_rfus_reas_code": "0000", "oder_cls_code": "01" } ], "rsp_cd": "5766", "rsp_msg": "조회가 완료되었습니다.", "tr_cont": "0" } -
04. 주식 호가 조회
국내주식 매도, 매수 호가와 잔량을 10단계까지 조회(GET /market/v1/orderbook)하는 예제입니다. 1단계 호가는 번호 없이 askp, bidp 로 오고, 2단계부터는 askp2 부터 askp10 까지 번호가 붙습니다. 잔량은 askp_rsqn1 부터 askp_rsqn10, bidp_rsqn1 부터 bidp_rsqn10 입니다.
# 호가 조회: GET /market/v1/orderbook res = call("GET", "/market/v1/orderbook", {"mrkt_div_code": "J", "iscd": "138040"}) b = res["data"] print("호가시각", b["prpr_hour"], "현재가", b["stck_prpr"]) print("매도1", b["askp"], b["askp_rsqn1"], "| 매수1", b["bidp"], b["bidp_rsqn1"]) # 1단계만 번호가 없습니다 for i in range(2, 11): print(f"매도{i}", b[f"askp{i}"], b[f"askp_rsqn{i}"], f"| 매수{i}", b[f"bidp{i}"], b[f"bidp_rsqn{i}"]) print("총매도잔량", b["total_askp_rsqn"], "총매수잔량", b["total_bidp_rsqn"])- 호가가 없는 단계는 가격과 잔량이 모두 0 으로 옵니다. 시간외 단일가 호가는 GET /market/v1/orderbook/after 로 조회합니다.
결과값 예시: 호가 조회 응답입니다. 일부 항목만 표시했고 숫자는 임의의 값입니다. 장 마감 후에 조회하면 shrn_iscd 와 stck_prpr 는 0 으로 옵니다.
{ "data": { "prpr_hour": "200000", "askp": 100100, "askp2": 100200, "askp3": 100300, "bidp": 100000, "bidp2": 99900, "bidp3": 99800, "askp_rsqn1": 1000, "askp_rsqn2": 2000, "askp_rsqn3": 3000, "bidp_rsqn1": 1500, "bidp_rsqn2": 2500, "bidp_rsqn3": 3500, "total_askp_rsqn": 30000, "total_bidp_rsqn": 40000, "shrn_iscd": "0", "stck_prpr": 0 }, "rsp_cd": "0000", "rsp_msg": "정상 처리 되었습니다." } -
05. 계좌 잔고 조회
APP KEY 에 연결된 계좌의 예수금(GET /accounts/v1/me/deposit), 자산평가(GET /accounts/v1/me/valuation), 보유종목별 평가(GET /accounts/v1/me/holdings)를 조회하는 예제입니다. 계좌번호와 계좌 비밀번호는 넣지 않습니다. acno, asno, acnt_pwd 를 보내면 오히려 실패합니다.
# 예수금 상세: GET /accounts/v1/me/deposit. 요청 항목은 없고 data 는 객체입니다 dep = call("GET", "/accounts/v1/me/deposit")["data"] print("예수금", dep["tfam"], "D+2 예수금", dep["d2_aft_tfam"], "출금가능", dep["drng_able_amt"], "주문가능현금", dep["oder_able_cash_amt"]) # 자산평가: GET /accounts/v1/me/valuation. 요청 항목은 없고 data 는 객체입니다 val = call("GET", "/accounts/v1/me/valuation")["data"] print("총자산", val["aset_vltn_amt"], "주식평가", val["mksc_vltn_amt"], "매입금액", val["byng_amt"], "대출", val["loan_amt"]) # 보유종목별 평가: GET /accounts/v1/me/holdings. data 는 배열이고, nthn_rmnd_incu_yn 을 "Y" 로 보내면 잔고가 0 인 종목도 포함됩니다 rows = call("GET", "/accounts/v1/me/holdings", {"nthn_rmnd_incu_yn": "N"})["data"] for h in rows: print(h["iscd"], h["isnm"], h["mksc_cls_name"], "수량", h["rmnd_qty"], "매입단가", h["byng_unpr"], "현재가", h["psnt_prc"], "평가금액", h["vltn_amt"], "평가손익", h["vltn_prls_amt"], f"({h['ert']}%)")- 계좌 조회 계열의 성공 응답 코드는 "5766"(조회가 완료되었습니다)입니다. 보유종목 목록에는 국내주식(pros_trgt_cls_code "1")과 해외주식("J")이 같이 내려옵니다. 해외주식은 단가(byng_unpr, psnt_prc)가 해당 통화 기준이고, 평가금액 같은 금액 항목은 원화로 환산돼 옵니다.
- 입출금 내역(GET /accounts/v1/me/transactions), 실현손익(GET /accounts/v1/me/profit-loss) 등 다른 계좌 API 는 API 문서를 참고하세요.
결과값 예시: 예수금 상세 조회 응답입니다. 일부 항목만 표시했고 숫자는 임의의 값입니다.
{ "data": { "tfam": 10000000, "d1_aft_tfam": 10000000, "d2_aft_tfam": 10000000, "drng_able_amt": 10000000, "oder_able_cash_amt": 10000000, "oder_able_sbsn_amt": 8000000, "crdt_loan_amt": 0, "cash_unam": 0 }, "rsp_cd": "5766", "rsp_msg": "조회가 완료되었습니다.", "tr_cont": "0" }결과값 예시: 보유종목별 평가 조회 응답입니다. data 는 배열이고 여기서는 국내주식과 해외주식 한 건씩만 표시했습니다. 해외주식과 펀드도 같은 배열에 섞여 오며, 숫자는 임의의 값입니다.
{ "data": [ { "iscd": "A138040", "isnm": "메리츠금융지주", "pros_trgt_cls_code": "1", "mksc_cls_name": "주식일반", "rmnd_qty": 10, "byng_unpr": "95000.00", "psnt_prc": "100000.000000", "prdy_clpr_vrss": "1000.00", "vltn_amt": 1000000, "vltn_prls_amt": 50000, "ert": "5.26000", "byng_amt": 950000, "rltm_prdy_vrss_sign": "2" }, { "iscd": "AAPL.OQ", "isnm": "애플", "pros_trgt_cls_code": "J", "mksc_cls_name": "해외주식", "rmnd_qty": 5, "byng_unpr": "200.00", "psnt_prc": "210.000000", "prdy_clpr_vrss": "2.00", "vltn_amt": 1365000, "vltn_prls_amt": 65000, "ert": "5.00000", "byng_amt": 1300000, "rltm_prdy_vrss_sign": "2" } ], "rsp_cd": "5766", "rsp_msg": "조회가 완료되었습니다.", "tr_cont": "0" } -
06. 실시간 시세 (WebSocket)
국내주식 실시간 체결(tr_cd "ohhts_stck_cntg")을 구독하는 예제입니다. 실시간 시세는 wss://openapi.imeritz.com:29443/websocket 접속점 하나로 받고, 어떤 데이터를 받을지는 구독 메시지의 tr_cd 로 정합니다. 인증도 접속할 때가 아니라 구독 메시지에 넣는 token 으로 합니다. websocket-client 패키지가 필요합니다(pip install websocket-client).
import json import websocket # pip install websocket-client ws = websocket.create_connection(WS_URL, timeout=30) ws.send(json.dumps({ "header": {"token": f"Bearer {get_token()}", "tr_type": "1"}, # tr_type: 1 구독 등록, 2 구독 해지 "body": {"tr_cd": "ohhts_stck_cntg", "tr_key": "138040"}, # 국내주식 체결(KRX). 종목코드 6자리 })) try: while True: msg = json.loads(ws.recv()) head = msg.get("header", {}) if "rsp_cd" in head: # 구독 응답(ACK). 성공이면 "00000" if head["rsp_cd"] not in ("00000", "0000"): raise RuntimeError(f"구독 실패 {head['rsp_cd']} {head.get('rsp_msg')}") print("구독 완료") continue b = msg["body"] # 체결 통보 (현재가 prpr, 건별 체결량 cntg_vol) print(b["bsop_hour"], "현재가", b["prpr"], "전일대비", b["prdy_vrss_sign"], b["prdy_vrss"], "체결량", b["cntg_vol"], "누적", b["acml_vol"], "매도1", b["askp1"], "매수1", b["bidp1"]) finally: ws.close()- 실시간 서비스 코드(tr_cd)는 국내주식 체결이 "ohhts_stck_cntg"(KRX), "ohhts_nxt_cntg"(NXT), "ohhts_unt_cntg"(통합), 국내주식 호가가 "ohhts_stck_aspr", 주문 접수 통보가 "ohhts_cntg_infr", 주문 체결 통보가 "ohhts_cntg_rslt" 입니다. 통보류는 tr_key 없이 구독합니다.
- 전일대비(prdy_vrss)에는 부호가 없으니 오르고 내린 방향은 prdy_vrss_sign("2" 상승, "5" 하락)으로 봅니다. 없는 종목코드를 구독해도 구독 응답은 정상으로 오고 체결 통보만 오지 않으므로, 아무것도 오지 않으면 종목코드와 장 운영 시간부터 확인하세요.
결과값 예시: 구독 응답(ACK)입니다. header 에 rsp_cd 가 있고 body 는 비어 있습니다.
{ "header": { "tr_type": "1", "tr_cd": "ohhts_stck_cntg", "rsp_cd": "00000", "rsp_msg": "정상처리되었습니다" }, "body": {} }결과값 예시: 체결 통보 메시지입니다. API 문서에 있는 예시이며 일부 항목만 표시했습니다. 장중에만 수신됩니다.
{ "header": { "tr_cd": "ohhts_stck_cntg", "tr_key": "138040" }, "body": { "shrn_iscd": "138040", "bsop_hour": "100000", "hour_cls_code": "0", "prpr": "100000", "prdy_vrss_sign": "2", "prdy_vrss": "3750", "prdy_ctrt": "1.00", "cntg_cls_code": "1", "cntg_vol": "10", "acml_vol": "1000000", "askp1": "100000", "bidp1": "100000", "askp_rsqn1": "5819", "bidp_rsqn1": "10000" } }
메뉴