
이번 글에서는
1. (무작정) 한국투자증권 API 코드 예제
2. API 개념 설명
3. API 흐름(실제 한국투자증권 API 코드 구현)
순으로 공부를 해볼 것이다...!
왜 코드 예제를 무작정 먼저 하냐면,
코드를 먼저 보고 나면 이 API를 활용한 주식 자동 매매 프로그램의 전체적인 숲을 먼저 볼 수 있기 때문이다.
어려워보여도 괜찮다!
어차피 실제 구현은 3. API 흐름에서 제대로 할 것이고,
지금은 그냥 한 줄씩 '아하 이때는 이런 코드가 쓰이는구나~'하고 가볍게 읽고 넘어가도 좋다. (●ˇ∀ˇ●)bb
그럼 이제 가보자고!!!
자, 이제 한국투자증권의 API를 받아오는 기본 코드를 한번 작성해보자!
이전 글에서 공부한 대로
기본 설정 > REQUESTS > RESPONSE 순으로 코드를 적어보자.
<상황>
: 삼성전자 주가 데이터를 조회(GET)해서 10주를 매수해보는 것으로(POST)
: HTTPS 통신의 기본인 requests를 불러오고
수많은 클라이언트 중에 '나'를 구별할 수 있는key,secret, token 등도 정의한다.
import requests
# 한국투자증권 API 기본 설정
BASE_URL = "https://openapi.koreainvestment.com:9443"
app_key = "YOUR_APP_KEY"
app_secret = "YOUR_APP_SECRET"
access_token = "YOUR_ACCESS_TOKEN"
Q1. key vs secret vs token 차이
A1. key로 기본 신원을 확인하고 secret으로 본인이 진짜 맞는지 확인하고 본인이 맞으면 실제 API 사용 권한인 token을 받는다.
Q2.
URL에서9443의 의미는?
A2. 포트 번호를 의미. 포트 번호란 한 서버 컴퓨터에서 서비스를 구분하는 번호로 아파트의 '호수'같은 개념.29443은 모의 투자 포트 번호를 의미. 참고로 알고있으면 좋은 포트 번호들은 아래에!
- 80: HTTP웹사이트(기본값)
- 443: HTTPS 웹사이트(기본값)
- 3306: MySQL 데이터베이스
- 8080: 개발용 웹서버
: REQUEST에는 크게 GET 요청과 POST요청이 있다.
이 프로젝트에서 GET 요청은 주가나 잔고 등의 데이터를 조회할 때 필요하며 POST 요청은 매수/매도 주문 등의 데이터를 보낼 때 필요하다.
각 메소드 별로 RESPONSE도 같이 적어본다.
먼저 GET부터 작성해보자
# GET: 삼성전자주가 조회
# try: 조회 시도해보는데 except Exception as e: 에러가 나오는 경우도 예외처리 해줘야 함
try:
# 현재 주가 조회 API 엔드포인트
price_url = f"{BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-price"
# 쿼리 파라미터(삼성전자: 005930)
params = {
"fid_cond_mrkt_div_code": "J", # 시장 구분 (J: 주식)
"fid_input_iscd": "005930" # 종목코드(삼성전자)
}
# GET 요청: 서버에서 주가 데이터 가져오기
# res는 서버의 응답을 의미
res = requests.get(price_url, headers=headers, params=params)
# 응답 상태 확인
if res.status_code == 200: # 왜 200이야??
data = res.json()
current_price = data['output']['stck_prpr'] # 현재가
change_rate = data['output']['prdy_ctrt'] # 전일대비 등락률
print(f"삼성전자 현재가: {current_price}원")
print(f"전일대비: {change_rate} %")
else:
print(f"주가 조회 실패: {res.status_code}")
except Exception as e:
print(f"주가 조회 에러: {e}")
Q1.
price_url에서 엔드포인트란?
A1. 엔드포인트란server에서 특정 기능을 담당하는 주소를 의미./quotations/inquire-price는 주가를 조회해주는 곳을 의미
Q2. 쿼리 파라미터에서
J란?
A2. 한국투자증권에서 임의로 정한 코드이다.
- J: 주식(JSTOCK)
- F: 선물(FUTURES)
- O: 옵션(OPTIONS)
Q3. 쿼리 파라미터에서
fid와isce의 의미?
A3. 한국투자증권 API에서 사용하는 약어
- FID: Field ID(어떤 필드인지 구분)
- ISCD: Issue Code(종목 코드 의미)
Q4.
res.status_code가 200인 이유
A4. HTTP 상태를 나타내는 국제 표준 코드
- 200 = OK(성공)
- 404 = Not Found(페이지 없음)
- 500 = Server Error(서버 오류)
그리고 이제 POST 요청도 작성해보자
# POST: 삼성전자 매수 주문
# GET과 마찬가지로 try-except으로 구성
try:
# 매수 주문 API 엔드포인트
# 엔드포인트란?
order_url = f"{BASE_URL}/uapi/domestic-stock/v1/trading/order-cash"
# POST 요청용 헤더(거래 ID 추가)
order_headers = headers.copy()
order_headers["tr_id"] = "VTTC0802U" # 매수 거래 ID
# 주문 데이터(JSON 바디 내용)
order_data = {
"CANO": "계좌번호 앞 8자리"
"ACNT_PRDT_CD": "01", # 계좌상품코드
"PDNO": "005930", # 삼성전자 종목코드
"ORD_DVSN": "01", # 주문구분(01: 시장가)
"ORD_QTY": "10", # 주문수량 10주
"ORD_UNPR": "0" # 주문단가(시장가이므로 0)
}
# POST 요청: 서버에 매수 주문 데이터 보내기
res = requests.post(order_url, headers=order_headers, json=order_data)
# 응답 상태 확인
if res.status_code == 200:
data = res.json()
# rt_cd: 한국투자증권 API 결과코드
if data['rt_cd'] == '0': # 성공
order_no = data['output']['ODNO']
print(f"매수 주문 성공!")
print(f"주문번호: {order_no}")
print(f"삼성전자 10주 시장가 매수 주문 완료")
else:
print(f"주문 실패: {data['msg1']}")
else:
print(f"주문 요청 실패: {res.status_code}")
except Exception as e:
print(f"매수 주문 에러: {e}")
Q1.
ORD_DVSN이01인 이유
A1. 한국투자증권 API 주문 구분 코드이다.
- 01: 시장가(현재 시세로 즉시 거래)
- 02: 지정가(내가 원하는 가격으로 주문)
- 03: 조건부 지정가
- 05: 장전 시간외
Q2.
ORD_UNPR이 0인 이유
A2. 시장가는 지금 시세가 얼마든 상관없이 '즉시' 사는 것이기에 지정가와 달리 가격을 지정하지 않는다. 따라서 0을 입력한다.
이렇게 우리는 어떻게 한국투자증권 API를 이용해 삼성전자 주가를 조회하고 매수까지 할지에 대해 전체 코드 흐름을 간단하게 알아보았다.
숲을 살펴보았으니 이제 이 API라는 나무에 대해 본격적으로 알아볼 시간!
API는
Application Programming Interface의 약자로, 프로그램들의 접점이 되는 것을 의미한다.
예를 들어 여행 블로그 서비스를 출시하고 싶은데 지도를 구현하는 것이 힘드니까 Google Maps API를 쓰는 것이 나의 여행블로그 서비스와 Google Maps 간의 접점을 만드는 것이다.
이 API를 어떻게 활용할 수 있는지에 대해서는 파이썬 DOCS와 마찬가지로 API 문서에서 확인할 수 있다.
한국투자증권의 API문서를 확인해보니
GET POSTGET 방식으로 진행하는 것을 볼 수 있다.그러면 이 API를 실제로 어떻게 사용할 수 있는지에 대해서는 일종의 작업이 필요하다. 자세한 내용은 아래와 같다.
Access Token 발급REQUEST 보내기자, 이제 단계별로 살펴보자.

