galaxis-po/docs/plans/2026-04-17-krx-openapi-migration.md
머니페니 9ab232ba12 feat: KRX Open API migration with pykrx fallback
- Add pykrx-openapi dependency
- New krx_client.py wrapper module
- ETFCollector: Open API bulk fetch + pykrx fallback
- ETFPriceCollector: Open API date-based bulk + pykrx fallback
- StockCollector: Open API base_info + daily_trade + pykrx fallback
- PriceCollector: Open API date-based bulk + pykrx fallback
- ValuationCollector: pykrx retained (Open API has no PER/PBR)
- generate_snapshots.py: Open API + pykrx fallback
- Auto-switch based on KRX_OPENAPI_KEY env var
- All 278 tests passing
2026-04-17 23:07:09 +09:00

98 lines
3.9 KiB
Markdown

# KRX Open API 전환 설계
## 배경
- pykrx는 KRX 웹 스크래핑 방식으로 불안정 (로그인 필수화, 구조 변경 시 깨짐)
- KRX Open API (openapi.krx.co.kr) 공식 REST API로 전환하여 안정성 확보
- `pykrx-openapi` 라이브러리 활용 (MIT, pip install pykrx-openapi)
## 전환 범위
### 현재 pykrx 의존 Collector (5개)
| Collector | pykrx 함수 | KRX Open API 대체 |
|---|---|---|
| StockCollector | `get_market_ticker_list`, `get_market_cap_by_ticker`, `get_market_fundamental_by_ticker` | `get_stock_base_info`, `get_stock_daily_trade`, `get_kosdaq_stock_daily_trade` |
| PriceCollector | `get_market_ohlcv` | `get_stock_daily_trade`, `get_kosdaq_stock_daily_trade` |
| ValuationCollector | `get_market_fundamental_by_ticker` | pykrx 유지 또는 별도 소스 (Open API에 PER/PBR 없음) |
| ETFCollector | `ETF_전종목기본종목().fetch()` | `get_etf_daily_trade` (종목 목록 겸용) |
| ETFPriceCollector | `get_etf_ohlcv_by_date` | `get_etf_daily_trade` |
### 주의: ValuationCollector
KRX Open API 서비스 목록에 PER/PBR/배당수익률 API가 없음.
→ ValuationCollector는 pykrx(스크래핑) 유지 또는 네이버/FnGuide 등 대체 소스 검토.
→ 1차 전환에서는 pykrx fallback으로 유지.
### 스크립트 의존
- `scripts/generate_snapshots.py``pykrx_stock.get_etf_ohlcv_by_date` 사용
- `jobs/kjb_signal_job.py``Price.ticker == "069500"` (DB 조회, pykrx 직접 의존 없음)
- `app/services/optimizer.py` — DB 조회만, pykrx 직접 의존 없음
## 구현 계획
### 1. KRX Open API 클라이언트 모듈 생성
**파일:** `backend/app/services/krx_client.py`
```python
from pykrx_openapi import KRXOpenAPI
class KRXClient:
"""Thin wrapper around pykrx-openapi with project defaults."""
def __init__(self, api_key: str = None):
self.client = KRXOpenAPI(
api_key=api_key or os.getenv("KRX_OPENAPI_KEY"),
rate_limit=10,
per_seconds=1,
timeout=30,
)
def get_etf_daily(self, date: str) -> pd.DataFrame: ...
def get_stock_daily(self, date: str, market: str) -> pd.DataFrame: ...
def get_stock_base_info(self, date: str, market: str) -> pd.DataFrame: ...
```
### 2. Collector 전환 (4개)
각 collector에 `KRX_OPENAPI_KEY` 환경변수가 있으면 Open API 사용, 없으면 pykrx fallback.
- **ETFCollector** → `get_etf_daily_trade` 로 전종목 ETF 목록 + 기본정보 취득
- **ETFPriceCollector** → `get_etf_daily_trade` 로 종가/거래량 취득
- **StockCollector** → `get_stock_base_info` + `get_stock_daily_trade` + `get_kosdaq_stock_daily_trade`
- **PriceCollector** → `get_stock_daily_trade` + `get_kosdaq_stock_daily_trade`
### 3. ValuationCollector
1차: pykrx 유지 (KRX_ID/KRX_PW 사용)
향후: 네이버 금융 또는 FnGuide 스크래핑으로 전환 검토
### 4. generate_snapshots.py 전환
`pykrx_stock.get_etf_ohlcv_by_date``KRXClient.get_etf_daily`
### 5. 의존성 변경
- `pyproject.toml`: `pykrx-openapi` 추가
- `pykrx` 는 ValuationCollector 용으로 유지
- `.env.example`: `KRX_OPENAPI_KEY` 추가
- Gitea Secrets: `KRX_OPENAPI_KEY` 추가
### 6. 테스트
- 기존 unit test 업데이트 (mock 대상 변경)
- KRX Open API mock으로 테스트
## 환경 변수
```
KRX_OPENAPI_KEY=xxx # KRX Open API 인증키 (신규)
KRX_ID=xxx # pykrx 웹 로그인 (ValuationCollector용, 유지)
KRX_PW=xxx # pykrx 웹 로그인 (ValuationCollector용, 유지)
```
## 롤백 전략
- `KRX_OPENAPI_KEY` 환경변수를 제거하면 자동으로 pykrx fallback
- 기존 pykrx 코드는 삭제하지 않고 fallback으로 유지
## 작업 완료 조건
- [ ] KRX Open API 클라이언트 모듈
- [ ] ETFCollector 전환
- [ ] ETFPriceCollector 전환
- [ ] StockCollector 전환
- [ ] PriceCollector 전환
- [ ] generate_snapshots.py 전환
- [ ] 테스트 통과
- [ ] .env.example / deploy.yml 업데이트