미국주식 백테스트 데이터 — yfinance·KIS 뭐가 다른가
yfinance는 auto_adjust 기본값이 바뀌어 Close가 사실상 수정주가가 됐고,
한국투자증권 KIS HHDFS76240000은 MODP로 직접 켜고 꺼야 하며,
비슷해 보이는 FHKST03030100은 미국주식을 다우30·나스닥100·S&P500만 줍니다.
그러니 "어디서 받았고 수정주가를 켰는지"를 캐시에 남기는 것이
라이브러리를 고르는 일보다 먼저입니다.
백테스트 결과가 이상할 때 사람들은 보통 전략을 의심합니다.
그런데 미국주식에서는 데이터를 받아 온 방식이 먼저 의심 대상인 경우가 꽤 됩니다.
국내 종목은 pykrx와 FinanceDataReader가 사실상 표준처럼 쓰이는 데 비해,
미국 종목은 선택지가 갈리고 그 선택지들이 서로 다른 답을 주기 때문입니다.
이 글은 자주 쓰이는 세 갈래 —
yfinance, 한국투자증권 KIS 해외주식 API,
FinanceDataReader — 를 같은 질문으로 대조합니다.
수익률이 좋은 소스를 고르자는 얘기가 아니라,
어느 소스가 무엇을 조용히 바꿔 놓는지를 파라미터 이름 단위로 적어 두는 것이 목적입니다.
목차
1. 먼저 정할 것 — 수정주가를 켤 것인가
미국주식은 액면분할이 잦습니다. 분할이 일어나면 그날 가격이 절반이나 4분의 1로 뚝 떨어진 것처럼 찍힙니다. 수정주가(adjusted price)는 과거 가격을 소급해 그 점프를 지워 연속된 시계열로 만드는 처리입니다.
수정주가를 끄면 전략이 분할일에 헛발질합니다. 돌파 전략이라면 분할일의 가짜 급락을 하락 신호로 읽고, 손절 로직이 걸려 있으면 그 자리에서 전량 청산합니다. 반대로 수정주가를 켜면 "지금 다시 받으면 과거 값이 또 달라질 수 있다"는 성질이 생깁니다. 배당이 한 번 더 나오면 그 이전 구간 전체가 미세하게 재조정되기 때문입니다. 어느 쪽이 정답이라기보다, 무엇을 골랐는지 기록에 남기지 않은 것이 문제입니다.
이 결정이 소스별로 어디에 숨어 있는지가 이 글의 본론입니다.
yfinance에서는 auto_adjust, KIS에서는 MODP,
FinanceDataReader에서는 고를 수 없음입니다.
2. yfinance — auto_adjust가 바뀌었다
yfinance는 설치 한 줄이면 끝나서 가장 널리 쓰입니다.
그런데 yf.download()의 auto_adjust 기본값이
False에서 True로 바뀐 이력이 있습니다
(0.2 계열 중간 릴리스에서 전환된 것으로 보고되어 있습니다 — 정확한 버전은 공식 릴리스 노트로 확인하십시오).
결과가 무엇이냐면, 기본값이 True인 버전에서는
Adj Close 컬럼이 아예 사라지고 Close가 수정주가가 됩니다.
같은 코드가 버전에 따라 다른 숫자를 돌려주는 상황이 만들어집니다.
import yfinance as yf
# ❌ 위험 — 기본값에 맡긴다. 버전에 따라 Close의 의미가 달라진다
df = yf.download("AAPL", start="2020-01-01", end="2024-12-31")
# ✅ 항상 명시한다
raw = yf.download("AAPL", start="2020-01-01", end="2024-12-31",
auto_adjust=False) # Close = 원가격, Adj Close 존재
adj = yf.download("AAPL", start="2020-01-01", end="2024-12-31",
auto_adjust=True) # Close = 수정주가, Adj Close 없음
print(raw.columns.tolist())
# ['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']
print(adj.columns.tolist())
# ['Open', 'High', 'Low', 'Close', 'Volume'] ← Adj Close 가 없다
버전 고정까지 같이 하십시오.
auto_adjust를 명시해도, 다른 기본값이 또 바뀌면 같은 일이 반복됩니다.
requirements.txt에 yfinance==<버전>으로 못 박고,
캐시 파일에 받은 날짜·라이브러리 버전·auto_adjust 값을 함께 남기는 것이
나중에 "그때 그 숫자가 왜 안 나오지"를 막아 줍니다.
같은 성격의 기준일 고정 문제를
백테스트와 실매매의 괴리 편에서도 다뤘습니다.
그 밖에 알아 둘 것
- 공식 API가 아닙니다. Yahoo Finance 페이지를 긁는 구조라
접근 방식이 바뀌면 조용히 빈
DataFrame이 돌아올 수 있습니다.len(df) == 0체크를 파이프라인에 넣어 두십시오. - 여러 티커를 한 번에 받으면 컬럼이
MultiIndex가 됩니다. 단일 티커 코드를 그대로 돌리면KeyError가 납니다. - 거래량
Volume은 수정되지 않습니다. 가격만 분할 조정되므로, 거래대금을Close × Volume으로 계산하면 분할 이전 구간이 틀어집니다.
3. KIS 해외주식 — MODP와 BYMD 되감기
한국투자증권 KIS API로도 미국주식 일봉을 받을 수 있습니다.
해외주식 기간별시세 HHDFS76240000
(/uapi/overseas-price/v1/quotations/dailyprice)입니다.
실전과 모의투자의 tr_id가 같습니다.
params = {
"AUTH": "", # (사용 안 함)
"EXCD": "NAS", # 거래소 — 나스닥. 주간거래 세션은 BAQ
"SYMB": "TSLA", # 티커
"GUBN": "0", # 0:일 1:주 2:월
"BYMD": "20241231", # 기준일 — 여기서 '과거로' 내려간다
"MODP": "1", # 0:수정주가 미반영 1:반영 ← 여기가 그 스위치
"KEYB": "",
}
여기서 yfinance와 결정적으로 다른 점이 둘입니다.
- 수정주가가 필수 파라미터입니다.
MODP를 빼면 공식 예제 기준으로ValueError가 납니다. 기본값에 조용히 당하지는 않는 대신, 반드시 내가 결정해야 합니다. - 기간을 못 줍니다.
BYMD기준일 하나만 받고 그 이전으로 내려갑니다. "2020년부터 2024년까지"를 한 번에 요청하는 형태가 없어서, 마지막 행의xymd를 다음 요청의BYMD에 넣는 루프를 직접 짜야 합니다.
한국투자증권이 공식 저장소에 함께 올려 둔 백테스터의 KIS 프로바이더는
이 호출을 최대 10회 반복하는 루프로 감싸고, MODP는 "1"로 고정해 둡니다.
응답 헤더의 tr_cont가 M 또는 F일 때만 다음 장을 요청합니다.
파라미터·필드·연속조회 구현을 줄 단위로 본 것은
HHDFS76240000 상세 편에 따로 정리했습니다.
응답 필드명이 국내와 완전히 다릅니다.
시계열은 output2에 들어오고, 일자는 xymd, 종가는 clos,
거래량은 tvol입니다. 국내 일봉 FHKST03010100의
stck_bsop_date·stck_clpr·acml_vol과 겹치는 이름이 하나도 없습니다.
국내용 파서를 재사용하면 KeyError가 나거나, 더 나쁘게는
.get() 기본값 0이 들어가 가격이 0인 캔들이 조용히 섞입니다.
4. KIS 안에서도 갈린다 — 종목 범위 제한
KIS에는 이름이 비슷한 API가 하나 더 있습니다.
해외주식 종목·지수·환율 기간별시세 FHKST03030100입니다.
기간을 inqr_strt_dt·inqr_end_dt로 줄 수 있어서 훨씬 편해 보입니다.
그런데 공식 예제 주석에 이렇게 적혀 있습니다.
해당 API로 미국주식 조회 시, 다우30·나스닥100·S&P500 종목만 조회 가능하며, 더 많은 미국주식 종목 시세를 이용하려면 해외주식 기간별시세 API를 사용하라는 안내입니다. 또한 해외지수 당일 시세는 지연시세 또는 종가시세로 제공된다고 명시되어 있습니다.
이 한 줄이 유니버스 설계를 바꿉니다.
S&P500 구성종목으로 도는 전략이라면 FHKST03030100이 편하고,
중소형주나 상장지수상품까지 훑는 스크리너라면 HHDFS76240000이 기본입니다.
편해 보이는 쪽을 골랐다가, 유니버스의 절반이 빈 응답으로 돌아오는 것을 나중에 발견하는 일이 생깁니다.
덧붙여 같은 주석에는 조건검색과의 차이도 적혀 있습니다 —
그날 거래량이나 시세가 형성되지 않은 종목은 HHDFS76240000에서는 조회되지만
해외주식 조건검색 HHDFS76410000에서는 조회되지 않습니다.
조건검색으로 유니버스를 만드는 파이프라인이라면 거래 없는 날의 종목이 통째로 빠집니다.
5. FinanceDataReader — 편한 대신 얇다
FinanceDataReader는 국내 종목으로 유명하지만 미국 티커도 받습니다.
호출이 가장 짧습니다.
import FinanceDataReader as fdr
df = fdr.DataReader("AAPL", "2020-01-01", "2024-12-31")
print(df.columns.tolist())
# ['Open', 'High', 'Low', 'Close', 'Volume', 'Change']
# 상장 종목 목록
nasdaq = fdr.StockListing("NASDAQ") # 현재 상장 종목만 — 폐지 종목은 없다
장점은 국내·해외를 같은 인터페이스로 받는다는 것이고,
단점은 수정주가 여부를 고를 수 없다는 것입니다.
auto_adjust나 MODP 같은 스위치가 노출되지 않으므로,
KIS 데이터와 대조할 때 어느 규칙으로 조정된 값인지를 내가 확정할 수 없습니다.
국내 데이터에서 같은 라이브러리들의 성격 차이를 정리한 글은
주가 데이터 수집 — pykrx·FDR·KIS에 있습니다.
6. 세 소스 한 장 비교
| 항목 | yfinance | KIS HHDFS76240000 | FinanceDataReader |
|---|---|---|---|
| 인증 | 없음 | 계좌 + appkey/appsecret | 없음 |
| 수정주가 | auto_adjust (기본값 변경 이력) | MODP 필수 지정 | 선택 불가 |
| 기간 지정 | start·end | BYMD 되감기 + 연속조회 | start·end |
| 컬럼 | Open/High/Low/Close/Volume | xymd/open/high/low/clos/tvol | Open/…/Close/Volume/Change |
| 종목 범위 | 넓음 | 넓음(FHKST03030100은 제한) | 넓음 |
| 호출 제한 | 비공식 — 과다 호출 시 차단 가능 | EGW00201 레이트리밋 | 비공식 |
| 주문 창구와 일치 | 아니오 | 예 — 같은 KIS 계좌 | 아니오 |
| 폐지 종목 | 유니버스에 없음 | 유니버스에 없음 | 유니버스에 없음 |
7. 어느 소스도 안 주는 것 — 폐지 종목
위 세 소스 모두 "현재 상장된 종목 목록"을 줍니다.
fdr.StockListing("NASDAQ")도, KIS의 종목 마스터도 마찬가지입니다.
그 목록으로 유니버스를 만들어 10년을 돌리면, 10년 전에 존재했지만 지금은 없는 종목이 통째로 빠집니다.
상장폐지된 종목은 대체로 성과가 나빴던 종목이므로, 백테스트 수익률이 실제보다 좋게 나옵니다.
이것이 생존편향(survivorship bias)입니다.
이 편향은 라이브러리를 바꿔도 사라지지 않습니다. 없애려면 시점별 구성종목 이력이 필요하고, 미국 시장에서 그 이력은 대체로 유료 데이터의 영역입니다. 무료 소스로 돌린 미국주식 백테스트라면 "생존편향이 남아 있다"를 한계로 적어 두는 것이 정직합니다. 같은 문제를 국내 시장에서 KRX 폐지 목록으로 재구성해 정량으로 확인한 기록은 생존편향 실측 편에 있습니다.
덧붙여 미국주식에는 국내에 없는 변수가 하나 더 있습니다. 환율입니다. 달러 기준 수익률과 원화 환산 수익률은 다른 숫자이고, 어느 쪽으로 볼지에 따라 최대낙폭(MDD)도 달라집니다. 시차·환전·결제까지 포함한 운영 쪽 이야기는 미국주식 자동매매 편에 정리해 두었습니다.
8. 그래서 어떻게 고르나
정답은 용도로 갈립니다. 실무에서 쓰는 기준은 이렇습니다.
| 단계 | 쓰는 소스 | 이유 |
|---|---|---|
| 아이디어 탐색 | yfinance 또는 FinanceDataReader | 인증이 없어 즉시 시작 — 버릴 가설을 빨리 버리는 단계 |
| 전략 검증 | 같은 소스 + 고정 스냅샷 | 돌릴 때마다 데이터가 달라지면 비교 자체가 성립하지 않는다 |
| 실전 투입 전 | KIS HHDFS76240000으로 대조 | 주문을 낼 창구와 캔들의 출처를 맞춰 둔다 |
| 운영 | KIS | 실시간·주문과 같은 인증·같은 레이트리밋 안에서 관리 |
핵심은 3단계입니다. yfinance로 만든 전략을 KIS로 주문 내면,
백테스트에 쓴 캔들과 봇이 보는 캔들이 다른 출처가 됩니다.
그 상태에서 실매매 성과가 백테스트와 어긋나면, 전략 탓인지 데이터 탓인지 영영 구분할 수 없습니다.
투입 전에 같은 종목·같은 구간을 두 소스로 받아 종가를 대조하는 절차 하나만 넣어도
나중에 오래 헤맬 일이 줄어듭니다.
검증 순서 전체는 실전 투입 전 3단계 검증에 있습니다.
캐시에 반드시 남길 다섯 가지
- 소스 이름 —
yfinance/kis/fdr - 수정주가 여부 —
auto_adjust값 또는MODP값 - 받은 날짜 — 같은 과거 구간도 받은 시점에 따라 값이 달라질 수 있다
- 라이브러리 버전 — 기본값이 바뀌는 사건은 또 일어난다
- 행 수 — 요청 구간 대비 짧으면 연속조회가 중간에 끊긴 것
데이터 소스를 고르는 일은 화려하지 않지만, 백테스트 결과의 신뢰도는 대체로 여기서 정해집니다. 전략을 바꿔 가며 숫자를 올리기 전에, 그 숫자가 어느 데이터 위에서 나왔는지를 먼저 고정해 두십시오. 백테스트 자체를 어떻게 설계하는지는 백테스팅 완전 가이드를, 결과를 얼마나 믿을 수 있는지는 유의성 검정 편을 함께 보십시오.
미국주식 전략을 봇으로 옮기려면
데이터 소스·수정주가·환율까지 정해 두지 않으면 백테스트와 실계좌가 갈립니다.
무엇을 어디서 받아 어떻게 고정할지부터 같이 정리해 드립니다. 24시간 빠른 답변 가능합니다.
koreainvestment/open-trading-api)의
examples_llm/overseas_stock/, legacy/Sample01/kis_ovrseastk.py,
backtester/kis_backtest/providers/kis/ 구현을 2026년 9월 21일 기준으로 읽어 정리했으며,
실계좌 호출 결과로 검증한 것이 아닙니다.
yfinance·FinanceDataReader의 동작과 기본값은 라이브러리 버전에 따라 달라지므로
각 프로젝트의 공식 릴리스 노트와 문서로 반드시 대조하십시오.
코드 예시는 구조를 보이기 위한 골격으로 그대로 실행되는 완제품이 아닙니다.
수정주가·생존편향·환율 효과는 학술적으로 알려진 검증 방법론 개념을 설명한 것이며,
과거 데이터로 얻은 어떤 결과도 미래 성과를 보장하지 않습니다.
이 글은 특정 종목·상품이나 매매 시점에 대한 권유를 담고 있지 않으며,
수익률이나 시장 방향에 대한 어떠한 전망도 하지 않습니다.
알고랩(퀀트웍스)은 투자자문업·투자일임업을 영위하지 않으며, 고객이 정한 규칙을 코드로 구현하는 도구 제작 서비스를 제공합니다.
투자 판단과 그 결과의 책임은 전적으로 투자자 본인에게 있습니다.