KIS 재무비율 API 7종 — ROE·부채비율로 팩터 만들기
FHKST66430300 하나로 같이 받습니다.
응답에서 ROE는 roe_val, 부채비율은 lblt_rate로 오고,
fid_div_cls_code가 0이면 연간, 1이면 분기입니다.
재무 관련 TR은 FHKST66430100부터 7개이고 전부 /uapi/domestic-stock/v1/finance/ 아래에 있습니다.
다만 일곱 개를 한 함수로 묶으려다 두 번 막힙니다 —
같은 파라미터인데 4개는 FID_DIV_CLS_CODE(대문자), 3개는 fid_div_cls_code(소문자)로 보내야 하고,
PER·PBR은 이 7종에 아예 없습니다(eps·bps까지만 옵니다).
아래는 공식 저장소 open-trading-api 예제 소스 원문으로 7종을 전수 대조한 결과입니다.
목차
팩터는 개념이고, 숫자는 어디서 오나
퀄리티 팩터는 ROE로, 밸류 팩터는 PBR로, 안정성은 부채비율로 잰다 — 팩터가 무엇인지는 이미 여러 번 설명된 이야기입니다. 막히는 곳은 그다음입니다. 그 숫자를 매일 아침 봇이 어디서 받아 오느냐.
주가 데이터는 선택지가 많습니다. pykrx·FinanceDataReader·KIS API로 시세와 거래량은 어렵지 않게 받습니다. 그런데 재무 데이터로 넘어가면 이야기가 달라집니다. 증권사 API에 재무제표가 들어 있다는 사실 자체를 모르고, DART 크롤링부터 알아보는 경우가 많습니다.
한국투자증권 KIS API에는 재무 전용 TR이 7개 있습니다.
대차대조표·손익계산서·재무비율·수익성비율·안정성비율·성장성비율·기타주요비율.
별도 크롤링 없이 이미 발급받은 같은 appkey로 호출됩니다.
재무 API 7종 전수표
공식 저장소 examples_llm/domestic_stock/ 아래 finance_로 시작하는 예제 7개에서
tr_id·API_URL·문서 번호를 그대로 뽑은 표입니다.
| TR ID | 내용 | 엔드포인트 (/uapi/domestic-stock/v1/finance/ 뒤) | 문서 |
|---|---|---|---|
FHKST66430100 | 대차대조표 | balance-sheet | 국내주식-078 |
FHKST66430200 | 손익계산서 | income-statement | 국내주식-079 |
FHKST66430300 | 재무비율 | financial-ratio | 국내주식-080 |
FHKST66430400 | 수익성비율 | profit-ratio | 국내주식-081 |
FHKST66430500 | 기타주요비율 | other-major-ratios | 국내주식-082 |
FHKST66430600 | 안정성비율 | stability-ratio | 국내주식-083 |
FHKST66430800 | 성장성비율 | growth-ratio | 국내주식-085 |
FHKST66430700과 문서 번호 084는 비어 있습니다.
예제 목록에 해당 항목이 없어서 무엇이 빠졌는지는 소스만으로 확인되지 않습니다.
번호가 연속일 것이라 가정하고 700을 찍어 보는 코드를 짜지 마십시오.
공통 파라미터는 세 개뿐이라 호출 자체는 단순합니다.
| 파라미터 | 값 |
|---|---|
fid_div_cls_code | 0 연간 · 1 분기 |
fid_cond_mrkt_div_code | J |
fid_input_iscd | 종목코드 (예제 기본값 000660) |
함정 1 — 같은 파라미터의 대소문자가 갈린다
7개를 하나의 공통 함수로 묶는 것이 자연스러운 설계입니다. 파라미터가 같으니까요.
그런데 예제 소스의 params 딕셔너리를 나란히 놓고 보면 표기가 일치하지 않습니다.
# finance_financial_ratio.py (FHKST66430300)
params = {
"FID_DIV_CLS_CODE": fid_div_cls_code, # ← 대문자
"fid_cond_mrkt_div_code": fid_cond_mrkt_div_code,
"fid_input_iscd": fid_input_iscd,
}
# finance_stability_ratio.py (FHKST66430600)
params = {
"fid_input_iscd": fid_input_iscd,
"fid_div_cls_code": fid_div_cls_code, # ← 소문자
"fid_cond_mrkt_div_code": fid_cond_mrkt_div_code,
}
| 표기 | 해당 TR |
|---|---|
FID_DIV_CLS_CODE(대문자) — 4개 | 대차대조표 FHKST66430100 · 손익계산서 FHKST66430200 · 재무비율 FHKST66430300 · 수익성비율 FHKST66430400 |
fid_div_cls_code(소문자) — 3개 | 기타주요비율 FHKST66430500 · 안정성비율 FHKST66430600 · 성장성비율 FHKST66430800 |
이 차이가 만드는 증상이 고약합니다. 인증 오류도 아니고 파라미터 누락 오류도 아닙니다.
연간을 요청했는데 분기가 오거나, 기간 구분이 무시된 결과가 오는 식으로 조용히 틀립니다.
응답은 200이고 stac_yymm에 날짜도 들어 있어서 로그로는 잡히지 않습니다.
에러코드가 뜨는 문제가 아니라는 점이 가장 위험합니다.
가장 안전한 대응은 두 표기를 모두 보내는 것이 아니라 TR별로 어느 표기를 쓰는지 표로 들고 있는 것입니다. 아래 코드에서 그렇게 처리합니다.
함정 2 — PER·PBR은 여기 없다
밸류 팩터를 만들려고 재무비율 FHKST66430300을 호출하면 기대했던 필드가 없습니다.
응답에 들어오는 것은 eps(EPS) · bps(BPS) · sps(주당매출액)까지입니다.
이유는 단순합니다. PER·PBR은 분모에 현재가가 들어가는 주가 배수라서 재무제표 API가 돌려줄 성질의 값이 아닙니다. 직접 계산해야 합니다.
# PBR = 현재가 / BPS, PER = 현재가 / EPS
# 현재가는 시세 API에서 따로 받는다 (FHKST01010100 → stck_prpr)
bps = float(ratio_row["bps"]) # 재무비율 FHKST66430300
price = float(quote_row["stck_prpr"]) # 현재가 FHKST01010100
pbr = price / bps if bps else None
# ⚠ 두 값의 기준 시점이 다르다는 것을 반드시 같이 기록한다.
record = {
"stck_shrn_iscd": code,
"pbr": pbr,
"bps_stac_yymm": ratio_row["stac_yymm"], # 재무 결산 년월
"price_asof": today, # 시세 조회 일자
}
현재가를 받는 쪽은 KIS API 현재가 조회 편에 정리돼 있습니다. 그리고 7종 API는 모두 종목코드를 하나씩만 받기 때문에, 유니버스 전체를 돌리려면 종목 마스터 파일에서 종목 목록을 먼저 만들어 두어야 합니다.
어느 지표가 어느 TR에 있나
예제의 column_mapping을 그대로 옮긴 것입니다. 필드명은 전부 소문자입니다.
| TR | 주요 필드 (원문 그대로) |
|---|---|
재무비율FHKST66430300 | roe_val ROE · eps EPS · bps BPS · sps 주당매출액 · lblt_rate 부채비율 · rsrv_rate 유보비율 · grs 매출액증가율 · bsop_prfi_inrt 영업이익증가율 · ntin_inrt 순이익증가율 |
안정성비율FHKST66430600 | lblt_rate 부채비율 · bram_depn 차입금의존도 · crnt_rate 유동비율 · quck_rate 당좌비율 |
수익성비율FHKST66430400 | cptl_ntin_rate 총자본순이익율 · self_cptl_ntin_inrt 자기자본순이익율 · sale_ntin_rate 매출액순이익율 · sale_totl_rate 매출액총이익율 |
성장성비율FHKST66430800 | grs 매출액증가율 · bsop_prfi_inrt 영업이익증가율 · equt_inrt 자기자본증가율 · totl_aset_inrt 총자산증가율 |
기타주요비율FHKST66430500 | payout_rate 배당성향 · eva EVA · ebitda EBITDA · ev_ebitda EV/EBITDA |
손익계산서FHKST66430200 | sale_account 매출액 · sale_cost 매출원가 · sale_totl_prfi 매출총이익 · bsop_prti 영업이익 · sell_mang 판관비 · depr_cost 감가상각비 · op_prfi 경상이익 · thtr_ntin 당기순이익 |
대차대조표FHKST66430100 | cras 유동자산 · fxas 고정자산 · total_aset 자산총계 · flow_lblt 유동부채 · fix_lblt 고정부채 · total_lblt 부채총계 · cpfn 자본금 · prfi_surp 이익잉여금 · total_cptl 자본총계 |
필드가 겹칩니다. lblt_rate(부채비율)는 재무비율과 안정성비율 양쪽에 있고,
grs·bsop_prfi_inrt는 재무비율과 성장성비율 양쪽에 있습니다.
즉 ROE·부채비율·성장률만 필요하면 재무비율 FHKST66430300 한 번으로 끝납니다.
7개를 다 부르는 것은 호출 수만 일곱 배로 늘리는 선택일 수 있습니다.
7종을 한 종목에 모으기
대소문자 차이를 표로 가두고, 필요한 TR만 골라 부르는 구조입니다.
import os, time, requests
BASE = "https://openapi.koreainvestment.com:9443"
FIN = "/uapi/domestic-stock/v1/finance/"
# TR ID → (엔드포인트, 기간구분 파라미터 표기)
# ★ 대소문자가 TR마다 다르다. 표로 들고 있는 편이 안전하다.
FINANCE_TR = {
"balance_sheet": ("FHKST66430100", "balance-sheet", "FID_DIV_CLS_CODE"),
"income_statement": ("FHKST66430200", "income-statement", "FID_DIV_CLS_CODE"),
"financial_ratio": ("FHKST66430300", "financial-ratio", "FID_DIV_CLS_CODE"),
"profit_ratio": ("FHKST66430400", "profit-ratio", "FID_DIV_CLS_CODE"),
"other_ratios": ("FHKST66430500", "other-major-ratios", "fid_div_cls_code"),
"stability_ratio": ("FHKST66430600", "stability-ratio", "fid_div_cls_code"),
"growth_ratio": ("FHKST66430800", "growth-ratio", "fid_div_cls_code"),
}
def fetch_finance(kind: str, code: str, token: str, yearly: bool = True):
tr_id, path, div_key = FINANCE_TR[kind]
params = {
div_key: "0" if yearly else "1", # 0=연간 1=분기
"fid_cond_mrkt_div_code": "J",
"fid_input_iscd": code,
}
headers = {
"authorization": f"Bearer {token}",
"appkey": os.environ["KIS_APP_KEY"], # 키는 환경변수로
"appsecret": os.environ["KIS_APP_SECRET"],
"tr_id": tr_id,
}
r = requests.get(BASE + FIN + path, headers=headers, params=params, timeout=10)
r.raise_for_status()
return r.json().get("output", [])
def quality_snapshot(code: str, token: str) -> dict:
"""ROE·부채비율·성장률은 재무비율 하나로 끝난다."""
rows = fetch_finance("financial_ratio", code, token)
if not rows:
return {}
latest = rows[0] # 최근 결산부터 내려온다
return {
"code": code,
"stac_yymm": latest.get("stac_yymm"), # ★ 결산 년월을 반드시 같이 남긴다
"roe": latest.get("roe_val"),
"debt_ratio": latest.get("lblt_rate"),
"eps": latest.get("eps"),
"bps": latest.get("bps"),
"sales_grow": latest.get("grs"),
}
# 유니버스를 돌 때는 호출 간격을 둔다
for code in ["005930", "000660", "035420"]:
print(quality_snapshot(code, TOKEN))
time.sleep(0.2)
time.sleep(0.2)를 넣은 이유는 7종 API가 종목 하나씩만 받기 때문입니다.
코스피·코스닥 전 종목을 돌면 호출 수가 수천 건이 됩니다.
유량 제한에 걸릴 때의 대응은 앞서 언급한 에러코드 정리에 그대로 적용되고,
조건에 맞는 종목을 먼저 좁히는 방법은 스크리너 편을 참고하십시오.
팩터로 쓰기 전에 알아야 할 두 가지 편향
여기까지가 데이터를 받는 방법입니다. 받은 숫자를 백테스트에 넣는 순간부터는 다른 문제가 시작됩니다. 재무 데이터 특유의 편향 두 가지는 둘 다 성과를 좋아 보이게 만드는 방향으로 작용합니다.
1. 룩어헤드 편향 — stac_yymm은 공시일이 아니다
응답의 stac_yymm은 결산 년월입니다. 그 숫자가 시장에 공개된 날짜가 아닙니다.
12월 결산 수치는 이듬해 봄에야 공시되는데, 백테스트에서 1월 1일부터 그 값을 쓰면
당시에는 알 수 없던 정보로 매매한 것이 됩니다.
최소한 결산월로부터 충분한 지연을 두고 반영해야 하며, 정확히 하려면 실제 공시일을 별도로 확보해야 합니다.
2. 생존편향 — API는 지금 살아 있는 종목만 준다
재무 API는 현재 상장된 종목에 대해 응답합니다. 상장폐지된 회사는 조회되지 않습니다. 과거 시점의 유니버스를 지금 종목 목록으로 구성하면 망한 회사가 전부 빠진 표본이 됩니다. 같은 문제를 백테스트 엔진 차원에서 다룬 이야기는 KIS 공식 백테스터 편에 정리돼 있습니다.
이 글은 어떤 재무 지표가 수익을 준다고 말하지 않습니다. ROE가 높은 종목이 더 오른다거나 부채비율이 낮으면 안전하다는 주장은 여기 없습니다. 팩터는 수익률을 사후에 설명하기 위한 학술적 분류이고, 특정 팩터의 과거 성과가 앞으로도 이어진다는 보장은 없습니다. 과거 데이터로 계산한 어떤 결과도 미래 수익을 보장하지 않습니다. 위 코드의 필드 선택은 예시일 뿐 근거 있는 임계값이나 종목 추천이 아닙니다.
성과를 재는 쪽은 샤프지수·MDD 계산과 매매비용 반영을 같이 보셔야 숫자가 현실에 가까워집니다. 비용을 빼지 않은 팩터 백테스트는 거의 예외 없이 좋게 나옵니다.
정리
- ROE·부채비율·성장률은
FHKST66430300한 번 —roe_val·lblt_rate·grs - 재무 TR은
FHKST66430100부터 7개,700과 문서 084는 비어 있다 fid_div_cls_code는 4개가 대문자, 3개가 소문자 — 틀려도 에러가 안 난다- PER·PBR은 없다 —
eps·bps를 현재가로 나눠야 한다 stac_yymm은 결산월이지 공시일이 아니다 — 지연 없이 쓰면 룩어헤드
팩터 규칙은 있는데 자동화가 막혔다면
어떤 지표를 어느 주기로 받아 어떻게 랭킹할지 정해 두셨다면
그대로 도는 프로그램으로 만들어 드립니다. 24시간 빠른 답변 가능합니다.
koreainvestment/open-trading-api의 examples_llm/domestic_stock/ 예제 소스와
MCP/KIS Code Assistant MCP/data.csv의 column_mapping 원문을 직접 대조해 정리한 것입니다.
실호출로 응답을 받아 검증한 것이 아니므로 실제 응답 값과 다를 수 있으며,
비어 있는 FHKST66430700·문서 084가 무엇인지는 단정하지 않았습니다.
API 구성·필드·유량 정책은 예고 없이 변경될 수 있으므로 KIS Developers 공식 문서로 현재 기준을 반드시 확인하십시오.
본문의 팩터·재무비율 설명은 학술적으로 통용되는 분류 개념을 소개한 것이며,
과거 성과가 미래 수익을 보장하지 않습니다. 특정 종목·지표에 대한 투자 권유가 아닙니다.
알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객이 지정한 규칙을 자동화하는 도구를 제작·납품합니다.
투자 판단과 그 결과는 이용자 본인에게 귀속됩니다.