52 Commits (20816118848ad8f54bb43f989dd3045c8cb83c84)

Author SHA1 Message Date
I Luk Kim 2081611884 fix: multi-ticker daily bars — DB storage + DB-first fetch logic
- AlpacaPriceService.get_or_fetch_multi_bars(): checks DB max_date per
  ticker, only fetches missing ranges from Alpaca, upserts with chunking
  (3000 rows/chunk, asyncpg 32767-param limit) then reads back from DB
- GET /price/data endpoint: now uses service + Depends(get_db); subsequent
  calls for same date range skip Alpaca entirely
- force_refresh=true bypasses DB check and re-fetches all from Alpaca

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim fbb42edfc2 feat: multi-ticker bulk bars endpoints + BF-B ticker normalization
- GET /api/v1/price/data?tickers=AAPL,MSFT,BF-B&start_date=...&end_date=...
  → multi-ticker daily OHLCV via Alpaca (ORB engine daily bars interface)
- GET /api/v1/alpaca/intraday?tickers=...&interval=5min&start_date=...&end_date=...
  → multi-ticker intraday OHLCV via Alpaca (ORB engine ORB-window interface)
- AlpacaMultiBarsResponse schema: {source, interval, count, bars: {sym → [bar]}}
- normalize_ticker(): BF-B→BF.B, BRK-B→BRK.B applied in get_bars/get_multi_bars/get_snapshot(s)
- get_multi_bars: transparent batching (200 symbols/request) + INTERVAL_MAP aliases (5min, 15min, 60min, …)
- Response re-keys Alpaca normalized symbols back to original input names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 846280c3f6 fix: alpaca snapshot 멀티 티커 제한 100 → 1000
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim a9a158d804 feat: Alpaca 실시간 snapshot 엔드포인트 추가
- GET /alpaca/snapshot/{ticker} — 단일 티커 실시간 가격/bid-ask/OHLCV/등락률
- GET /alpaca/snapshot?tickers=A,B — 최대 100개 멀티 티커 일괄 조회
- AlpacaClient.get_snapshot / get_snapshots 메서드 추가
- AlpacaSnapshotResponse / AlpacaMultiSnapshotResponse 스키마 추가
- docs/PYTHON_CLIENT.md 사용 예시 업데이트
- 캐시 없음 — 매 요청마다 Alpaca API 직접 호출

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 33b1749721 fix: GET /price/data/{ticker} — start/end alias 지원 + 단일 날짜 조회 허용
- ?start=&end= 쿼리 파라미터를 start_date/end_date의 alias로 수용
- end_date <= start_date 검증을 end_date < start_date 로 완화하여 당일(start==end) OHLCV 조회 가능

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim a496789fbe feat: 8-K 재파싱 지원 — force_reparse + 단일 accession 재처리 endpoint
- POST /filings/events/parse/{accession_number}: 특정 filing 강제 재파싱
  (parsed_status 무관하게 pending으로 리셋 후 즉시 파싱)
- POST /filings/events/parse/bulk에 force_reparse: bool 추가
  (true이면 succeeded/failed도 pending으로 리셋 후 재처리)
- BulkParseRequest에 force_reparse 필드 추가

사용법:
  curl -X POST /api/v1/filings/events/parse/0001193125-26-144028
  curl -X POST /api/v1/filings/events/parse/bulk -d '{"tickers":["AVGO"],"force_reparse":true}'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 25603c390c feat: 8-K filing parser — Item 추출 + event 생성
AVGO 8-K (accession 0001193125-26-144028, Item 8.01, Google TPU 공급계약)이
DB에 수집은 되었으나 파싱/event 생성이 불가했던 문제 해결.

구현:
- sec_8k_parser.py: 8-K primary document HTML 파싱
  - extract_items(): regex 기반 Item 헤더 추출, 목차 중복 제거 (last-wins)
  - _strip_ixbrl_viewer(): documents_json의 /ix?doc=... URL → 직접 URL 변환
  - _find_primary_doc_url(): primary_document_url 우선 사용 (iXBRL viewer 회피)
  - Item 8.01 단독 filing: exhibit(9.01) 없이 본문에서 직접 content 추출
  - Item 9.01 skip, 나머지는 ITEM_EVENT_MAP으로 event_type 분류
  - Exhibit enrichment: 2.02/7.01/8.01 + EX-99.1 있을 때 exhibit content 우선
