들어가며

1편에서 "AI한테 주식 정보 사이트를 만들어 달라고 했다"고 소개했다. 그러면 가장 먼저 해야 할 일은 뭘까?

당연히 데이터다. 주가, 재무제표, 수급, 배당 — 이런 데이터가 없으면 대시보드도 스크리너도 퀀트도 아무것도 할 수 없다.

이번 편에서는 증권사 API를 이용해서 3,970종목의 데이터를 자동으로 수집하는 파이프라인을 어떻게 만들었는지 정리한다.


어떤 데이터가 필요한가

주식 정보 사이트를 만들려면 최소한 이런 데이터가 필요하다:

  • 종목 마스터: 종목코드, 종목명, 시장구분(KOSPI/KOSDAQ), 업종
  • 일별 주가: 시가, 고가, 저가, 종가, 거래량 (OHLCV)
  • 재무지표: PER, PBR, EPS, BPS, ROE, 시가총액
  • 수급: 외국인, 기관, 연기금 등 투자자별 순매수
  • 배당: 배당수익률, 주당배당금, 배당빈도
  • 증권사 추천: 컨센서스 목표가, 투자의견

문제는 이 데이터를 어디서 가져오느냐다.


데이터 소스 선택: 증권사 API vs 크롤링

처음에는 웹 스크래핑으로 시작했다. 빠르게 프로토타입을 만들기에는 좋았지만, 한계가 분명했다.

  • HTML 구조가 바뀌면 파서가 깨진다
  • 장중 데이터와 마감 데이터가 섞인다
  • 제공하지 않는 데이터가 있다 (수급, 투자경고 등)

그래서 증권사 공식 API로 전환했다. 현재 사용 중인 API는 두 가지:

API 용도 특징
한국투자증권 OpenAPI 주가, 재무, 투자경고 REST, 초당 20건, 무료
키움증권 REST API 투자자별 수급 REST, 초당 10건, 무료


웹 스크래핑은 완전히 버린 건 아니고, 증권사 API가 제공하지 않는 데이터(지수 차트, 환율 등)의 보조 소스로 남겨뒀다.


한국투자증권 OpenAPI — 주가와 재무를 한 번에

API 키 발급

한국투자증권 계좌가 있으면 KIS Developers 사이트에서 무료로 API 키를 발급받을 수 있다. appkey와 appsecret 두 개가 나오는데, 이걸로 OAuth 토큰을 발급받는 구조다.

# 토큰 발급
POST https://openapi.koreainvestment.com:9443/oauth2/tokenP
{
    "grant_type": "client_credentials",
    "appkey": "발급받은_앱키",
    "appsecret": "발급받은_시크릿"
}

# 응답
{
    "access_token": "eyJ0eXAiOi...",
    "token_type": "Bearer",
    "expires_in": 86400
}

토큰 유효기간이 24시간이라 하루에 한 번만 발급받으면 된다.

현재가 조회 (FHKST01010100)

핵심은 현재가 조회 API다. TR 코드 FHKST01010100 하나로 주가 + 재무 + 투자경고까지 한 번에 가져올 수 있다.

GET /uapi/domestic-stock/v1/quotations/inquire-price
헤더: tr_id: FHKST01010100
파라미터: FID_INPUT_ISCD=005930  (삼성전자)


응답에 포함된 주요 필드:

필드 설명 예시 (삼성전자)
stck_prpr 현재가(종가) 57,800
stck_oprc 시가 57,500
stck_hgpr 고가 58,200
stck_lwpr 저가 57,100
acml_vol 누적 거래량 12,345,678
per PER 12.5
pbr PBR 1.1
eps EPS 4,624
hts_avls 시가총액(억) 3,451,234
iscd_stat_cls_code 종목상태 관리종목/투자경고 등


다른 API였으면 주가 따로, 재무 따로, 경고 따로 3번 호출해야 할 텐데, KIS는 1번이면 끝난다. 3,970종목이면 3,970번만 호출하면 된다.

수집 흐름