모바일 앱으로 회원가입을 진행했다. 앱에서 추천하는 대로 ISA + 국내/해외주식 + CMA 계좌로 개설했다.
개설하려니 계좌 종류도 선택하라고 해서

위 내용을 참고하여 필자는 투자 초보이므로 안전하게 RP형으로 선택 완료!(계좌종류는 API와 전혀 상관이 없다고 한다.)
일반 은행 계좌 개설과 달리 투자 계좌라서 그런지 위험성을 엄청 강조하고 그만큼 설문도 많아서 시간이 더 많이 드는 것 같았다.

회원가입하고 로그인하고 계좌개설까지 다 마쳤는데
알고 보니 1년 전에 가입해둔 계좌가 하나 있었다...처음이 아니었네 (머쓱🙄)
자동 매매 연결 계좌는 실제 계좌 아니면 모의 투자 계좌로 설정 가능한데
자동 매매인 만큼 예기치 못한 리스크가 크고 투자 감각을 익히기 위해서
모의 투자 계좌를 만들기로 했다.
트레이딩 탭 > 모의투자 안내 > 신청/재도전 에서 모의 투자 계좌를 만들고
이때 리그 구분은 국내 주식 1000만원 + 해외주식과 선물옵션을 선택하고 금액과 기간은 기존에 설정되어 있는대로 진행했다.

