You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

32 KiB

Data Coverage & Backfill Guide

각 API 엔드포인트의 실제 DB 보유 데이터 범위과거 데이터 백필 방법을 정리한 문서입니다.

마지막 업데이트: 2026-04-26 DB 실측 기준


요약 테이블

엔드포인트 데이터 소스 DB 저장 현재 보유 범위 이론적 최대 범위 백필 필요
/price Yahoo Finance 2018-04-24 ~ 현재 (1682 티커) 20년+ 요청 기반 자동 누적
/alpaca/intraday (SIP, 과거) Alpaca SIP 요청 기반 자동 누적 2016년~어제 요청 기반 자동 누적
/alpaca/intraday/today (IEX, 당일) Alpaca IEX 당일만 오늘 장 중 해당 없음
/alpaca/snapshot Alpaca IEX 실시간만 없음 해당 없음
/finra/short-volume FINRA CDN 2018-08-01 ~ 현재 (1,829일, 22,722심볼) 2016년~ 백필 완료 (2020-04~10 갭 제외)
/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년~ 자동 수집됨
/insider/transactions SEC EDGAR Form 4 요청 기반 자동 누적 2004년~ 요청 기반 자동 누적
/insider/form4/{ticker} SEC EDGAR Form 4 최근 2년 (8개 quarter) 사전 bootstrap 2024년 Q3 ~ 현재, 676,858건 / 4,814 티커 bootstrap_form4_by_ticker.py 완료
/insider/form4/by-date/{date} SEC EDGAR Form 4 동상 동상 동상
/insider/form4/aggregate/{ticker} SEC EDGAR Form 4 동상 동상 동상
/ownership/13dg/{ticker} SEC EDGAR SC 13D/G 최근 2년 (8개 quarter) 사전 bootstrap 2024년 Q3 ~ 현재, 42,329행 bootstrap_13dg.py 완료
/ownership/13dg/active SEC EDGAR SC 13D/G 동상 동상 동상
/earnings/surprise yfinance-plus earnings_dates 요청 기반 자동 누적 ~25분기 (6년+) 요청 기반 자동 누적
/universe/screen SEC EDGAR + yfinance 월별 스냅샷 (사전 빌드 필요) admin 빌드 후 사용 가능 2010년~ ⚠️ 사전 빌드 필요
/company/{ticker} yfinance-plus + universe_ticker_registry (Redis 24h + DB 영구) 모든 yfinance 지원 티커 즉시 요청 기반 자동 누적
/company/bulk yfinance-plus + universe_ticker_registry (Redis 24h + DB 영구) 최대 100 티커/요청 즉시 요청 기반 자동 누적
/news/v2/headlines Alpaca News (Benzinga) + StockTwits + Finnhub (news_headline) ingest 시작 시점 이후만 (vendor 한계) Alpaca: ingest 시작 누적 / Finnhub: 12개월 / StockTwits: 적재 시작 이후 ⚠️ NEWS_INGEST_ENABLED=true opt-in 필요
/news/v2/session_aggregate* news_headline 즉시 SQL 집계 (raw 기반) headlines와 동일 동상 동상
/news/v2/coverage news_headline MIN/MAX/COUNT 운영 도구

엔드포인트별 상세


/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에서 실시간 페치 → 자동 저장

백필: 별도 작업 불필요. 처음 조회 시 자동으로 인제스트됨.

# 특정 티커 과거 데이터 미리 채우기 (선택 사항)
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 저장
GET /alpaca/intraday SIP 과거 분봉 (2016년~어제)
GET /alpaca/intraday/today IEX 당일 실시간 분봉
GET /alpaca/snapshot IEX 현재가 스냅샷
GET /alpaca/status API 키 유효성 확인

피드 차이:

  • SIP: 전체 미국 거래소 통합 데이터. 거래량 100% 정확. 과거 데이터(어제까지) 무료 접근.
  • IEX: IEX 거래소 단일. 실시간이지만 거래량은 실제의 2~5%. 당일 데이터 전용.