매일 장 마감 후 실행되는 수집 흐름은 이렇다:

1. KIS 토큰 발급
2. DB에서 활성 종목 목록 조회 (is_active=1)
3. 오늘 데이터가 이미 있는 종목은 건너뛰기
4. 종목별 현재가 API 호출 (0.06초 간격)
5. 응답에서 OHLCV → daily_prices 버퍼
6. 응답에서 PER/PBR/EPS/BPS → financials 버퍼
7. 응답에서 종목상태 → warning_type 버퍼
8. 버퍼 일괄 INSERT


3,970종목 × 0.06초 = 약 4분. 4분이면 전 종목의 주가와 재무를 한 번에 수집할 수 있다.

비거래일 자동 감지

공휴일이나 주말에 실행하면 어떻게 될까? KIS API 응답에는 stck_bsop_date(영업일자) 필드가 있다. 오늘 날짜와 다르면 비거래일이라는 뜻이다.

연속 50개 종목이 전부 비거래일로 나오면 "오늘은 장이 안 열렸구나" 판단하고 수집을 중단한다. 배치 스크립트가 매일 실행되도록 걸어놨기 때문에 이런 안전장치가 필요하다.


키움증권 REST API — 투자자별 수급

KIS API만으로는 한 가지가 부족하다. 투자자별 수급 데이터 — 외국인이 얼마나 샀는지, 기관이 얼마나 팔았는지 

수급은 키움증권 REST API로 가져온다. KIS API에도 있지만 병렬로 수집하는 것이 속도 면에서 유리하기 때문에 나눠서 한다.

수급 데이터 구조

키움 API ka10059 (주식일별투자자별매매수량구분요청)를 쓰면, 종목별로 11개 투자 주체의 일별 순매수를 받을 수 있다:

  • 외국인 / 기관합계
  • 금융투자(증권) / 보험 / 투신 / 사모펀드
  • 은행 / 기타금융 / 연기금등
  • 기타법인 / 개인

각 주체별로 순매수 주수(_net)와 순매수 금액(_amt, 백만원)을 모두 저장한다. 22개 컬럼이다.

초기 수집 vs 일일 업데이트

처음 한 번은 2015년부터의 이력을 전부 수집해야 한다. 3,970종목 × 약 25페이지 × 0.2초 = 약 5.5시간. 꽤 걸린다.

하지만 그 이후에는 --update 모드로 오늘 데이터가 없는 종목만 1~2페이지씩 받으면 된다. 약 15분이면 끝난다.

비동기 처리

키움 API는 초당 10건 제한이 있지만, aiohttp로 비동기 요청을 보내면서 0.21초 간격을 두면 안전하게 처리할 수 있다. 동기 방식보다 확실히 빠르다.


그 외 데이터 소스

증권사 API 두 개로 핵심 데이터는 해결했지만, 완전한 정보 사이트를 만들려면 더 필요하다:

데이터 소스 수집기
ETF 분배금 + 총보수 KIS API (HHKDB669102C0) etf_dist_collector.py
ETF 구성종목 KIS API etf_holdings_collector.py
증권사 컨센서스 공개 웹 데이터 broker_collector.py
시장 지수 (KOSPI/KOSDAQ) 공개 웹 데이터 market_db.py
매출/영업이익/순이익 DART OpenAPI market_db.py


가능하면 증권사 API를 우선 사용하고, 안 되는 것만 공개 데이터나 DART를 쓴다. 이유는 단순하다 — 공식 API가 안정적이고 데이터가 정확하다.


자동화: 매일 오후 8시에 전부 수집

수집기가 7개나 되면 매일 하나씩 실행하기 귀찮다. 그래서 배치 파일 하나로 묶었다:

[1/7] 주가/재무 업데이트        → market_db.py --update
[2/7] 수급 업데이트             → kiwoom_investor.py --update
[3/7] ETF 분배금 수집           → etf_dist_collector.py
[4/7] ETF 구성종목 수집         → etf_holdings_collector.py
[5/7] 증권사 추천 수집          → broker_collector.py --update
[6/7] 퀀트 검증 이력 업데이트   → finalize_monthly.py
[7/7] 주간/월간 리뷰 생성       → generate_review.py