(모의투자 계좌 개설 완료!)
app key와 app secret을 발급받기 위함.드디어 개발자 등록 단계이다...!
트레이딩 탭 > OpenAPI > KIS Developers > KIS Developers 서비스 신청/조회

(나중에 실전 투자 계좌도 따로 추가할 수 있으니 일단 모의 투자 계좌만 신청)
엇.. 근데 아까 모의 투자 계좌를 3개 만들었었는데(상시대회 국내주식, 상시대회 해외주식, 상시대회 선물옵션)
해외 취업을 원하지만 일단 기초 감각을 키우고 국내에서 경력을 쌓아야 하기에
상시대회 국내주식 모의 계좌로 API 신청했다.
(KIS 개발자 신청하는데 '토큰이 존재하지 않습니다.'라는 오류창이 떴다. 단순 오류였고 새로운 탭에서 신청하니까 잘 됐다.)

개발자 신청 완료!

[중요] 앱키와 시크릿키는 절대 유출 및 도용되지 않도록 주의해야한다!!!
이제 Step3부터는 실제 한국 투자 증권의 API들을 불러올 것이다.
Access Token 발급app key(앱키)와 app secret(시크릿키)을 제시하면 한국투자증권은 자사 API를 쓸 수 있는 하루용 Access Token을 발급해준다.API문서 중 접근토큰발급 관련이다.