필수 조건: ALPACA_API_KEY, ALPACA_SECRET_KEY 환경 변수 설정 필요.

이론적 범위: 2016년~ (Alpaca 무료 플랜 기준)

# 과거 5분봉 조회 (SIP, DB 저장)
curl "http://localhost:18001/api/v1/alpaca/intraday?tickers=AAPL,MSFT&interval=5m&start_date=2025-01-01&end_date=2025-01-31"

# 당일 실시간 분봉 (IEX)
curl "http://localhost:18001/api/v1/alpaca/intraday/today?tickers=AAPL,MSFT&interval=5m"

# 현재가 스냅샷
curl "http://localhost:18001/api/v1/alpaca/snapshot?tickers=AAPL,MSFT,SPY"

/api/v1/finra — FINRA 공매도 (RegSHO)

현재 DB 보유: 2018-08-01 ~ 2026-05-29, 1,829 거래일, 22,722 심볼

  • FINRA CDN(무료, API 키 없음): cdn.finra.org/equity/regsho/daily/CNMSshvol{YYYYMMDD}.txt
  • 롤링 ~7년 보유 (2018-08 이전 403)
  • 2020-04-01 ~ 2020-10-31 결손 (~138 평일 — COVID 갭): backfill_finra_2020_gap.py로 메울 수 있음

PIT(Point-in-Time) 유니버스 멤버십

finra_short_volume에 등장한 22,722 심볼 중 16,143개는 현 활성 유니버스(9,635)에 없는 상폐/합병 과거 종목. DB의 pit_universe_membership 뷰로 노출.

-- 특정 날짜에 실제 거래되던 종목 (PIT 유니버스, 생존편향 0)
SELECT DISTINCT symbol FROM pit_universe_membership WHERE d = '2023-03-09';
-- SIVB, SBNY 등 그날 마지막으로 거래된 종목 포함됨

-- 공매도비율 횡단면 (상폐 종목 포함)
SELECT p.d, p.symbol, p.short_ratio, a.close
FROM pit_universe_membership p
LEFT JOIN alpaca_price_data a
  ON a.ticker = p.symbol AND a.date::date = p.d AND a.interval = '1d'
WHERE p.d = '2023-03-09' ORDER BY p.short_ratio;

⚠️ 한계:

  • 티커 재활용: BBBY(2023-05 파산 → 2년 공백 → 2025-08 다른 엔티티)처럼 동일 티커가 재사용될 수 있음. 심볼 기준 PIT에서 경계 날짜 부근 ±2주 윈도우 제외 권장.
  • NMS 슈퍼셋: ETF, ADR, 워런트(/U, /WS), 우선주 포함. 분석단에서 필터.
  • 엄밀한 티커 재활용 해결 = CUSIP/PERMNO 매핑 (유료 데이터, 현재 범위 외).

API 조회 파라미터

  • days: 최근 N일 (기본 30, 최대 3650 ≈ 10년)
  • limit: 반환 최대 건수 (기본 100, 최대 10000)
  • 주의: limit=100 기본값은 "100 거래일"이 아니라 "100행" 제한. 전체 히스토리 조회 시 반드시 지정:
# SIVB 전체 히스토리 (상폐 전까지)
curl "http://localhost:18001/api/v1/finra/short-volume/SIVB?days=3650&limit=10000"

# 정규화된 일봉 공매도비율 (시장 통합)
curl "http://localhost:18001/api/v1/finra/short-ratio/AAPL?days=3650"

2020 갭 메우기

docker exec stock_oracle_api python scripts/backfill_finra_2020_gap.py
# 예상 소요: ~10-20분, idempotent (이미 있는 날짜 자동 스킵)

FINRA CDN은 API 키 없이 사용 가능. 1년치 백필 시 ~3000 HTTP 요청 + DB write, 20-40분 소요.


/api/v1/etf — ETF 보유 종목 (SEC EDGAR)

현재 DB 보유: 요청된 ETF 스냅샷만 (요청 기반 자동 누적)