NXT(야간시장)까지 마감되는 오후 8시 이후에 이 배치를 실행한다. 전체 소요시간은 약 20~25분.

마지막에 DB 무결성 점검도 돌린다. 주가가 비정상적으로 튀었거나, 누락된 데이터가 있으면 바로 알 수 있다.


SQLite에 전부 담기

수집한 데이터는 전부 SQLite 하나에 넣는다. krx_market.db 파일 하나가 약 1.8GB다.

테이블행 수설명

stocks 3,970 종목 마스터 (활성 3,878)
daily_prices 740만 일봉 OHLCV (2015~)
financials 8.6만 PER/PBR/ROE/EPS/시총
investor_trading 투자자별 일별 순매수
index_daily 9천 시장 지수 OHLCV
dividends 배당금
broker_consensus 증권사 컨센서스


"왜 MySQL이나 PostgreSQL 안 쓰고 SQLite?" 라고 할 수 있는데, 이유가 있다:

  • 서버 설치가 필요 없다 — 파일 하나가 곧 DB
  • 백업이 파일 복사 한 번이면 끝
  • 읽기 성능이 충분하다 (WAL 모드 기준, 동시 읽기 OK)
  • 운영 서버로 동기화할 때 파일 하나만 복사하면 된다

740만 행의 주가 데이터를 조회해도 인덱스만 잘 잡으면 수십ms 이내로 응답한다. 개인 프로젝트 규모에서는 SQLite로 충분하다.


삽질 기록

1. 장중 수집 사고

한번은 장 마감 전에 수집 스크립트를 실행해버린 적이 있다. 결과는 참혹했다 — 3,970종목의 종가가 전부 장중 가격으로 들어갔다. 대시보드를 열어보니 52주 신고가에 +30% 종목들이 잔뜩 떴다.

KIS API의 기간별시세(FHKST03010100)로 전 종목을 다시 수집해서 교정했다. 이후로는 오후 8시 이전에는 절대 실행하지 않는다.

2. Rate Limit과의 싸움

KIS API는 초당 20건 제한이지만, 너무 빠르게 보내면 429 에러가 온다. 처음에는 0.05초 간격으로 보냈다가 간헐적으로 실패해서, 0.06초로 올렸다. 429가 오면 0.5초 대기 후 1회만 재시도한다. 그래도 실패하면 해당 종목은 건너뛰고 다음으로 넘어간다.

키움은 초당 10건 제한이라 0.21초 간격을 둔다. 약간 여유를 두는 게 안전하다.

 

3. ROE  수집 문제

초기에 사용한 KIS 기본 시세 조회 API(FHKST01010100)에서는 PER, PBR, EPS, BPS는 주지만 ROE를 바로 찾지 못했다. 나중에 확인해보니 KIS에는 수익성비율 전용 API가 별도로 있었지만, 이미 직접 계산과 폴백을 조합하는 방식으로 구축한 뒤였다.

나중에 있다는 것을 발견하여 다시 DB 데이터를 전체 업데이트 했다.


정리

데이터 수집 파이프라인을 요약하면:

  • 주가 + 재무: 한국투자증권 API → 4분 (3,970종목)
  • 수급: 키움증권 REST API → 15분 (일일 업데이트)
  • 기타: ETF, 증권사 추천, 지수, DART → 각 수 분
  • 전체 자동화: 배치 파일 1개, 매일 오후 8시, 약 25분
  • 저장: SQLite 1.8GB 단일 파일

데이터가 준비됐으니 다음 편에서는 이 데이터를 API로 서빙하는 백엔드를 만든다.


다음 편 예고

  • 3편: SQLite로 3,970종목 DB 설계하기 — 스키마, 인덱스, WAL 모드, 마이그레이션

 

+ Recent posts