- 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
98 lines
3.9 KiB
Markdown
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 업데이트
|