- sec_filing_events 테이블 신설 (UniqueConstraint: accession_number + item_number)
- sec_filings 테이블에 parsed_status / items_json 컬럼 추가
- index_filings() 완료 후 신규 8-K auto-parse 트리거
- GET /filings/events/{ticker}: lazy parse + 조회
- POST /filings/events/parse/bulk: backfill용 일괄 파싱
- FilingSummary에 parsed_status / items 필드 포함
- alembic migration: g8a9b0c1d2e3
- 테스트 22개 추가 (extract_items, strip_ixbrl, find_primary_doc, parse_filing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 3eb736445b feat: PIT dividend calendar + earnings surprise calendar endpoint
dividend calendar:
- DividendCalendar 모델 (PIT revisioned: as_of_date 컬럼으로 lookahead bias 방지)
- FINRA 5yr 데이터 backfill + yfinance 갱신 지원
- GET /dividends/calendar/{ticker}, POST /dividends/calendar/bulk
- alembic migration: f7a8b9c0d1e2

earnings:
- GET /earnings/calendar/{ticker}: ex-dividend 방식 earnings calendar 제공
- EarningsSurprise 모델에 fiscal_date 인덱스 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 2438c511c7 fix: SEC EDGAR stale 8-K index auto-refresh + HTTP cache bypass
- fetch_json()에 skip_cache 파라미터 추가 (in-memory/disk 캐시 bypass)
- index_filings()에 force_refresh → skip_cache 자동 연동
- search_filings()에 staleness check (MAX indexed_at vs SEC_DATA_REFRESH_HOURS)
  → 24h 이상 stale한 ticker 자동 re-index (per-ticker asyncio.Lock으로 thundering herd 방지)
- search_filings_bulk()도 동일 staleness 처리
- 테스트 7개 추가 (skip_cache 동작, force_refresh 연동)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 7a003a5360 fix: screener yf.screen() rate limit/401 retry 처리 추가
429/rate limit 및 401/unauthorized 발생 시 최대 3회 재시도 (delay 3s, 6s).
모든 retry 소진 후 RuntimeError로 변환하여 503 응답.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim fb692fe592 feat: yfinance rate limit 처리 강화
- yfinance_plus: 세션 풀 다양화 (chrome/edge/safari/firefox 핑거프린트 6개)
  + 모듈 싱글톤으로 process-wide rate limiter 공유
  + Adaptive throttling (rate limit 감지 시 0.3s→최대 5s 자동 증가)
  + EnhancedTicker별 전용 세션으로 스레드 race condition 해결
- price.py: rate limit 에러 감지 시 HTTP 500 → HTTP 429 + Retry-After: 30
- test_rate_limit.py: rate limit 발생 조건 측정 스크립트 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim c4565159b1 fix: universe service - NASDAQ case bug + OOM prevention
Two fixes to universe snapshot build:

1. SEC EDGAR exchange case mismatch: company_tickers_exchange.json uses
   "Nasdaq" (mixed case) but filter expected "NASDAQ". All NASDAQ-listed
   stocks (AAPL, MSFT, GOOGL, etc.) were silently excluded from registry.
   Fixed with case-insensitive _US_EXCHANGE_MAP lookup + canonical normalization.

2. OOM during large builds: SEC EDGAR companyfacts JSONs accumulate in
   _json_cache without eviction, causing OOM after ~1500-1800 tickers.
   Fixed by clearing _json_cache + _text_cache + gc.collect() every 20
   batches. Memory remains stable throughout full 9,376-ticker build.

Result: 529,328 snapshot rows, 5,570 tickers (NASDAQ:2515, NYSE:2084, OTC:971)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 24736954bb fix: yfinance threads=False + 배치 30개로 OS 스레드 폭발 방지
threads=True일 때 배치당 50개 OS 스레드 생성 → 시스템 자원 고갈 → API 먹통.
threads=False로 순차 다운로드, 배치 30개로 축소.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim d4008924d9 fix: isolated thread에 독립적인 DB 엔진 생성
asyncpg 커넥션은 event loop에 바인딩됨. API의 AsyncSessionLocal을
다른 loop에서 사용하면 'Future attached to a different loop' 오류 발생.
→ build thread 내에서 전용 engine + session factory 생성.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 1b037c9256 fix: full force_rebuild 시 universe_snapshot 전체 삭제
tickers=None (전체 빌드)일 때 registry에서 제거된 stale 티커 데이터도
정리되도록 DELETE FROM universe_snapshot 실행.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim e51e045169 fix: universe build을 isolated thread로 분리하여 API 먹통 방지
BackgroundTask가 FastAPI event loop을 공유해서 deadlock 발생 →
별도 thread에서 새 asyncio event loop으로 실행하도록 변경.
- API event loop 완전 분리
- DB 커넥션 풀 독립적 사용 (per-batch factory session)
- 빌드 중 API 정상 응답 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 0e048f82ef fix: universe build event loop 차단 방지
- _PRICE_BATCH 100 → 50 (배치당 부하 감소)
- 배치 완료 후 asyncio.sleep(1) 추가 (event loop yield)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim b54ac6d5b7 fix: universe 데이터 품질 개선 — sanity check + preferred stock 필터링
- SEC EDGAR fallback에서 "-" 포함 티커 제외 (preferred stock, BAC-PL 등)
- W/R/Z suffix 티커 제외 (warrant, right, special)
- build_snapshots: close > $1M 또는 shares < 100,000 또는 market_cap > $5T 제외
- screen_historical: market_cap > $5T 쿼리 레벨 필터 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 5ab1663977 feat: universe discover에 SEC EDGAR fallback 추가
yf.screen() 401 에러 시 SEC EDGAR company_tickers_exchange.json으로 자동 전환.
- 5,848개 NYSE/NASDAQ/AMEX/ARCA 종목 등록 가능
- response에 source 필드 추가 (yfinance / sec_edgar)
- market_cap_min 필터는 yfinance 사용 시에만 적용 (SEC Edgar fallback 시 미적용, 스크리닝 시점에 필터링)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim b3092a0d5e feat: 백테스팅 유니버스 과거 시점 주식 스크리닝 (Historical Stock Universe)
## 새 기능
- GET  /universe/screen — 과거 날짜 기준 시총/섹터/거래소 필터링
- GET  /universe/registry — 추적 종목 목록 조회
- POST /universe/admin/discover — yfinance screener로 US 주식 자동 등록
- POST /universe/admin/build-snapshots — SEC EDGAR × yfinance 월별 시총 스냅샷 생성

## 데이터 모델
- universe_ticker_registry: 종목 마스터 (ticker, name, cik, sector, industry, exchange)
- universe_snapshot: 월별 스냅샷 (ticker, snapshot_date, market_cap, close_price, shares_outstanding)
  - 인덱스: (snapshot_date, market_cap) — 핵심 스크리닝 쿼리 최적화
  - ~4000종목 × 120개월 ≈ 480K 행 예상

## 데이터 흐름
1. SEC EDGAR companyfacts → shares_outstanding (최신, 주가분할 반영)
2. yfinance bulk download 1mo interval → 월별 종가
3. market_cap = latest_shares × close_price (yfinance 분할조정 가격과 일관성)

## 제한사항
- Survivorship bias: 현재 상장 종목만 (상폐 종목 미포함)
- 자사주 매입으로 과거 시총 ~20% 오차 가능 (분할 오차 방지가 주목적)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim e121c7ab40 feat: Earnings Surprise를 yfinance-plus로 전환 (Alpha Vantage 제거)
- yfinance Ticker.earnings_dates에서 EPS Estimate + Reported EPS + Surprise(%) 직접 제공
- Alpha Vantage 의존성 완전 제거 (API 키 불필요, rate limit 없음)
- ~25분기(6년+) 커버리지, 진짜 애널리스트 컨센서스 기반
- AAPL: 12분기 연속 beat, avg +4.36%
- MSFT: 8분기 연속 beat, avg +4.50%
5 months ago
I Luk Kim 99a998b2f1 feat: Earnings Surprise에 Alpha Vantage 애널리스트 추정치 통합
- XBRL reported EPS + Alpha Vantage estimated EPS 합산
- surprise = reported - estimated (진짜 컨센서스 대비)
- fiscal 날짜 ±15일 fuzzy match (Apple 등 비표준 fiscal calendar 대응)
- AV 키 미설정 시 reported EPS만 반환 (graceful degradation)
- AV rate limiter: 5 req/min token bucket
- force_refresh 시 기존 데이터 삭제 후 재인덱싱
5 months ago
I Luk Kim 971c0f053f feat: Earnings Surprise를 SEC XBRL로 전환 + Insider/FINRA 과거 데이터 확장
1. Earnings Surprise: Alpha Vantage → SEC EDGAR XBRL 전환
   - companyfacts API에서 EarningsPerShareDiluted/Basic 추출
   - QoQ surprise 계산 (현재 EPS - 이전 분기 EPS)
   - 15일 이내 중복 분기 제거 (10-Q 우선)
   - force_refresh 시 기존 데이터 삭제 후 재인덱싱
   - API 키 불필요, 2009년~ 커버리지

2. Insider transactions: days max 1095→3650, older pages 3→10

3. FINRA: 이전 커밋에서 이미 days=3650, limit=10000 적용됨
5 months ago
I Luk Kim e2cd16035a feat: Form 4 내부자 거래 + Earnings Surprise + FINRA 확장
F1: SEC Form 4 내부자 거래 (insider transactions)
- GET /insider/transactions/{symbol} — Form 4 거래 내역 조회
- GET /insider/summary/{symbol} — 3/6/12개월 매수/매도 집계
- SEC EDGAR submissions JSON → Form 4 XML 파싱 → DB 저장
- 자동 인덱싱 (첫 조회 시 SEC에서 페치)
- joint filing, derivative/non-derivative 거래 모두 지원

F2: Earnings Surprise (Alpha Vantage)
- GET /earnings/surprise/{symbol} — 분기별 EPS surprise
- reported EPS vs estimated EPS, beat/miss streak 계산
- ALPHA_VANTAGE_API_KEY 환경변수 필요 (무료 tier: 25 req/day)
- DB 캐싱으로 반복 호출 시 API 절약

F3: FINRA Short Volume 확장
- days 파라미터: max 365 → 3650 (10년)
- limit 파라미터: max 1000 → 10000
- 5년치 백필 완료 반영

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 months ago
I Luk Kim fd4816dbbd 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>
5 months ago
I Luk Kim 2c4b4d583d perf: API 성능 개선 Round 3 — 8개 항목 (P15-P22)
P15: FINRA ingest N+1 쿼리 → chunked batch upsert (on_conflict_do_nothing)
P16: Request log 통계 4개 COUNT 쿼리 → 단일 case() 집계 쿼리
P17: stocks/52-week-gainers (1h), stocks/trending (30m) 캐시 추가
P18: Alpaca bars/data 캐시 TTL None→86400 (과거 불변 데이터)
P19: Request log flush 배치 100→500, 간격 1s→2s
P20: Overlay feature_builder matched_symbols Python 필터 → DB JSONB @> 연산자
P21: with_cache Pydantic 모델 캐시 키에 model_dump_json() 사용
P22: Alpaca price 저장 SELECT+filter → batch upsert (on_conflict_do_nothing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 months ago
I Luk Kim 0752b29a63 perf: API 성능 개선 Round 2 — 9개 항목 (P6-P14)
Tier 1:
- P6: 미들웨어 request_info 지연 추출 (성공 경로에서 body/headers 읽기 제거)
- P7: GZip 압축 미들웨어 (minimum_size=1000, ~4x 압축)
- P8: Redis maxmemory 512MB + allkeys-lru 퇴거 정책

Tier 2:
- P9: _store_ticker_data() batch upsert 전환 (ON CONFLICT DO NOTHING)
- P10: DB 커넥션 풀 증가 (pool_size=20, max_overflow=30, 환경변수 설정)
- P11: Attention 엔드포인트 Redis 캐시 추가 (@with_cache ttl=3600)

Tier 3:
- P12: Dockerfile 멀티 워커 (--workers 4, dev는 --reload 오버라이드)
- P13: aiohttp ClientSession 싱글턴 공유 (4개 파일 7곳 TCP/TLS 재사용)
- P14: Pydantic model_validate → model_construct (bulk 경로 validation 스킵)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 months ago
I Luk Kim 6a98cb2d94 perf: API 성능 개선 — 로그 분석 기반 6개 항목 수정
P0 — 500 에러 대폭 감소 (1.1% → 0.11%)
- price.py / financial.py: except HTTPException: raise 추가 →
  HTTPException(404)이 except Exception에 잡혀 500으로 재포장되던 버그 수정
- real_sec_financial_service: _get_price_data_for_period에 try/except 추가 →
  yfinance 실패가 financial 엔드포인트 500으로 전파되지 않도록 방어
- price.py: TimeoutError 별도 핸들러 추가 → yfinance 타임아웃 시 503 반환

P1 — _store_price_data() N+1 → batch upsert
- price_data_service: 252회 개별 SELECT+INSERT 루프를
  pg_insert(PriceData).on_conflict_do_nothing('uq_price_data') 단일 쿼리로 교체

P2 — financial/data 캐시 TTL 1h → 24h
- financial.py: 재무 데이터는 분기 발표 주기 → _FIN_TTL = 86400

P3 — 과거 가격 데이터 TTL 연장
- price.py: end_date < today-1 이면 TTL=7일, 나머지 1h 유지

P4 — 요청 로그 비동기 배치 처리
- error_logger.py: asyncio.Queue(10_000) 추가, _log_request를 put_nowait으로
  변경 (논블로킹), 백그라운드 _request_log_flusher 코루틴 (1s/100건마다 flush)
- main.py: 앱 시작 시 start_request_log_flusher() 호출

P5 — 누락 DB 인덱스 추가
- financial.py: CalculatedMetrics에 calculation_date, period_date 단독 인덱스
- attention.py: AttentionFeaturesDaily에 ticker 단독 인덱스
- alembic b3c4d5e6f7a8: 위 3개 인덱스 생성 마이그레이션 (idempotent)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 months ago
I Luk Kim d869ea98bd fix(etf): as_of_date에 가장 가까운 파일링 반환하도록 수정
- _load_snapshot_holdings(): DB 캐시 스냅샷이 as_of_date와 120일 초과
  차이나면 stale로 판단하고 None 반환 → SEC 신규 fetch 트리거
- _find_best_filing_and_xml(): eligible 필터에 365일 하한 추가,
  target_date 기준 1년 이내 파일링만 우선 후보로 사용
  (하한 내 후보 없으면 기존 전체 검색 fallback 유지)

수정 전: SPY?as_of_date=2021-06-30 → 2019-11-18 (19개월 stale)
수정 후: SPY?as_of_date=2021-06-30 → 2021-05-28 (정상)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 4a0aff9ad6 fix(overlay): 다중 버그 수정 및 OpenAPI 문서 개선
## Overlay 버그 수정

- **collect_all 동시성 오류**: asyncio.gather로 공유 DB 세션에 동시 접근 → SQLAlchemy 오류
  발생. 어댑터를 순차 실행으로 변경
- **feedparser/apscheduler/pytrends 미설치**: Docker 이미지 재빌드로 패키지 영구 포함
- **중복 job log 항목**: _log_job이 매번 새 행 삽입 → running+completed 중복 생성.
  기존 running 행을 업데이트하도록 수정
- **admin/health 잘못된 job_type**: yahoo_rss/wikimedia 등 존재하지 않는 타입 조회.
  실제 로깅되는 collect_all/feature_build만 조회하도록 수정
- **source_presence 항상 false**: z-score가 계산 불가능하면(2일 미만 데이터) source가
  false로 표시됨. 실제 데이터 존재 여부(headline_count_24h > 0 등)로 판단하도록 수정
- **top-movers 심볼 중복**: 파이프라인 실행 횟수만큼 같은 심볼 반복 출력.
  심볼별 최신 레코드만 조회하는 서브쿼리로 수정
- **YouTube None 곱셈 오류**: view_count * channel_weight에서 None이면 TypeError.
  (or 0) / (or 0.5) 가드 추가

## Trends 기능 수정

- **ThemeTopicMap 자동 시딩**: 파이프라인 최초 실행 시 TOP_50_SYMBOLS에 대한
  기본 topic 매핑 자동 생성
- **GOOGLE_TRENDS_ENABLED=true**: docker-compose.yml에 환경변수 추가
- **theme_heat_z 항상 null**: feature_builder에 build_trends_features() 메서드
  누락 → OverlayTrendObservation 데이터가 점수에 반영 안 됨. 메서드 추가 및 연결
- **POST /admin/seed-topics**: ThemeTopicMap 수동 시딩용 admin 엔드포인트 추가

## OpenAPI 문서 개선

- 모든 엔드포인트에 summary/description 추가 (filings, news, database, etf, stocks,
  screener, fred, attention, overlay)
- Pydantic 스키마에 json_schema_extra example 추가 (attention, filing)
- 누락된 태그 6개 추가 (attention, attention-admin, database, fred, error-logs,
  request-logs)
- 루트(/) 랜딩 페이지를 Swagger UI로 리다이렉트로 교체

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim afd133e027 refactor(main): 루트 문서 페이지 OpenAPI 스키마에서 자동 생성
하드코딩된 330줄 HTML을 제거하고 request.app.openapi()에서
동적으로 엔드포인트 목록을 렌더링하도록 변경.
새 라우터 추가 시 / 페이지를 별도로 수정할 필요 없음.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 8c9abe3e31 feat(attention): Attention 서브시스템 추가 — 이벤트 중심 Wikipedia/GDELT 관심도 피처
## 주요 기능
- Entity resolver: ticker → canonical name → Wikipedia 매칭
  - SEC company_tickers.json fallback으로 placeholder name 자동 수정
  - all-caps SEC 이름 title-case 변환, "Com" suffix 처리
- Wikipedia 페이지뷰 수집 + spike_10d / zscore_20d 피처 계산
- GDELT V2 DOC API 뉴스 기사 수집 (2017-01-01 이후)

## GDELT rate limit 제약 강제
- /event/{ticker} 온디맨드 GDELT 수집 제거 (IP ban 방지)
- 프로세스 전역 asyncio.Lock + 10초 최소 간격 강제
- 429 시 exponential backoff (30→60→120s)
- news.gdelt_status 필드로 클라이언트에 수집 상태 명시
  ('collected' | 'not_collected' | 'not_available')

## API
- GET  /api/v1/attention/event/{ticker}?event_date=YYYY-MM-DD
- GET  /api/v1/attention/entity/{ticker}
- POST /api/v1/attention/admin/resolve/{ticker}
- POST /api/v1/attention/admin/collect/wiki/{ticker}
- POST /api/v1/attention/admin/collect/gdelt/{ticker}  ← scheduler 전용

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 45d1469332 fix(sec): resp.get_encoding() 버그 수정 — 504 직접 원인
content.read()로 body를 읽은 후 resp.get_encoding() 호출 시
aiohttp 내부 self._body(None) 접근으로 예외 발생.
이 예외가 except Exception에 잡혀 6번 retry + 지수 backoff(최대 ~22초)
→ asyncio.wait_for 캔슬 → TimeoutError → 504.

Content-Type 헤더에서 charset 직접 파싱으로 교체.
기본값 utf-8, SEC는 대부분 charset 미지정이므로 실질적으로 항상 utf-8 사용.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim fb6a099e50 fix(sec): exhibit timeout 15초 → 25초 (2단계 fetch 고려)
index 페이지 + exhibit 두 단계를 15초에 커버하기엔 너무 빡빡함.
미캐시 시 index fetch 최대 8초 + exhibit fetch 최대 12초 = 최악 20초.
max_bytes 수정으로 메모리 고갈은 이미 해결되었으므로
deadline은 25초로 조정 (원래 30초보다 5초 단축).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim e2c242c7cc fix(sec): 대형 exhibit 파일로 인한 서버 메모리 고갈 방지
- fetch_text에 max_bytes 파라미터 추가: Content-Length 헤더로 다운로드 전 사전 reject,
  헤더 없으면 content.read(max_bytes+1)로 제한적 읽기
- ValueError는 즉시 raise (retry 없음 — 크기는 재시도해도 안 줄어듦)
- in-memory 캐시(_text_cache) 1MB 가드: 대형 응답은 디스크 캐시에만 저장
- MAX_EXHIBIT_SIZE 1MB → 5MB, fetch_text(max_bytes=...) 호출로 다운로드 전 체크
- exhibit deadline 30초 → 15초 (서비스 + 엔드포인트 + bulk)
- 신규 테스트 5개: Content-Length 사전 거부, body 제한 읽기, 메모리 캐시 가드,
  소형 캐시 유지, ValueError no-retry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 89113d45ab fix(sec): _deadline 레이스 컨디션 수정, bulk exhibit 동시성 제한 추가
- SECHttpClient._deadline(인스턴스 변수) → contextvars.ContextVar로 교체
  asyncio task별 독립 데드라인으로 싱글턴 공유로 인한 레이스 컨디션 해결
- bulk exhibit에 Semaphore(4) + 전체 300s 타임아웃 추가
  동시 50개 코루틴이 Semaphore(2)를 무제한 점유하던 문제 해결

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim f17644e5e8 fix(sec): N+1 쿼리 제거, 청크 커밋, 세션 분리, rate limiter 추가
- sec_filings_service: 루프 전 accession_number 일괄 pre-fetch로 N+1 제거
- sec_filings_service: 500건 단위 청크 커밋으로 all-or-nothing 트랜잭션 방지
- sec_filings_service: bulk 인덱싱 시 코루틴별 독립 세션 생성으로 동시 세션 충돌 해결
- sec_filings_service: index_filings 데드라인 60s → 120s, bulk timeout 동일 적용
- sec_http_client: _TokenBucket(10 req/sec) 추가로 SEC EDGAR 과부하 방지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim af88f89f69 docs: 앱 내부 문서(루트 HTML, OpenAPI)에 index 엔드포인트 추가
- main.py 루트 HTML: Stock Market Data 섹션에 /stocks/index/{index_name} 항목 및 예시 추가
- stocks 태그 설명 업데이트 (index constituents 포함)
- get_index_constituents: summary/response_description/docstring 상세화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 969706de01 fix(stocks): Wikipedia 403 우회를 위해 User-Agent 헤더 추가
pd.read_html() 대신 urllib로 HTML 직접 fetch 후 파싱.
User-Agent 미설정 시 Wikipedia가 403 반환하는 문제 수정.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim cbc0f93123 feat(stocks): Wikipedia 인덱스 구성 종목 조회 API 추가
GET /stocks/index/{index_name} 엔드포인트 추가.
sp500/nasdaq100 구성 종목을 Wikipedia에서 실시간 파싱하여 반환.
24시간 Redis 캐시 및 30초 타임아웃 적용.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 1e9432a6e3 fix: exhibit/bulk에서 concurrent DB 세션 충돌 수정
asyncio.gather로 동시에 실행되는 exhibit 항목들이 단일 DB 세션을
공유하면서 "concurrent operations are not permitted" 에러 발생.

각 _fetch_one 태스크가 AsyncSessionLocal()로 독립 세션 사용하도록 수정.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 45d832ba5c perf: Phase 1-3 Stock Oracle API 성능 개선
Phase 1A - yfinance hang 제거
- _run_with_timeout() 헬퍼 추가 (asyncio.wait_for 래퍼)
- run_in_executor 6곳에 timeout 적용: history(30s), .info(20s), bulk download(60s)

Phase 1B - SEC Filing deadline 설정
- index_filings: 60s deadline + try/finally
- get_filing_documents: 30s deadline (중첩 호출 시 기존 deadline 유지)
- get_exhibit_content: 30s deadline + try/finally

Phase 1C - _get_ticker_max_range async 전환
- sync → async def + run_in_executor + wait_for(20s)
- get_or_create_company_data에서 period=="max" 사전 체크 → await 직접 호출

Phase 1D - Endpoint 레벨 timeout
- POST /price/data/bulk: 300s → 504
- POST /financial/data/bulk: 300s → 504
- GET /filings/search/{ticker}: 120s → 504
- GET /filings/documents/{accession}: 30s → 504
- GET /filings/exhibit/{accession}: 30s → 504

Phase 2 - Filing 캐시 추가
- GET /filings/documents: @with_cache(ttl=86400)
- GET /filings/exhibit: @with_cache(ttl=86400)

Phase 3A - POST /filings/search/bulk 추가
- BulkFilingSearchRequest/Item/Response 스키마
- search_filings_bulk(): 배치 DB 조회 → 미인덱싱 ticker 병렬 인덱싱(Semaphore 4)
- @with_cache(ttl=3600), 600s endpoint timeout

Phase 3B - POST /filings/exhibit/bulk 추가
- BulkExhibitRequest/Item/Response 스키마
- asyncio.gather + 개별 30s timeout, 최대 50건

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim b88835cded feat(screener): add stock screener API with yfinance EquityQuery
Implements GET /api/v1/screener/stocks and GET /api/v1/screener/fields
for condition-based stock filtering without manual web searches.

- app/schemas/screener.py: ScreenerStockItem + ScreenerResponse Pydantic models
- app/services/screener_service.py: ScreenerService wrapping yfinance screen()
  via run_in_executor; exchange mapping (NYSE→NYQ, NASDAQ→NMS/NGM/NCM, etc.);
  btwn/gt/lt/is-in/eq EquityQuery builder; post-filter for ETF/FUND exclusion
- app/api/v1/endpoints/screener.py: /stocks (with_cache TTL=300) + /fields metadata
- app/api/v1/api.py: register screener router at prefix /screener
- app/main.py: add screener OpenAPI tag and HTML doc section with examples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim d82ba624e2 chore: remove SQLite entirely, PostgreSQL only
SQLite was never used in production (Docker Compose always sets
DATABASE_URL to postgresql+asyncpg://...) but the fallback kept
creating confusing stock_oracle.db files locally.

- config.py: default DATABASE_URL fallback → PostgreSQL (localhost:15433)
- database.py: remove _is_sqlite conditional branch and NullPool import
- error_logs.py: replace strftime/dialect-check with pg to_char()
- request_logs.py: replace strftime with pg to_char()
- feature_builder.py: remove _ensure_utc() helper and all call sites
- overlay_pipeline.py: remove naive-datetime workaround for is_stale()
- requirements-api.txt / requirements-test.txt: drop aiosqlite
- stock_oracle.db: deleted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 9085151af8 chore(main): remove SQLite ALTER TABLE migration shim
PostgreSQL is the actual DB; create_all handles schema creation.
The SQLite workaround added in the previous commit is not needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 76e154eccd feat(filings): add accepted_at field to filing search response
Expose SEC ACCEPTANCE-DATETIME so downstream consumers (filing_poller)
can populate Document.accepted_at_utc and Event.filed_at_utc.

- SECFiling model: add nullable accepted_at TIMESTAMP column
- main.py startup: ALTER TABLE migration for existing SQLite DBs
- sec_filings_service: extract acceptanceDateTime from SEC JSON, store on
  INSERT and force_refresh UPDATE
- FilingSummary schema: add Optional[str] accepted_at field
- filings endpoint: map accepted_at as ISO 8601 string in response
- CHANGELOG: v3.0.1 entry

Existing rows have accepted_at=NULL; backfill with force_refresh=true.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 29fc870b37 Add Phase 5 Attention Overlay API and fix Redis port mismatch
- 12 REST endpoints: overlay score, bulk, top-movers, headlines, youtube, wiki, crowding, trends, history, admin (health/trigger/job-log)
- 10 services: entity_resolver, yahoo_rss_adapter, wikimedia_adapter, youtube_adapter, google_trends_adapter, finra_overlay_loader, feature_builder, overlay_scorer, overlay_pipeline, scheduler
- 10 DB tables across overlay_registry, overlay_raw_event, overlay_feature models
- APScheduler: collect @ 23:30 UTC + feature build @ 01:30 UTC weekdays
- Fix Redis port mismatch: config default 16379 → 16380 to match docker-compose external port
- 64 overlay tests covering cache utils, Redis config, all 12 endpoints, route ordering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 9e2a8aba47 Codebase improvements: caching decorator, Pydantic v2, DB indexes, connection pooling, Alembic
- Add @with_cache() decorator to eliminate ~15-line caching boilerplate per endpoint
- Apply decorator to 8 existing endpoints (stocks, alpaca, finra) and add caching
  to 6 previously uncached endpoints (news, etf, filings) with appropriate TTLs
- Migrate all @validator to @field_validator (Pydantic v2), deduplicate validation
  logic into shared functions in validators.py
- Fix datetime.utcnow() → datetime.now(timezone.utc), remove unused uuid import
- Convert ErrorLogResponse class Config → model_config = ConfigDict(...)
- Add health check exception logging instead of silent pass
- Add data_source indexes to PriceData and FinancialData tables
- Initialize Alembic with async engine configuration
- Add persistent HTTP sessions for SEC client (aiohttp) and FRED proxy (httpx)
- Add response_model schemas for Alpaca bars/intraday and news-only/social-only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim bf6932ca2f Add Alpaca-specific DB table and Redis caching for Alpaca/FINRA endpoints
Separate Alpaca price data into dedicated AlpacaPriceData table to avoid
UniqueConstraint('ticker', 'date') conflicts with Yahoo Finance PriceData.
Add Redis caching (build_cache_key/get_cached_response/set_cached_response)
to 3 Alpaca endpoints and 2 FINRA query endpoints with appropriate TTLs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago
I Luk Kim 93d8018746 Update API documentation with SEC filings endpoints
Add SEC Filings section to root HTML docs, example requests, key features,
and OpenAPI tags for Swagger UI / ReDoc visibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 months ago