docs: 데이터 보유 범위 및 백필 가이드 추가

- docs/DATA_COVERAGE.md 신규 생성: 엔드포인트별 실제 DB 보유 범위,
  이론적 최대 범위, 백필 방법, SQL 확인 쿼리 포함
- FINRA/Alpaca/stocks/filings 엔드포인트 description에 데이터 범위 및
  백필 방법 안내 추가 (Swagger UI에 표시됨)

현재 백필 필요 항목:
- FINRA: 2026-02-10~ 28거래일만 존재 → 2025년치 백필 권장

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
main
I Luk Kim 5 months ago
parent 2c4b4d583d
commit fd4816dbbd

@ -69,7 +69,11 @@ async def alpaca_status():
"/bars/{ticker}",
response_model=AlpacaBarsResponse,
summary="Get Alpaca bars (raw, no DB)",
description="Fetch historical bars directly from Alpaca without storing in DB.",
description=(
"Fetch historical bars directly from Alpaca without storing in DB.\n\n"
"**주의**: 이 엔드포인트는 DB에 저장하지 않음. 저장이 필요하면 `/alpaca/data/{ticker}` 사용.\n\n"
"**이론적 범위**: Alpaca API 제공 범위 (~20년). `ALPACA_API_KEY` / `ALPACA_SECRET_KEY` 필수."
),
)
@with_cache(namespace="alpaca:bars", ttl=86400, key_params=["ticker", "interval", "start_date", "end_date", "limit"])
async def get_alpaca_bars(
@ -116,14 +120,15 @@ async def get_alpaca_bars(
"/data/{ticker}",
response_model=PriceDataResponse,
summary="Get price data via Alpaca (with DB storage)",
description="""
Fetch OHLCV price data from Alpaca, store in AlpacaPriceData table, and return
in PriceDataResponse format. Includes vwap and trade_count in metadata.
- Requires `ALPACA_API_KEY` / `ALPACA_SECRET_KEY`
- Uses `data_source = "ALPACA"` to distinguish from Yahoo data
- Supports: 1m, 5m, 15m, 1h, 1d, 1w, 1mo intervals
""",
description=(
"Fetch OHLCV price data from Alpaca, store in AlpacaPriceData table, and return "
"in PriceDataResponse format. Includes vwap and trade_count in metadata.\n\n"
"- Requires `ALPACA_API_KEY` / `ALPACA_SECRET_KEY`\n"
"- Uses `data_source = \"ALPACA\"` to distinguish from Yahoo data\n"
"- Supports: 1m, 5m, 15m, 1h, 1d, 1w, 1mo intervals\n\n"
"**현재 DB 보유**: 현재 테스트 데이터만 존재 (AAPL 27일치). "
"백필은 이 엔드포인트를 원하는 날짜 범위로 호출하면 자동으로 DB에 누적됨."
),
)
@with_cache(namespace="alpaca:data", ttl=86400, key_params=["ticker", "interval", "start_date", "end_date"])
async def get_alpaca_price_data(

@ -39,6 +39,8 @@ logger = logging.getLogger("app.api.v1.filings")
"Search SEC filings for the given ticker. Supported form types: **8-K, 6-K, 20-F, 40-F**.\n\n"
"Auto-indexes filings from EDGAR on first request (or when `force_refresh=true`). "
"Results are cached for 1 hour.\n\n"
"**현재 DB 보유**: 1994-01-05 ~ 현재, 1598 티커. "
"처음 조회하는 티커는 SEC EDGAR에서 자동 인덱싱 (수 초 소요).\n\n"
"**Example**: `GET /filings/search/AAPL?form_type=8-K&limit=10`"
),
)