기본 정보 - 요청(Header & Body) - 응답(Header & Body) - 예시로 구성되어 있다.
요청할 때 Body에서 appkey와 appsecret을 제시하면
응답 Body에서 access_token 토큰과 관련된 정보들을 제공한다.
확실히 처음에 코드 예제로 먼저 살펴본 내용이라 낯익은 단어들이 많아 이해하기 쉽다...!♪(´▽`)
API 호출의 기본 구조:
1. requests 모듈 import
2. API를 불러오기 위한 기본 정보(BASE_URL, APPKEY, APPSECRET, ACCESS_TOKEN 등) 정리 =>신원확인
3. url 설정(실전 or 모의 Domain)
4. headers = 서버에게 알려주는 내용
5. body = 서버에게 알려줄 때 함께 보낼 데이터 내용
6. try-except = 오류 처리 (네트워크 문제, 서버 오류 등)후 결과 출력
(참고: HTTP 메소드에는 GET, POST외에도 많지만 대부분의 증권 API 메소드가 GET, POST 위주라서 이 둘을 중심으로 정리한 내용)
위 내용을 참고해서 접근토큰발급 API 코드를 작성해보자.
함수 형식으로 정리하는 게 접근성과 보편성이 좋다고 해서def함수 키워드를 이용해 적어볼 것이다.
전체적인 파일 구조는 이러하다
AutomatedTrading/
├── auth.py
├── market_data.py
└── (auth.py 실행 후) access_token.txt (토큰 문자열이 저장된 파일)
처음에는 코드잇 예제 코드처럼 딕셔너리 구조로 간단하게 코드를 작성하려고 했지만
코드 안정성(예외처리)과 재사용성(여러 번 호출 될 수 있음) 측면에서
함수로 정의하고 토큰은 파일로 저장하는 방식이 더 적합하다고 판단했다.
(이 파일 저장방식이 실무에서도 많이 사용된다고 하기도 하고..!)
따라서 최종코드는 아래와 같다.
auth.py># 이 파일은 한국 투자증권 '접근토큰발급'을 어떻게 발급받는지에 대한 실제 코드이다.
# 파일 저장 방식으로 구성
import requests
# 한국투자증권 기본 설정
BASE_URL = "https://openapivts.koreainvestment.com:29443" # 모의 투자 Domain
APPKEY = "비밀1"
APPSECRET = "비밀2"
# 접근 토큰을 발급받아 파일로 저장
def get_access_token():
url = f"{BASE_URL}/oauth2/tokenP"
headers = {"Content-Type": "application/json"}
body = {
"grant_type": "client_credentials",
"appkey": APPKEY,
"appsecret": APPSECRET
}
try:
res = requests.post(url, headers=headers, json=body)
if res.status_code == 200:
data = res.json()
access_token = data.get('access_token')
if access_token:
# 토큰을 텍스트 파일로 저장
with open('access_token.txt', 'w') as f:
f.write(access_token)
print(f"=== 토큰 발급 및 저장 성공 ===")
print(f"토큰: {access_token[:10]}...(이하 생략)")
return access_token
else:
print(f"=== 토큰 발급 실패: 응답에 토큰이 없음 ===")
return None
else:
print(f"=== 요청 실패: {res.status_code} ===")
return None
except Exception as e:
print(f"오류 발생: {e}")
return None
# auth.py을 실행했을 때만 토큰 발급
if __name__ == "__main__":
get_access_token()
(마지막 if __name__ == "__main__": 코드에 대한 설명은 다음 Step4.에서 자세하게 다룬다.)
사실 여기서 한 발 더 나아가면
res.status_code를 다양하게 나누어 예외처리를 하거나(e.g. 400: 요청 오류, 401: 인증 실패 등) except의 경우의 수를 다양하게 처리 해주는 게 더 나으나(e.g., except requests.exceptions.Timeout: 요청시간 초과, except requests.exceptions.ConnectionError: 연결 오류 등) 지금은 최소한의 필수 코드만 구현하는 데 집중했다 :>

REQUEST 보내기이번에 해볼 것은 [국내주식] 기본 시세 > 주식당일분봉조회 이다.
접근 토큰 발급 API 파일보다 내용이 훨씬 많지만 차근차근 작성해보자 (๑•̀ㅂ•́)و✧
코드 흐름은 다음과 같다.
step1. def load_token(): 으로 access_token.txt에 저장된 토큰 불러오기
step2. def get_stock_price(stock_code):로 주식당일 분봉 조회하기
step3. 전체 코드 실행
market_data.py>의 최종 코드는 두 버전으로 나누었다.
# 이 파일은 한국 투자증권 '주식당일분봉조회' API를 어떻게 발급받는지에 대한 코드이다.
# Method: GET
import requests
# 한국투자증권 기본 설정
BASE_URL = "https://openapivts.koreainvestment.com:29443"
APPKEY = "비밀1"
APPSECRET = "비밀2"
# 저장된 토큰 파일 읽어오는 함수
def load_token():
print("토큰 읽어오는 중 ...")
try:
with open('access_token.txt', 'r') as f:
token = f.read().strip()
return token
except FileNotFoundError:
print("토큰 파일이 없습니다. auth.py 먼저 실행하세요!")
return None
except Exception as e:
print(f"토큰 읽기 오류: {e}")
return None
# 주식 분봉 데이터 조회하는 함수
def get_stock_price(stock_code):
print("get_stock_price 함수 시작")
# 저장된 토큰 불러오기
access_token = load_token()
if not access_token:
print("토큰 없어서 함수 종료~")
return
print("저장된 토큰 확인 완료")
# 기본 정보 URL
url = f"{BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-time-itemchartprice"
# REQUEST 부분
headers = {
"content-type": "application/json; charset=utf-8",
"authorization": f"Bearer {access_token}",
"appkey": APPKEY,
"appsecret": APPSECRET,
"tr_id": "FHKST03010200",
"custtype": "P" ,
}
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
"FID_INPUT_HOUR_1": "100000",
"FID_PW_DATA_INCU_YN": "Y",
"FID_ETC_CLS_CODE": "00"
}
try:
res = requests.get(url, headers=headers, params=params)
# 응답 상태 확인
if res.status_code == 200:
print(f"응답 상태 코드: {res.status_code}")
data = res.json()
# 응답 구조
if data.get('rt_cd') == '0': # 한투 API 성공코드
# output1: 종목 현재 상태(Object)
stock_info = data.get('output1', {})
stock_name = stock_info.get('hts_kor_isnm', 'N/A') # 종목명
current_price = stock_info.get('stck_prpr', 'N/A') # 현재가
current_rate = stock_info.get('prdy_ctrt', 'N/A') # 등락률
print(f"=== {stock_name} 현재 정보 ===")
print(f"현재가: {current_price}원, 등락률: {current_rate}%")
# output2: 분봉 데이터(Object Array)
candles = data.get('output2', [])
if candles:
print(f"=== {stock_name} 분봉 데이터 ===")
for i, candle in enumerate(candles[:5]):
date = candle.get('stck_bsop_date', 'N/A') # 주식 영업일자
time = candle.get('stck_cntg_hour', 'N/A') # 주식 체결시간
price = candle.get('stck_prpr', 'N/A') # 주식 현재가
volume = candle.get('cntg_vol', 'N/A') # 체결 거래량
print(f"{i+1}.{date}{time} - 가격: {price}원, 거래량: {volume}")
else:
print("분봉 데이터가 없습니다.")
else:
print(f"API 오류: {data.get('msg1', '알 수 없는 오류')}")
else:
print(f"HTTP 요청 실패: {res.status_code}")
print(f"에러 내용: {res.text}")
except Exception as e:
print(f"요청 중 네트워크/코드 오류 발생: {e}")
if __name__ == "__main__":
print("프로그램 시작")
get_stock_price("005930")
print("프로그램 종료")
# 이 파일은 한국 투자증권 '주식당일분봉조회' API를 어떻게 발급받는지에 대한 코드이다.
# Method: GET
import requests
# 한국투자증권 기본 설정
BASE_URL = "https://openapivts.koreainvestment.com:29443"
APPKEY = "비밀1"
APPSECRET = "비밀2"
# 저장된 토큰 파일 읽어오는 함수
def load_token():
print("토큰 읽어오는 중 ...")
try:
with open('access_token.txt', 'r') as f:
token = f.read().strip()
return token
except FileNotFoundError:
print("토큰 파일이 없습니다. auth.py 먼저 실행하세요!")
return None
except Exception as e:
print(f"토큰 읽기 오류: {e}")
return None
# 주식 분봉 데이터 조회하는 함수
def get_stock_price(stock_code):
print("get_stock_price 함수 시작")
# 저장된 토큰 불러오기
access_token = load_token()
if not access_token:
print("토큰 없어서 함수 종료~")
return
print("저장된 토큰 확인 완료")
# 기본 정보 URL
url = f"{BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-time-itemchartprice"
# REQUEST 부분
headers = {
"content-type": "application/json; charset=utf-8",
"authorization": f"Bearer {access_token}",
"appkey": APPKEY,
"appsecret": APPSECRET,
# "personalseckey": , # 선택사항 - 개인키 없으면 생략
"tr_id": "FHKST03010200", # transaction 거래 식별자_당일 분봉조회 API 고유코드
# "tr_cont": , # 연속조회 - 처음엔 공백
"custtype": "P" ,
# 아래 항목들은 모의 투자에서 보통 생략 가능
# "seq_no": ,
# "mac_address": ,
# "phone_number": ,
# "ip_addr":,
# "gt_uid"
}
# GET 요청에서는 POST와 달리 데이터를 URL의 쿼리 파라미터로 전송
params = {
"FID_COND_MRKT_DIV_CODE": "J", # Field ID 국내주식
"FID_INPUT_ISCD": stock_code, # Issue Code 종목코드
"FID_INPUT_HOUR_1": "100000", # 조회할 시간(HHMMSS): 9시 1분~10시 00분까지
"FID_PW_DATA_INCU_YN": "Y", # Previous Data Include
"FID_ETC_CLS_CODE": "00" # Et Cetera Class 기타 구분 코드. 기본값 00
}
# Codeit
# 짧고 간단하나 안정성이 매우 중요한 실무에서 이러한 코드는 사용 x
# try:
# res = requests.get(url, headers=headers, params=params)
# data = res.json()
# print(data["output1"]["hts_kor_isnm"]) # HTS 한글 종목명
# for item in data["output2"]:
# print(f"시간: {item['stck_bsop_date']} {item['stck_cntg_hour']} 가격:{item['stck_prpr']}")
# except Exception as e:
# print(e)
try:
res = requests.get(url, headers=headers, params=params)
# 응답 상태 확인
if res.status_code == 200:
print(f"응답 상태 코드: {res.status_code}")
data = res.json()
# 응답 구조
if data.get('rt_cd') == '0': # 한투 API 성공코드
# output1: 종목 현재 상태(Object)
stock_info = data.get('output1', {})
stock_name = stock_info.get('hts_kor_isnm', 'N/A') # 종목명
current_price = stock_info.get('stck_prpr', 'N/A') # 현재가
current_rate = stock_info.get('prdy_ctrt', 'N/A') # 등락률
print(f"=== {stock_name} 현재 정보 ===")
print(f"현재가: {current_price}원, 등락률: {current_rate}%")
# output2: 분봉 데이터(Object Array)
candles = data.get('output2', [])
if candles:
print(f"=== {stock_name} 분봉 데이터 ===")
for i, candle in enumerate(candles[:5]):
date = candle.get('stck_bsop_date', 'N/A') # 주식 영업일자
time = candle.get('stck_cntg_hour', 'N/A') # 주식 체결시간
price = candle.get('stck_prpr', 'N/A') # 주식 현재가
volume = candle.get('cntg_vol', 'N/A') # 체결 거래량
print(f"{i+1}.{date}{time} - 가격: {price}원, 거래량: {volume}")
else:
print("분봉 데이터가 없습니다.")
else:
print(f"API 오류: {data.get('msg1', '알 수 없는 오류')}")
else: # 통신 o 정보 전달 x
print(f"HTTP 요청 실패: {res.status_code}")
print(f"에러 내용: {res.text}")
except Exception as e: # 통신 x
print(f"요청 중 네트워크/코드 오류 발생: {e}")
if __name__ == "__main__":
print("프로그램 시작")
get_stock_price("005930") # 만약 종목을 바꾸고 싶으면 이 get_stock_price 파라미터 숫자만 바꿔주면 됨!
print("프로그램 종료")

여기서
if __name__ == "__main__"란?
: 파이썬의 관례적인 코드 패턴으로 해당 파일을 '직접 '실행할 때만 동작하게 한다(일종의 제어 목적). 이 문구가 없으면 다른 파일에서import할 때 원하지 않는 코드가 자동 실행되어 문제가 발생한다.
e.g.,
# market_data.py (if문 없이 작성) def get_stock_price(code): # API 호출 로직 get_stock_price("035720") # 카카오 조회하는 것으로 코드 설정 # -------- # main.py에서 import market_data # 이 순간 카카오 API 자동 호출됨 market_data.get_stock_price("005930") # 삼성전자 조회
결과: 카카오 API + 삼성전자 API 총 2번 호출
문제점: 불필요한 API 호출(시간 지연), API 서버 부하 증가 등
╭ ◜◝ ͡ ◜◝ ͡ ◜◝ ͡ ◜◝ ╮
후... 드디어 다했다..!
╰ ◟◞ ͜ ◟ ͜ ◟◞ ͜ ◟ ͜ ◟◞◟◞╯
⠀⠀⠀⠀O
⠀⠀⠀⠀⠀°
〃o ()_()
⊂⌒ (´ ^ ﻌ ^)
ヽ_っ_/ ̄ ̄ ̄/
\/___/
API 공부는 처음이라 코드를 작성하고 공부하는데 생각보다 많은 시간이 걸렸지만...
Claude 덕분에 전반적으로 코드를 보다 빨리 이해할 수 있었다.
다음에는 투자의 꽃, 투자 전략에 대해 다뤄보겠뜨아!!!