이론적 범위: 2019년~ (NPORT-P 도입 이후). 일부 ETF는 더 이전 N-Q 파일링 존재.

조회 파라미터:

  • as_of_date: 기준 날짜 (YYYY-MM-DD). 가장 가까운 파일링 자동 선택.
  • 생략 시: 가장 최신 파일링 반환.

동작 방식: 첫 조회 시 SEC EDGAR에서 자동 페치 → 스냅샷 DB 저장. 재조회 시 캐시.

# 특정 날짜 기준 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} 최초 호출 시 자동 인덱싱됨.

# 특정 티커 전체 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년부터 조회 가능.

# 파이프라인 수동 트리거 (신규 심볼 추가 시)
POST /api/v1/overlay/admin/trigger-pipeline

# 특정 심볼의 헬스 상태 확인
GET /api/v1/overlay/admin/health

/api/v1/insider — 내부자 거래 (SEC Form 4)

현재 DB 보유:

  • 기존 /transactions, /summary: 요청 기반 자동 누적 (첫 조회 시 자동 인덱싱)
  • 신규 /form4/*: 최근 2년 사전 bootstrap 완료 — 676,858건 / 4,814 티커 / 2024 Q3 ~ 2026-04-23

자동 갱신: 매 영업일 09:00 ET — 직전 영업일 daily full-index → Form 4 upsert (scheduler)

⚠️ PIT 주의: 신규 /form4/* 엔드포인트는 as_of 파라미터 필수. 누락 시 422.


기존 엔드포인트 (Lazy on-demand)

# 최근 90일 내부자 거래 조회 (자동 인덱싱)
curl "http://localhost:18001/api/v1/insider/transactions/NVDA?days=90"

# 내부자 매매 요약 (순매수/매도 금액)
curl "http://localhost:18001/api/v1/insider/summary/AAPL?period=90d"

지원 거래 유형: P-Purchase, S-Sale, A-Award, D-Return, F-TaxWithholding, G-Gift, M-OptionExercise


신규 PIT-safe 엔드포인트 (2026-04-23)

GET /insider/form4/{ticker} — 티커별 Form 4 거래 목록

파라미터 필수 설명
as_of 기준 날짜 (YYYY-MM-DD). filing_date <= as_of 필터.
start - 시작 날짜 (filing_date 기준)
end - 종료 날짜 (filing_date 기준)
buy_only - true → 매수(P,A) 거래만
csuite_only - true → CEO·CFO·COO·CTO 등 C-suite만

반환 필드: symbol, filing_date, transaction_date, owner_cik, owner_name, owner_relationship, is_officer, is_director, is_ten_percent_owner, is_ceo, is_cfo, is_c_suite, shares, price, total_value, shares_owned_following, purchase_pct_of_holding, transaction_code, accession_number

# TSLA CEO·CFO 매수 거래 조회
curl "http://localhost:18001/api/v1/insider/form4/TSLA?as_of=2026-04-20&start=2026-01-01&csuite_only=true&buy_only=true"

# NVDA 최근 30일 내부자 거래 전체
curl "http://localhost:18001/api/v1/insider/form4/NVDA?as_of=2026-04-20&start=2026-03-20"

GET /insider/form4/by-date/{date} — 특정 공시일 전체 거래 (cross-ticker)

파라미터 필수 설명
buy_only - 매수 거래만 반환
# 2026-04-17 공시 전체 매수 거래
curl "http://localhost:18001/api/v1/insider/form4/by-date/2026-04-17?buy_only=true"

GET /insider/form4/aggregate/{ticker} — 집계 요약 (PIT-safe)

파라미터 필수 설명
as_of 기준 날짜
window_days - 집계 윈도우 (기본 30일). filing_date ∈ (as_of - window_days, as_of]

반환 필드: buy_count, buy_dollar_total, cluster_size, csuite_count, avg_pct_of_holding, recency_days

# AAPL 최근 30일 내부자 매수 집계
curl "http://localhost:18001/api/v1/insider/form4/aggregate/AAPL?as_of=2026-04-20&window_days=30"

C-suite 판별 기준

officer_title 에 대해 case-insensitive 정규식 적용:

  • is_ceo: CEO, Chief Executive Officer
  • is_cfo: CFO, Chief Financial Officer, Principal Financial Officer
  • is_c_suite: 위 둘 + COO, CTO, CIO, CLO, CMO, President, Chairman/person/woman, Chief * Officer


/api/v1/ownership — Activist Ownership (SEC SC 13D/G) (신규, 2026-04-23)

현재 DB 보유: 42,329행, 최근 2년 (2024 Q3 ~ 2026-04-23) 사전 bootstrap 완료

자동 갱신:

  • 매 영업일 09:00 ET — daily full-index → SC 13D/G index-only upsert
  • 30분 주기 background enrich — parse_status='index_only' 200행씩 cover-page XML/HTML 파싱 → ownership_pct, shares_owned 보강

parse_status 의미:

  • index_only: EDGAR 인덱스에서 가져온 기본 메타만 있음. ownership_pct=NULL
  • parsed: cover-page XML/HTML 파싱 완료. ownership_pct 채워짐
  • parse_failed: 파싱 시도했으나 문서 구조 불명확

⚠️ PIT 주의: as_of 파라미터 필수. 누락 시 422. 모든 날짜 필터는 filing_date 기준.


GET /ownership/13dg/{ticker} — 티커별 activist 이벤트 목록

파라미터 필수 설명
as_of 기준 날짜. filing_date <= as_of 필터.
start - 시작 날짜 (filing_date 기준)
end - 종료 날짜 (filing_date 기준)

반환 필드: symbol, filing_date, filer_name, filer_cik, form_type, ownership_pct, shares_owned, is_amendment, change_pct, accession_number, parse_status

form_type 값: SC 13D, SC 13G, SC 13D/A, SC 13G/A (또는 SCHEDULE 13D/G 등 변형 포함)

# AAPL activist filing 전체 (as_of 기준 이전)
curl "http://localhost:18001/api/v1/ownership/13dg/AAPL?as_of=2026-04-23"

# RLGT 2025년 이후 activist 이벤트
curl "http://localhost:18001/api/v1/ownership/13dg/RLGT?as_of=2026-04-23&start=2025-01-01"

GET /ownership/13dg/active — 현재 활성 activist 포지션 목록

파라미터 필수 설명
as_of 기준 날짜
min_ownership_pct - 최소 지분율 (기본 5.0)

쿼리 로직: (filer_cik, issuer_cik) 쌍별 최신 filing (filing_date DESC) 한 행씩, ownership_pct >= min_ownership_pct 필터.

# 현재 5% 이상 activist 포지션 전체 (파싱된 행만)
curl "http://localhost:18001/api/v1/ownership/13dg/active?as_of=2026-04-23&min_ownership_pct=5.0"

# 10% 이상 대형 activist
curl "http://localhost:18001/api/v1/ownership/13dg/active?as_of=2026-04-23&min_ownership_pct=10.0"

Bootstrap (1회성, 이미 완료)

# 최근 8개 quarter SC 13D/G index-only 수집
docker exec stock_oracle_api python scripts/bootstrap_13dg.py --quarters 8

# Form 4 bootstrap (최근 2년)
docker exec stock_oracle_api python scripts/bootstrap_form4_by_ticker.py

/api/v1/earnings — 어닝 서프라이즈 (yfinance-plus)

현재 DB 보유: 요청 기반 자동 누적 (첫 조회 시 자동 인덱싱)

이론적 범위: 약 25분기 (6년+). yfinance earnings_dates 데이터 기준.

조회 파라미터:

  • GET /earnings/surprise/{symbol}?limit=20: 분기별 EPS surprise 이력

반환 필드: reported_eps, estimated_eps, surprise, surprise_percentage, streak

  • surprise = reported_eps estimated_eps
  • surprise_percentage = (surprise / estimated) × 100
  • streak: 연속 beat(+) 또는 miss() 횟수

동작 방식: 첫 조회 시 yfinance earnings_dates 자동 인제스트 → DB 저장. 이후 1시간 Redis 캐시.

# 어닝 서프라이즈 이력 조회 (자동 인덱싱)
curl "http://localhost:18001/api/v1/earnings/surprise/AAPL?limit=20"

/api/v1/universe — 과거 주식 유니버스 (백테스팅)

현재 DB 보유: admin 엔드포인트로 사전 빌드 필요 (초기에는 빈 상태)

이론적 범위: 2010년~ (SEC EDGAR 데이터 + yfinance 가격 데이터 가용 범위)

데이터 소스: SEC EDGAR companyfacts (shares_outstanding) × yfinance 월별 종가 → 월별 시총 계산

Survivorship bias 주의: 현재 상장된 종목만 포함. 상폐 종목 미포함. 정확도: 시총 오차 ±10~20% (buyback 반영 지연, SEC 분기별 업데이트 때문).

워크플로우

1단계: 유니버스 등록 (1~5분)
POST /universe/admin/discover?market_cap_min=100000000
→ yfinance screener로 ~3000~5000 종목 발견
→ universe_ticker_registry 테이블에 저장

2단계: 스냅샷 빌드 (30~60분, 백그라운드)
POST /universe/admin/build-snapshots
→ SEC EDGAR shares_outstanding × yfinance 월별 종가 = 월별 시총
→ universe_snapshot 테이블에 ~480K 행 저장 (4000 × 120개월)

3단계: 과거 스크리닝
GET /universe/screen?date=2018-01-01&market_cap_min=2e9&market_cap_max=20e9
→ 2018년 초 기준 시총 $2B~$20B 종목 목록 반환

엔드포인트 상세

GET /universe/screen — 과거 시점 기준 종목 스크리닝

파라미터 필수 설명
date 기준 날짜 YYYY-MM-DD (월초로 자동 반올림)
market_cap_min - 최소 시총 (USD), 예: 2e9 = $2B
market_cap_max - 최대 시총 (USD), 예: 20e9 = $20B
sector - 섹터 필터 (예: Technology, Healthcare)
exchange - 거래소 필터 (NYSE, NASDAQ, AMEX)
page / page_size - 페이지네이션 (기본 100, 최대 500)
sort_by - 정렬 기준 (market_cap 또는 ticker)
# 2018년 초 시총 $2B~$20B 종목 (Small/Mid Cap)
curl "http://localhost:18001/api/v1/universe/screen?date=2018-01-01&market_cap_min=2000000000&market_cap_max=20000000000"

# 2020년 기준 Technology 섹터 Large Cap ($10B+)
curl "http://localhost:18001/api/v1/universe/screen?date=2020-01-01&market_cap_min=10000000000&sector=Technology"

# 2023년 기준 Top 100 시총 순위
curl "http://localhost:18001/api/v1/universe/screen?date=2023-01-01&sort_by=market_cap&sort_ascending=false&page_size=100"

GET /universe/registry — 등록된 종목 목록 조회

# 등록된 전체 종목 조회
curl "http://localhost:18001/api/v1/universe/registry?page_size=200"

# NASDAQ 기술주 필터
curl "http://localhost:18001/api/v1/universe/registry?exchange=NASDAQ&sector=Technology"

POST /universe/admin/discover — 종목 발견 및 등록

# $100M 이상 ~3000~5000 종목 등록 (1~5분 소요)
curl -X POST "http://localhost:18001/api/v1/universe/admin/discover?market_cap_min=100000000"

# 소규모 테스트 ($1T 이상, ~50 종목)
curl -X POST "http://localhost:18001/api/v1/universe/admin/discover?market_cap_min=1000000000000"

POST /universe/admin/build-snapshots — 월별 시총 스냅샷 빌드

# 소규모 테스트 (3 종목 × 2년, 즉시 반환)
curl -X POST "http://localhost:18001/api/v1/universe/admin/build-snapshots" \
  -H "Content-Type: application/json" \
  -d '{"tickers":["AAPL","MSFT","NVDA"],"start_date":"2023-01-01","end_date":"2024-12-01"}'

# 전체 유니버스 × 10년 빌드 (백그라운드, 30~60분)
curl -X POST "http://localhost:18001/api/v1/universe/admin/build-snapshots" \
  -H "Content-Type: application/json" \
  -d '{"start_date":"2015-01-01","end_date":"2025-12-01","force_rebuild":false}'

tickers 생략 시 registry 전체 대상. 20개 이하면 동기 실행(즉시 결과), 21개 이상이면 백그라운드 실행.

초기 세팅 권장 순서

# 1. 유니버스 등록 ($100M+ → ~4000 종목)
curl -X POST "http://localhost:18001/api/v1/universe/admin/discover?market_cap_min=100000000"

# 2. 전체 스냅샷 빌드 (백그라운드 시작)
curl -X POST "http://localhost:18001/api/v1/universe/admin/build-snapshots" \
  -H "Content-Type: application/json" \
  -d '{"start_date":"2015-01-01","end_date":"2025-12-01"}'

# 3. 빌드 완료 후 스크리닝 테스트
curl "http://localhost:18001/api/v1/universe/screen?date=2020-01-01&market_cap_min=10000000000"

/api/v1/company — 종목 메타데이터 (신규, 2026-04-20)

현재 DB 보유: 조회 시 자동 저장. 두 번째 조회부터 DB 히트 (Redis 24h → DB 영구).

데이터 소스: yfinance-plus .infouniverse_ticker_registry + companies 테이블 UPSERT

반환 필드: ticker, name, cik, exchange, sector, industry, country, market_cap, business_description

엔드포인트 메서드 설명
/company/{ticker} GET 단일 종목 메타데이터. 미지원 티커 → 404
/company/bulk POST 최대 100개 일괄 조회. 부분 실패 허용 (per-ticker error 필드)

캐시 전략:

  • Redis 24h TTL (key: company:meta:{TICKER})
  • yfinance 실패 시 15분 TTL (재시도 빈도 조절)
  • DB에 영구 저장 (sector 있는 경우 fast-path: DB만 조회, yfinance 미호출)

조회 예시:

# 단일 종목
curl "http://localhost:18001/api/v1/company/AU"
# → {"ticker":"AU","sector":"Basic Materials","industry":"Gold","exchange":"NYSE","country":"United States",...}

# 유효하지 않은 티커 → 404
curl "http://localhost:18001/api/v1/company/ZZZZZZ"
# → {"detail":"Unknown ticker: ZZZZZZ"}

# 최대 100개 일괄 조회
curl -X POST "http://localhost:18001/api/v1/company/bulk" \
  -H "Content-Type: application/json" \
  -d '{"tickers":["AU","USAS","CPRX","HE","ACHR"]}'
# → {"results":[...],"total":5,"success_count":5,"error_count":0}

성능 특성:

  • 콜드 캐시 (첫 조회): yfinance 호출 ~2-5초 / 티커. 동시 최대 5개 (semaphore)
  • 웜 캐시 (재조회): Redis <10ms / DB <50ms
  • bulk 100개 콜드 캐시: ~30-50초 (semaphore=5로 직렬화)

sector 대량 사전 보강 (선택):

# universe_ticker_registry의 NULL sector 전체 보강 (~30분, 9376 티커)
python scripts/backfill_registry_sector.py --batch 50
# Dry-run
python scripts/backfill_registry_sector.py --dry-run

/financial/data/{ticker} 변경사항 (2026-04-20):

  • company 블록에 exchange, country, market_cap 필드 추가
  • placeholder sector(Technology/Software/XXX Corporation) 감지 시 자동 재보강
  • financials/price 조회 실패 시에도 company 블록은 200 OK로 유지 (이전: 500 가능)

/api/v1/news/v2 — 멀티소스 헤드라인 + 세션 집계 (신규, 2026-04-26)

현재 DB 보유: news_headline 테이블. ingest 시작 후 누적. 빈 DB 상태에서도 엔드포인트는 200 + 빈 결과 반환.

데이터 소스 (모두 NEWS_INGEST_ENABLED=true 시 활성화):

Source History Rate limit Sentiment
alpaca_benzinga ~30일 vendor cap → ingest 시작 후 누적 200 req/min 없음 (free tier)
stocktwits rolling 200 req/hr/IP Bullish/Bearish → ±1
finnhub ~12개월 vendor cap 60 calls/min free 없음

카테고리 정규화: vendor 라벨 → 22-term 통합 vocab (analyst_rating_*, earnings_release, m_and_a, fda_approval/rejection, buyback, litigation, ... 등). 원본은 vendor_categories에 보존.

세션 윈도우 (NYSE / pandas_market_calendars XNYS 휴장일·short day 처리):

  • premarket = 전일 close → 당일 09:30 ET
  • intraday = 09:30 → 16:00 ET
  • post = 16:00 → 다음 거래일 04:00 ET (다음 premarket과 중복 없음)
  • full_session = 전일 close → 다음 거래일 04:00 ET

PIT 안전성: 집계 시 ingested_at <= window_end_utc 필터 적용 → backtest가 lookahead 데이터를 못 봄.

엔드포인트 메서드 설명
/news/v2/headlines GET raw 헤드라인 (symbols, start, end, sources, limit, cursor 필터)
/news/v2/session_aggregate GET 단일 ticker × session × window 집계 (Redis 캐시)
/news/v2/session_aggregate/batch POST 다수 ticker 일괄 (V49 핫패스, 캐싱 없음 — 클라이언트 disk-cache 가정)
/news/v2/coverage GET source × symbol 적재 깊이 probe

Ingest 활성화 절차:

# 1. .env에 키 설정 (Alpaca는 기존 키 재사용, Finnhub은 신규)
echo "FINNHUB_API_KEY=<your-key>" >> .env
echo "NEWS_INGEST_ENABLED=true" >> .env

# 2. 컨테이너 재시작
docker restart stock_oracle_api

# 3. 로그에서 시작 메시지 확인
docker logs stock_oracle_api 2>&1 | grep "\[News\]"
# → "[News] ingest scheduler started — sources: ['alpaca_benzinga', 'finnhub', 'stocktwits']"

# 4. 5분 후 첫 Alpaca News poll 결과 확인
curl "http://localhost:18001/api/v1/news/v2/coverage?source=alpaca_benzinga"

Fail-fast: NEWS_INGEST_ENABLED=true인데 ALPACA_API_KEY/SECRET & FINNHUB_API_KEY 모두 없으면 scheduler 시작 거부 (StockTwits만으로는 신호 부족).

Finnhub 12개월 백필 (수동 1회):

docker exec stock_oracle_api python scripts/news_backfill.py \
  --source finnhub \
  --tickers AAPL,MSFT,NVDA,TSLA,GOOGL \
  --start 2025-04-26 --end 2026-04-26 \
  --chunk monthly
# → 100 ticker × 12 month ≈ 1,200 calls @ 60 cpm ≈ 20분

StockTwits 동적 universe (자동, 매 평일 09:00 ET):

  • (최근 14일 V49 universe 합집합) (당일 09:00 ET premarket gap > threshold movers)
  • 기본 cap 300 ticker, 5분 polling, 200 req/hr 안정
  • Config: STOCKTWITS_UNIVERSE_LOOKBACK_DAYS (14), STOCKTWITS_PREMARKET_GAP_THRESHOLD (0.02), STOCKTWITS_UNIVERSE_MAX_SIZE (300)
  • Redis key: news_v2:stocktwits:universe

제외:

  • WebSocket 실시간 push (P2)
  • vendor 통합 sentiment NLP (현재는 vendor passthrough)
  • GDELT raw feed 노출 (기존 attention 서브시스템 활용)
  • Reddit/Twitter

백필 우선순위 권장 사항

우선순위 대상 이유 예상 소요 시간
🔴 높음 상폐 가격 백필 (PIT 생존편향-0) 숏볼륨 신호 검정 시 수익 측 생존편향 제거 필수 1-3시간
🔴 높음 FINRA 2020 갭 (2020-04~10) COVID 약세장/회복 레짐 없으면 멀티레짐 검정 불가 10-20분
🔴 높음 Universe 스냅샷 빌드 백테스팅 유니버스 기능 사용 전 필수 1회 실행 30-60분 (4000 종목 × 10년)
🟢 낮음 추가 FINRA 구간 이미 2018-08 ~ 현재 수집 완료

생존편향-0 데이터셋 구축 (권장 실행 순서)

# Step 1: FINRA 2020 갭 메우기 (10-20분, idempotent)
docker exec stock_oracle_api python scripts/backfill_finra_2020_gap.py

# Step 2: 상폐 가격 백필 — PIT 생존편향-0 핵심 (1-3시간, idempotent)
# FINRA 22,722 심볼 전체에 대해 Alpaca SIP 일봉 수집 (무료 플랜 포함)
# adjustment='all' (분할+배당 조정), 2018-08-01부터
docker exec stock_oracle_api python scripts/backfill_alpaca_daily_pit.py

# Step 3: 리서치 레이어용 parquet 익스포트 (pandas + pyarrow 필요)
docker exec stock_oracle_api python scripts/export_pit_panel.py
# 출력: ./data/pit_panel.parquet

PIT 뷰 (DB 직접 쿼리 시)

-- 날짜별 PIT 유니버스 (상폐 종목 포함)
SELECT DISTINCT symbol FROM pit_universe_membership WHERE d = '2023-03-09';

-- 공매도비율 × 가격 패널 (생존편향-0, backfill_alpaca_daily_pit.py 실행 후)
SELECT p.d, p.symbol, p.short_ratio, a.close
FROM pit_universe_membership p
LEFT JOIN alpaca_price_data a
  ON a.ticker = p.symbol AND a.date::date = p.d AND a.interval = '1d'
WHERE p.d BETWEEN '2022-01-01' AND '2023-12-31'
ORDER BY p.d, p.short_ratio;

현재 DB 상태 확인 쿼리

-- 각 테이블 데이터 범위 확인
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
UNION ALL
SELECT 'insider_transactions', MIN(transaction_date)::date, MAX(transaction_date)::date, COUNT(DISTINCT transaction_date::date), COUNT(DISTINCT ticker) FROM insider_transactions
UNION ALL
SELECT 'earnings_surprise', MIN(earnings_date)::date, MAX(earnings_date)::date, COUNT(DISTINCT earnings_date::date), COUNT(DISTINCT ticker) FROM earnings_surprise
ORDER BY tbl;

-- Form 4 PIT 데이터 상태
SELECT MIN(filing_date)::date, MAX(filing_date)::date, COUNT(*) AS txns, COUNT(DISTINCT ticker) AS tickers
FROM insider_transactions;

-- SC 13D/G activist 데이터 상태
SELECT parse_status, COUNT(*),
       COUNT(*) FILTER (WHERE ownership_pct IS NOT NULL) AS has_pct,
       MIN(filing_date)::date, MAX(filing_date)::date
FROM activist_ownership_events GROUP BY parse_status ORDER BY parse_status;

-- Universe 스냅샷 상태 확인
SELECT
  COUNT(DISTINCT ticker) AS tickers,
  COUNT(*) AS snapshots,
  MIN(snapshot_date)::date AS earliest,
  MAX(snapshot_date)::date AS latest
FROM universe_snapshot;

-- Universe 등록 종목 수
SELECT COUNT(*) AS registered, COUNT(*) FILTER (WHERE is_active) AS active
FROM universe_ticker_registry;