@ -28,7 +28,12 @@ router = APIRouter()
"/short-volume/{symbol}",
response_model=ShortVolumeResponse,
summary="Get short volume data for a symbol",
description="Query FINRA RegSHO short sale volume. Auto-ingests if data is missing.",
description=(
"Query FINRA RegSHO short sale volume. Auto-ingests if data is missing.\n\n"
"**현재 DB 보유**: 2026-02-10 ~ 현재 (약 28거래일). "
"더 긴 과거 데이터가 필요하면 `POST /finra/admin/ingest?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD` 로 백필하세요.\n\n"
"**데이터 소스**: FINRA RegSHO CDN (공개, API 키 불필요). 주말/공휴일 데이터 없음."
),
)
@with_cache(namespace="finra:short-volume", ttl=None, key_params=["symbol", "days", "limit"])
async def get_short_volume(
@ -67,7 +72,11 @@ async def get_short_volume(
"/short-ratio/{symbol}",
response_model=ShortRatioHistoryResponse,
summary="Get short ratio history for a symbol",
description="Return daily short_ratio (aggregated across markets) for the last N days.",
description=(
"Return daily short_ratio (aggregated across markets) for the last N days.\n\n"
"**현재 DB 보유**: 2026-02-10 ~ 현재. z-score 신호 품질 향상을 위해 1년치 이상 백필 권장.\n\n"
"백필: `POST /finra/admin/ingest?start_date=2025-01-02&end_date=2025-12-31`"
),
)
@with_cache(namespace="finra:short-ratio", ttl=None, key_params=["symbol", "days"])
async def get_short_ratio(
@ -99,7 +108,14 @@ async def get_short_ratio(
"/admin/ingest",
response_model=IngestResponse,
summary="Manually ingest FINRA short volume data",
description="Download and ingest FINRA short volume file(s) for a specific date or date range.",
description=(
"Download and ingest FINRA short volume file(s) for a specific date or date range.\n\n"
"**백필 예시**:\n"
"- 단일 날짜: `?date=2025-01-15`\n"
"- 날짜 범위: `?start_date=2025-01-01&end_date=2025-12-31`\n"
"- 이미 있는 데이터 재인제스트: `?start_date=...&end_date=...&force=true`\n\n"
"주말/공휴일은 자동으로 건너뜀. 1년치 기준 약 20-40분 소요."
),
)
async def ingest_short_volume(
date_str: Optional[str] = Query(None, alias="date", description="Single date (YYYY-MM-DD)"),

@ -85,7 +85,15 @@ async def get_index_constituents(
)
@router.get("/most-active", summary="Most actively traded stocks by volume")
@router.get(
"/most-active",
summary="Most actively traded stocks by volume",
description=(
"Get most actively traded stocks from Yahoo Finance.\n\n"
"**⚠️ 실시간 전용**: DB에 저장되지 않음. 과거 데이터 조회 불가.\n"
"캐시 TTL: 1시간 (`X-Cache: HIT/MISS` 헤더 포함)."
),
)
@with_cache(namespace="stocks:most-active", ttl=3600, key_params=["limit"])
async def get_most_active_stocks(
response: Response,
@ -184,7 +192,15 @@ async def get_most_active_stocks(
)
@router.get("/52-week-gainers", summary="Top 52-week gaining stocks")
@router.get(
"/52-week-gainers",
summary="Top 52-week gaining stocks",
description=(
"Get 52-week top gaining stocks from Yahoo Finance.\n\n"
"**⚠️ 실시간 전용**: DB에 저장되지 않음. 과거 데이터 조회 불가.\n"
"캐시 TTL: 1시간. 첫 호출 시 15-30초 소요 (웹 스크래핑)."
),
)
@with_cache(namespace="stocks:52-week-gainers", ttl=3600, key_params=["limit", "max_pages"])
async def get_52week_gainers(
response: Response,
@ -283,7 +299,15 @@ async def get_52week_gainers(
)
@router.get("/trending", summary="Trending stocks combining most active and 52-week gainers")
@router.get(
"/trending",
summary="Trending stocks combining most active and 52-week gainers",
description=(
"Get trending stocks by combining most-active + 52-week gainers.\n\n"
"**⚠️ 실시간 전용**: DB에 저장되지 않음. 과거 데이터 조회 불가.\n"
"캐시 TTL: 30분. 병렬 스크래핑으로 최적화."
),
)
@with_cache(namespace="stocks:trending", ttl=1800, key_params=["n", "most_active_limit", "gainers_limit"])
async def get_trending_stocks(
response: Response,

@ -0,0 +1,243 @@
# Data Coverage & Backfill Guide
각 API 엔드포인트의 **실제 DB 보유 데이터 범위**와 **과거 데이터 백필 방법**을 정리한 문서입니다.
> 마지막 업데이트: 2026-03-23
> DB 실측 기준
---
## 요약 테이블
| 엔드포인트 | 데이터 소스 | DB 저장 | 현재 보유 범위 | 이론적 최대 범위 | 백필 필요 |
|---|---|---|---|---|---|
| `/price` | Yahoo Finance | ✅ | 2018-04-24 ~ 현재 (1682 티커) | 20년+ | 요청 기반 자동 누적 |
| `/alpaca/bars`, `/alpaca/data` | Alpaca API | ✅ | 테스트 데이터만 | ~20년 | 필요시 수동 |
| `/finra/short-volume` | FINRA CDN | ✅ | **2026-02-10 ~ 현재 (28거래일)** | 수년치 | **⚠️ 백필 권장** |
| `/etf/holdings` | SEC EDGAR (NPORT) | ✅ (스냅샷) | 요청 기반 자동 누적 | 2019년~ | 요청 기반 자동 누적 |
| `/filings/search` | SEC EDGAR | ✅ | 1994-01-05 ~ 현재 (1598 티커) | 1994년~ | 요청 기반 자동 누적 |
| `/stocks/most-active` | Yahoo 실시간 스크래핑 | ❌ | 실시간만 | 없음 | 해당 없음 |
| `/stocks/52-week-gainers` | Yahoo 실시간 스크래핑 | ❌ | 실시간만 | 없음 | 해당 없음 |
| `/stocks/trending` | Yahoo 실시간 스크래핑 | ❌ | 실시간만 | 없음 | 해당 없음 |
| `/overlay/{symbol}` | Yahoo/YouTube/Wikipedia/FINRA | ✅ | 2026-03-17 ~ 현재 (50 심볼) | 서비스 시작 이후 | 과거 백필 불가 |
| `/overlay/{symbol}/headlines` | Yahoo Finance RSS | ✅ | 2026-02-24 ~ 현재 | 서비스 시작 이후 | 과거 백필 불가 |
| `/overlay/{symbol}/wiki` | Wikipedia Pageviews API | ✅ | 2015-12-26 ~ 현재 | 2015년~ | 자동 수집됨 |
---
## 엔드포인트별 상세
---
### `/api/v1/price` — 주가 (Yahoo Finance)
**현재 DB 보유**: 2018-04-24 ~ 현재, 1682 티커, 약 190만 행
**조회 파라미터**:
- `period`: `1d` `7d` `30d` `1m` `3m` `6m` `1y` `2y` `5y` `max`
- `start_date` + `end_date`: 특정 날짜 범위 (YYYY-MM-DD)
- `quarters`: `["2024Q1", "2024Q2"]` 형식
**동작 방식**: DB 캐시 우선 → 누락 구간만 Yahoo Finance에서 실시간 페치 → 자동 저장
**백필**: 별도 작업 불필요. 처음 조회 시 자동으로 인제스트됨.
```bash
# 특정 티커 과거 데이터 미리 채우기 (선택 사항)
curl -X POST "http://localhost:18001/api/v1/price/data" \
-H "Content-Type: application/json" \
-d '{"ticker": "AAPL", "start_date": "2020-01-01", "end_date": "2024-12-31"}'
```
---
### `/api/v1/alpaca` — 주가 (Alpaca API)
**현재 DB 보유**: 테스트 데이터만 (AAPL 27일치). 실 운용 데이터 없음.
**조회 파라미터**:
- `start_date` + `end_date` 필수
- `interval`: `1m` `5m` `15m` `1h` `1d` `1w` `1mo`
**필수 조건**: `ALPACA_API_KEY`, `ALPACA_SECRET_KEY` 환경 변수 설정 필요.
**백필**: `/alpaca/data/{ticker}` 엔드포인트 호출 시 자동으로 DB에 저장됨.
```bash
# Alpaca 과거 데이터 수동 백필
curl "http://localhost:18001/api/v1/alpaca/data/AAPL?start_date=2020-01-01&end_date=2024-12-31&interval=1d"
```
> ⚠️ `/alpaca/bars`는 DB에 저장되지 않음 (raw 조회 전용). DB 저장은 `/alpaca/data`만 해당.
---
### `/api/v1/finra` — FINRA 공매도 (RegSHO)
**현재 DB 보유**: 2026-02-10 ~ 2026-03-20, 28거래일 (서비스 가동 시점부터)
> 데이터 소스인 FINRA CDN은 수년치 과거 파일을 보유하고 있으나, 현재 DB에는 최근 28일치만 있음.
**조회 파라미터**:
- `days`: 최근 N일 (기본 30, 최대 365)
- `limit`: 반환 최대 건수 (기본 100, 최대 1000)
**⚠️ 백필 방법**:
```bash
# 단일 날짜 백필
POST /api/v1/finra/admin/ingest?date=2025-01-02&force=false
# 날짜 범위 백필 (권장)
POST /api/v1/finra/admin/ingest?start_date=2024-01-01&end_date=2026-02-09&force=false
```
curl 예시:
```bash
# 2024년 전체 백필 (~252 거래일 × ~11,000 심볼 = ~280만 행)
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2024-01-01&end_date=2024-12-31"
# 2025년 전체 백필
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2025-01-01&end_date=2025-12-31"
# 운영 공백 구간 채우기 (2026-01-01 ~ 2026-02-09)
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2026-01-01&end_date=2026-02-09"
```
> 주의: 1년치 백필 시 ~3000 HTTP 요청 + DB write. 수십 분 소요될 수 있음. FINRA CDN은 API 키 없이 사용 가능하나 과부하를 피하기 위해 범위를 분할해서 실행 권장.
---
### `/api/v1/etf` — ETF 보유 종목 (SEC EDGAR)
**현재 DB 보유**: 요청된 ETF 스냅샷만 (요청 기반 자동 누적)
**이론적 범위**: 2019년~ (NPORT-P 도입 이후). 일부 ETF는 더 이전 N-Q 파일링 존재.
**조회 파라미터**:
- `as_of_date`: 기준 날짜 (YYYY-MM-DD). 가장 가까운 파일링 자동 선택.
- 생략 시: 가장 최신 파일링 반환.
**동작 방식**: 첫 조회 시 SEC EDGAR에서 자동 페치 → 스냅샷 DB 저장. 재조회 시 캐시.
```bash
# 특정 날짜 기준 ETF 보유 종목 조회 (자동 캐시)
curl "http://localhost:18001/api/v1/etf/holdings/QQQ?as_of_date=2023-12-31"
curl "http://localhost:18001/api/v1/etf/holdings/SPY?as_of_date=2022-06-30"
```
> ETF 출시 이전 날짜 요청 시 `availability` 필드에 가능한 날짜 범위 반환됨.
---
### `/api/v1/filings` — SEC 공시 (EDGAR)
**현재 DB 보유**: 1994-01-05 ~ 현재, 1598 티커, 약 8000일치
**지원 양식**: `8-K`, `6-K`, `20-F`, `40-F`
**조회 파라미터**:
- `form_type`: 쉼표 구분 (예: `8-K,6-K`)
- `start_date` + `end_date`: 공시 날짜 범위
- `limit` / `offset`: 페이지네이션 (최대 100)
**동작 방식**: 첫 조회 시 SEC EDGAR 자동 인덱싱 → DB 저장. 이후 DB 캐시. 1시간 Redis 캐시.
**백필**: 별도 작업 불필요. `GET /filings/search/{ticker}` 최초 호출 시 자동 인덱싱됨.
```bash
# 특정 티커 전체 8-K 조회 (자동 인덱싱)
curl "http://localhost:18001/api/v1/filings/search/AAPL?form_type=8-K&start_date=2020-01-01"
# 여러 티커 일괄 인덱싱
curl -X POST "http://localhost:18001/api/v1/filings/search/bulk" \
-H "Content-Type: application/json" \
-d '{"tickers": ["AAPL","MSFT","NVDA","TSLA"], "form_type": "8-K", "limit_per_ticker": 100}'
```
---
### `/api/v1/stocks` — 시장 현황 (Yahoo Finance 실시간)
**현재 DB 보유**: **없음**. 실시간 스크래핑 전용.
| 엔드포인트 | 데이터 | 캐시 TTL |
|---|---|---|
| `/stocks/most-active` | 실시간 상위 ~170 종목 | 1시간 |
| `/stocks/52-week-gainers` | 실시간 상위 ~1350 종목 | 1시간 |
| `/stocks/trending` | most-active + gainers 결합 | 30분 |
> 과거 데이터 조회 불가. 시계열 추적이 필요하면 주기적으로 `/price` 엔드포인트를 통해 개별 종목 가격을 저장하는 별도 배치 작업 필요.
---
### `/api/v1/overlay` — Attention Overlay
**현재 DB 보유**:
| 데이터 | 보유 범위 |
|---|---|
| Overlay score (feature records) | 2026-03-17 ~ 현재, 50 심볼 |
| 뉴스 헤드라인 | 2026-02-24 ~ 현재 |
| Wikipedia 페이지뷰 | 2015-12-26 ~ 현재 (풍부) |
| YouTube 멘션 | 서비스 시작 이후 |
| Google Trends | 서비스 시작 이후 |
**조회 파라미터**:
- `/{symbol}`: 최신 composite score
- `/{symbol}/history?days=N`: 스코어 시계열 (최대 365일)
- `/{symbol}/headlines?hours=N`: 뉴스 (최대 168시간)
- `/{symbol}/wiki?days=N`: Wikipedia 페이지뷰 (최대 90일)
- `/{symbol}/crowding`: FINRA 기반 crowding 지표
**지원 심볼**: 기본 50개 (`TOP_50_SYMBOLS`). 그 외 심볼은 파이프라인 트리거 필요.
**과거 데이터 백필**: Overlay score는 실시간 수집 기반으로 **과거 소급 생성 불가**.
Wikipedia 페이지뷰 (`/wiki`)는 2015년부터 조회 가능.
```bash
# 파이프라인 수동 트리거 (신규 심볼 추가 시)
POST /api/v1/overlay/admin/trigger-pipeline
# 특정 심볼의 헬스 상태 확인
GET /api/v1/overlay/admin/health
```
---
## 백필 우선순위 권장 사항
| 우선순위 | 대상 | 이유 | 예상 소요 시간 |
|---|---|---|---|
| 🔴 높음 | FINRA 1년치 (2025년) | z-score 계산 윈도우(30일)가 너무 짧아 신호 품질 저하 | 20-40분 |
| 🟡 중간 | FINRA 2년치 (2024년) | 더 긴 추세 분석 가능 | 1-2시간 |
| 🟢 낮음 | Alpaca 데이터 | Yahoo Finance와 중복, API 키 필요 | 필요시 |
### FINRA 권장 백필 스크립트
```bash
# 1단계: 2025년 (가장 중요)
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2025-01-02&end_date=2025-12-31"
# 2단계: 2026년 공백 구간
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2026-01-02&end_date=2026-02-09"
# 3단계 (선택): 2024년
curl -X POST "http://localhost:18001/api/v1/finra/admin/ingest?start_date=2024-01-02&end_date=2024-12-31"
```
---
## 현재 DB 상태 확인 쿼리
```sql
-- 각 테이블 데이터 범위 확인
SELECT 'price_data' AS tbl, MIN(date)::date, MAX(date)::date, COUNT(DISTINCT date::date) AS days, COUNT(DISTINCT ticker) AS tickers FROM price_data
UNION ALL
SELECT 'finra_short_volume', MIN(date)::date, MAX(date)::date, COUNT(DISTINCT date::date), NULL FROM finra_short_volume
UNION ALL
SELECT 'sec_filings', MIN(filing_date)::date, MAX(filing_date)::date, COUNT(DISTINCT filing_date::date), COUNT(DISTINCT ticker) FROM sec_filings
UNION ALL
SELECT 'overlay_feature_records', MIN(as_of_ts)::date, MAX(as_of_ts)::date, COUNT(DISTINCT as_of_ts::date), COUNT(DISTINCT symbol) FROM overlay_feature_records
ORDER BY tbl;
```
Loading…
Cancel
Save