등락폭은 사실 두 가지다
주가가 하루에 얼마나 움직였나를 재는 방법은 하나가 아니다. 흔히 쓰는 등락률은 오늘 종가를 어제 종가로 나눈 값이다. 어제 6,257에서 오늘 6,358로 끝났으면 1.62%다. 하루가 어디서 끝났는지를 본다.
다른 하나는 일중 고저폭이다. 그날의 가장 높은 값에서 가장 낮은 값을 뺀 뒤 어제 종가로 나눈다. 하루 안에서 얼마나 출렁였는지를 본다. 이 글에서 등락폭이라고 하면 두 가지를 함께 가리킨다.
둘을 같이 봐야 하는 이유는 실제 사례가 보여 준다. 2026년 7월 29일 코스피는 종가 기준으로 5.98% 내렸는데, 장중에는 고가와 저가 사이가 16.03% 벌어져 있었다. 종가만 보면 큰 하락일이지만, 그날 안에서 사고판 사람에게는 완전히 다른 하루였다.
이 두 숫자를 재려면 종가만으로는 안 되고 시가·고가·저가·종가가 다 필요하다. 그래서 데이터 소스가 중요해진다.
왜 무료 차트 사이트로는 안 되나
야후 파이낸스처럼 무료로 시세를 주는 곳은 많다. 값이 틀린 경우는 드물다. 문제는 다른 데서 난다. 봉이 통째로 빠진다.
2026년 8월 4일 코스피 일봉을 야후에서 받으면 시가·고가·저가·종가가 전부 6,358.95로 같다. 하루 종일 한 번도 안 움직였다는 뜻인데, 거래소 원본은 시가 6,351.38 고가 6,389.40 저가 6,080.25 종가 6,358.95다. 그날 고저폭은 4.94%였다.
여기서 무서운 건 오류가 조용하다는 점이다. 값이 틀리면 이상해 보여서 눈에 걸리지만, 뭉개진 봉은 계산에 0%로 들어가 평균을 조용히 낮춘다. 그날 하나만으로 고저폭 평균이 어긋나고, 여러 날이 그러면 구간 전체가 실제보다 잔잔해 보인다.
무료 소스의 실패 양상은 틀린 값이 아니라 빠지거나 뭉개진 봉이다. 그래서 국내 지수·종목의 일별 시세는 거래소 원본을 쓴다.
KRX Open API 신청 — 화면에 적힌 그대로 네 단계
한국거래소는 openapi.krx.co.kr에서 Data Marketplace OPEN API를 운영한다. 첫 화면에 STEP 01부터 STEP 04까지 카드 넷이 있고, 실제 절차가 그 넷이다.
서비스는 지수·주식·증권상품·채권·파생상품·일반상품·ESG 일곱 갈래로 나뉜다. 코스피 일별 시세가 들어 있는 지수 서비스는 API 다섯 개를 제공하고 json과 xml 두 형식으로 응답한다.
| 단계 | 화면에 적힌 이름 | 하는 일 |
|---|---|---|
| STEP 01 | 로그인/인증키 신청 | Data Marketplace 회원가입 후 로그인(법인은 사업자 정보 등록, 개인은 본인인증 또는 소셜 로그인). 이어서 인증키를 신청하고 관리자 승인을 기다린다. |
| STEP 02 | API 서비스 목록 검색 | 서비스 목록과 명세서에서 원하는 API를 찾는다. 코스피 일별 시세는 지수 카테고리의 kospi_dd_trd다. 샘플 기능으로 호출을 미리 테스트할 수 있다. |
| STEP 03 | API 활용 신청 | 쓰려는 API마다 활용신청을 넣고 다시 관리자 승인을 기다린다. 인증키 승인과 별개 단계다. |
| STEP 04 | 개발 적용 및 서비스 개시 | 승인이 나면 그 API를 호출할 수 있다. |
첫 호출은 curl 한 줄이다
승인이 끝나면 호출 자체는 단순하다. 주소 끝에 서비스 이름을 붙이고, 인증키를 AUTH_KEY라는 헤더에 담고, 기준일 하나를 basDd로 넘긴다. 토큰을 먼저 발급받는 절차도, 서명도 없다.
응답에서 실제 데이터는 OutBlock_1 배열 안에 들어 있다. 지수 서비스는 그날 코스피·코스피 200·코스피 100처럼 여러 지수를 한꺼번에 주므로, IDX_NM이 코스피인 행을 골라 쓴다. 가격 필드는 쉼표가 섞인 문자열이라 숫자로 바꿔야 한다.
curl -H "AUTH_KEY: $KRX_API_KEY" \
"https://data-dbg.krx.co.kr/svc/apis/idx/kospi_dd_trd?basDd=20260804"{
"OutBlock_1": [
{
"BAS_DD": "20260804",
"IDX_NM": "코스피",
"OPNPRC_IDX": "6,351.38",
"HGPRC_IDX": "6,389.40",
"LWPRC_IDX": "6,080.25",
"CLSPRC_IDX": "6,358.95"
}
]
}나도 퀀트 — 파이썬 30줄로 등락폭 재기
퀀트라고 하면 거창하게 들리지만, 출발점은 가격 데이터를 받아서 직접 통계를 내 보는 일이다. 남이 만든 지표를 읽는 대신 정의를 손으로 구현해 보면 그 숫자가 무엇을 세는지 정확히 알게 된다.
아래는 표준 라이브러리만 쓰는 완결 스크립트다. 설치할 패키지가 없고, 환경변수 KRX_API_KEY에 발급받은 키만 넣으면 바로 돈다. 날짜 구간을 하루씩 돌면서 코스피 행을 뽑고, 등락률과 고저폭을 계산해 평균·중앙값·극단일 수를 낸다.
코드에서 두 줄이 중요하다. 주말을 아예 호출하지 않는 줄과, 가격 필드가 비어 있으면 건너뛰는 줄이다. 뒤 줄이 곧 휴장일 판정이다. 거래소는 휴장일에도 종목 행을 주되 가격만 빈 문자열로 채우기 때문에, 행이 왔다는 사실로 거래일을 판정하면 틀린다.
실행하면 이렇게 나온다. 아래는 7월 24일부터 8월 4일까지를 넣은 결과다.
구간을 넓히려면 날짜만 바꾸면 된다. 다만 KRX는 요청 한 번에 하루치를 주므로 100거래일을 보려면 100번 호출한다. 같은 날짜를 반복해 부르게 되면 응답을 파일로 저장해 두고 재사용하는 편이 낫다. 과거 확정치는 바뀌지 않으니 영구 캐시로 둬도 된다. 단 오늘 날짜만은 캐시하지 않는다. 장중에는 아직 0건이라 그 응답을 굳히면 그날이 영영 휴장일로 남는다.
import os, json, statistics as st, urllib.request, datetime
KEY = os.environ["KRX_API_KEY"]
BASE = "https://data-dbg.krx.co.kr/svc/apis/idx/kospi_dd_trd"
def day(basDd):
req = urllib.request.Request(f"{BASE}?basDd={basDd}", headers={"AUTH_KEY": KEY})
rows = json.loads(urllib.request.urlopen(req, timeout=30).read())["OutBlock_1"]
for r in rows:
if r["IDX_NM"].strip() == "코스피" and r["CLSPRC_IDX"].strip():
f = lambda k: float(r[k].replace(",", ""))
return f("HGPRC_IDX"), f("LWPRC_IDX"), f("CLSPRC_IDX")
return None # 휴장일 — 행은 오되 가격 필드가 비어 있다
d, end, series = datetime.date(2026, 7, 24), datetime.date(2026, 8, 4), []
while d <= end:
if d.weekday() < 5: # 주말은 애초에 호출하지 않는다
v = day(d.strftime("%Y%m%d"))
if v:
series.append(v)
d += datetime.timedelta(days=1)
chg = [abs(series[i][2] / series[i-1][2] - 1) * 100 for i in range(1, len(series))]
rng = [(series[i][0] - series[i][1]) / series[i-1][2] * 100 for i in range(1, len(series))]
print(f"거래일 {len(chg)}일")
print(f"등락률 평균 {st.mean(chg):.2f}% · 중앙 {st.median(chg):.2f}%")
print(f"고저폭 평균 {st.mean(rng):.2f}% · 중앙 {st.median(rng):.2f}%")
print(f"±2% 넘은 날 {sum(1 for c in chg if c >= 2)}일")거래일 7일
등락률 평균 6.24% · 중앙 5.12%
고저폭 평균 8.42% · 중앙 6.23%
±2% 넘은 날 4일실측 — 단일종목 레버리지 ETF 상장 전후
같은 방법으로 구간을 넓혀 실제 질문 하나를 재 봤다. 2026년 5월 27일 삼성전자와 SK하이닉스를 기초자산으로 하는 단일종목 레버리지·인버스 ETF가 한꺼번에 상장했다. 그 전후로 코스피 일간 등락폭이 달라졌는가.
상장일 직전 48거래일(3월 17일부터 5월 26일)과 직후 48거래일(5월 27일부터 8월 4일)을 같은 길이로 잘라 비교한 결과다. 평균과 중앙값을 함께 낸 이유는, 7월 31일 하루의 17.91% 같은 극단값이 평균만 끌어올렸을 가능성을 배제하기 위해서다. 중앙값도 같이 올랐으므로 그 하루를 빼도 결론은 유지된다.
| 구분 | 상장 전 48거래일 | 상장 후 48거래일 | 배율 |
|---|---|---|---|
| 일간 등락률 절대값 평균 | 2.56% | 4.08% | 1.59배 |
| 일간 등락률 절대값 중앙값 | 2.11% | 3.62% | 1.71배 |
| 일중 고저폭 평균 | 2.99% | 5.81% | 1.94배 |
| 일중 고저폭 중앙값 | 2.38% | 5.13% | 2.16배 |
| ±2% 이상 움직인 날 | 25일 / 48일 | 32일 / 48일 | 1.28배 |
처음 만나면 반드시 걸리는 함정 넷
첫째, 승인이 두 단계다. 인증키 승인 메일을 받고 바로 호출하면 401이 뜬다. 이때 에러 문구로 원인이 갈린다. Unauthorized Key는 키 자체가 무효라는 뜻이고, Unauthorized API Call은 키는 살아 있는데 그 서비스를 활용신청하지 않았다는 뜻이다. 아무 문자열이나 키로 넣고 같은 주소를 때려 보면 메시지가 갈리는지 바로 확인된다.
둘째, 접근하는 축이 반대다. 야후는 종목과 기간을 주면 시계열을 돌려주지만, KRX는 기준일 하루를 주면 그날의 전 종목을 돌려준다. 한 종목의 150일 시계열이 필요하면 150번 부르게 된다. 대신 한 번 받은 날짜는 그날의 모든 종목을 덮으므로, 날짜 단위로 캐시해 두면 보는 종목이 늘어도 추가 호출이 없다.
셋째, 휴장일에도 행이 온다. 토요일에 요청해도 1,155개 행이 오는데 가격 필드가 전부 빈 문자열이다. 행 수로 거래일을 판정하면 틀리고, 가격이 실렸는지로 판정해야 한다. 뒤집어 말하면 이 판정이 거래일 달력의 가장 정확한 정의이기도 하다.
넷째, 종가가 소급 조정되지 않는다. KRX가 주는 종가는 그날 실제로 체결된 값이다. 야후 종가는 액면분할을 소급 반영한 조정본이라 정반대다. 두 소스를 섞어 쓰면서 분할 보정을 한쪽 기준으로 일괄 적용하면 이중 보정이 나서 과거 수량이 통째로 틀어진다. 보정은 반드시 소스별로 갈라야 한다.
마지막으로 이 API의 경계도 알아 둘 것. T+1 확정치 전용이라 장중 현재가와 호가·체결은 없다. 오늘 날짜로 부르면 장이 열려 있어도 0건이다. 실시간이 필요하면 증권사 API를, 미국 주식과 환율은 다른 소스를 쓴다.
숫자를 어디까지 말할 수 있나
직접 재 보면 유혹이 하나 생긴다. 상장 후 변동폭이 1.6배에서 1.9배로 늘었으니 그 ETF 때문이라고 말하고 싶어진다. 그런데 같은 데이터가 그 결론을 지지하지 않는다.
월별로 끊어 보면 변동성은 상장 전부터 오르고 있었다. 1월 1.15%, 2월 2.49%, 3월 3.64%, 4월 2.06%, 5월 2.91%, 6월 3.57%, 7월 4.89%다. 상장일이 변곡점이 아니다. 게다가 상장 후 구간에는 7월 한 달 지수가 22% 빠진 급락장이 통째로 들어 있고, 극단적으로 움직인 날들은 상장 두 달 뒤에 몰려 있다.
자본시장연구원도 인과를 단정하지 않았다. 같은 기간 SK하이닉스 변동성이 90%에서 101%로 오르는 동안 마이크론은 85%에서 126%로, 필라델피아 반도체지수는 46%에서 75%로 더 크게 올랐다는 점을 들어, 리밸런싱 영향과 거시 불확실성 등 여러 요인이 겹친 결과로 평가한다.
그래서 이 글이 말할 수 있는 것은 여기까지다. 상장 전후로 코스피 일간 등락폭이 커졌다. 실측 변수는 등락률과 고저폭이고, 그 값이 커졌다는 사실까지가 데이터가 보증하는 범위다. 왜 커졌는지는 이 표로 판정되지 않는다.
직접 재는 일의 값어치가 여기에 있다. 남의 결론을 받아 읽으면 그 결론이 어디까지 근거를 갖는지 알 수 없지만, 스스로 재면 내 숫자가 무엇을 세었는지 정확히 알기 때문에 그 경계에서 멈출 수 있다.
