From fd4816dbbd20d96f39df32663ab5d0c569e1eb5d Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Mon, 23 Mar 2026 13:08:49 -0700 Subject: [PATCH] =?UTF-8?q?docs:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B3=B4?= =?UTF-8?q?=EC=9C=A0=20=EB=B2=94=EC=9C=84=20=EB=B0=8F=20=EB=B0=B1=ED=95=84?= =?UTF-8?q?=20=EA=B0=80=EC=9D=B4=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- app/api/v1/endpoints/alpaca.py | 23 +-- app/api/v1/endpoints/filings.py | 2 + app/api/v1/endpoints/finra.py | 22 ++- app/api/v1/endpoints/stocks.py | 30 +++- docs/DATA_COVERAGE.md | 243 ++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+), 15 deletions(-) create mode 100644 docs/DATA_COVERAGE.md diff --git a/app/api/v1/endpoints/alpaca.py b/app/api/v1/endpoints/alpaca.py index ea0a7e7..c8eb409 100644 --- a/app/api/v1/endpoints/alpaca.py +++ b/app/api/v1/endpoints/alpaca.py @@ -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( diff --git a/app/api/v1/endpoints/filings.py b/app/api/v1/endpoints/filings.py index 2379ef9..f9a5187 100644 --- a/app/api/v1/endpoints/filings.py +++ b/app/api/v1/endpoints/filings.py @@ -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`" ), ) diff --git a/app/api/v1/endpoints/finra.py b/app/api/v1/endpoints/finra.py index dfc255c..f340d20 100644 --- a/app/api/v1/endpoints/finra.py +++ b/app/api/v1/endpoints/finra.py @@ -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)"), diff --git a/app/api/v1/endpoints/stocks.py b/app/api/v1/endpoints/stocks.py index cbf2459..41cd562 100644 --- a/app/api/v1/endpoints/stocks.py +++ b/app/api/v1/endpoints/stocks.py @@ -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, diff --git a/docs/DATA_COVERAGE.md b/docs/DATA_COVERAGE.md new file mode 100644 index 0000000..f00c23f --- /dev/null +++ b/docs/DATA_COVERAGE.md @@ -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; +```