70 Commits (f5feffb80ef80f9fcfa75da308920bf94c318e40)

Author SHA1 Message Date
I Luk Kim f5feffb80e fix: adjustment='all' 조정가 적용 + parquet export 엔드포인트
- alpaca.py backfill: on_conflict_do_nothing → on_conflict_do_update
  기존 미조정 행(split 전 $1,208)을 조정가($120)로 덮어씀
  NVDA 2024-06-07 ~$120 통과 기준
- finra.py: POST /admin/export-pit-panel (Background) + GET /download
  pit_universe_membership × alpaca_price_data 조인 → /app/data/pit_panel.parquet
- requirements-api.txt: pyarrow>=14.0.0 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
I Luk Kim 2fb6ec0f0c fix: price_data_service null/zero close 자동 재수집 + Yahoo rate limit 429 + earnings 캐시
- price_data_service: null/zero close를 missing으로 처리 → 재수집 트리거
  UTC 자정 정규화로 yf.history / yf.download 중복 방지
  get_quote fallback: .info 실패 시 fast_info + history('2d') 체인
  historical upsert: close IS NULL/0인 경우만 덮어쓰기 (정상 데이터 보호)
- price.py: Yahoo rate limit → HTTP 429 + Retry-After: 30 응답
- earnings.py: bulk calendar 엔드포인트 캐시 1시간 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 months ago
I Luk Kim e9ad89ff74 fix: PIT 백필 — FINRA '/' 심볼 Alpaca 전송 전 정규화 ('/' → '.')
FINRA 심볼(BRK/B, HPX/U, WARR/WS 등)을 Alpaca에 그대로 전송하면
URL에 %2F가 포함되어 400 Bad Request 발생.
alpaca_batch = [_finra_to_alpaca(s) for s in batch] 로 사전 변환.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
I Luk Kim 843a188b76 feat: GET /finra/pit-panel — PIT 횡단면 패널 엔드포인트
날짜별 전체 심볼의 공매도량 + 조정종가 조인 결과 반환.
pit_universe_membership ⋈ alpaca_price_data(interval='1d') 조인.
생존편향-0: 상폐 종목도 그날 거래됐으면 포함.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
I Luk Kim ab693d332c refactor: 백필 스크립트 → REST admin 엔드포인트로 전환
직접 DB/서비스에 접근하는 스크립트 패턴을 REST API 패턴으로 교체:
- 삭제: scripts/backfill_finra_2020_gap.py (기존 POST /finra/admin/ingest으로 충분)
- 삭제: scripts/backfill_alpaca_daily_pit.py
- 삭제: scripts/export_pit_panel.py (외부 DB 연결으로 대체)
- 추가: POST /api/v1/alpaca/admin/backfill-pit
  BackgroundTasks 패턴, FINRA 전체 PIT 심볼 백필, adjustment=all

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
I Luk Kim 0c797ceb22 fix: attention zscore_20d null 3개 버그 수정 + 엔티티 오버라이드 API 추가
1. materialize_features identity-map 버그 수정
   - upsert commit 후 populate_existing=True로 재SELECT
   - resolve_entity와 동일 패턴 (SQLAlchemy async stale cache)

2. @with_cache 조건부 TTL — null 응답 캐시 단축
   - wiki.views=null → TTL 300s (5분)
   - 완전한 응답 → TTL 3600s (1시간)
   - 기존: null 응답이 1시간 캐시되어 재수집 영구 차단

3. 재실체화 조건 확장: wiki_zscore_20d is None도 재트리거
   - wiki_views는 있지만 lookback 부족으로 zscore만 null인 케이스 처리

4. POST /admin/entity/{ticker}/override 엔드포인트 추가
   - wiki_title 수동 지정 + is_manual_override=True 설정
   - CSCO→Cisco, DKNG→DraftKings 잘못된 매핑 수정용

5. entity_resolver: 소송 페이지 패턴 억제
   - "X v. Y" 형식 제목 score=0.05 (예: FSF v. Cisco Systems)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
I Luk Kim d513f64922 fix: screener total_available 필드 오독 수정 + feat: min_dollar_volume opt-in 추가
[사안 A] total_available 정확성 수정
- raw.get('count')(페이지당 행수=page_size)를 raw.get('total')(실제 매칭
  총수)보다 먼저 읽어 total_available=250/total_pages=1로 잘못 보고됨
- 'total' 우선으로 교정 (screen_stocks + screen_preset)
- 732행 집합에서 total_available 250→732, total_pages 1→3 검증

[사안 B] min_dollar_volume opt-in 파라미터 신규
- 가격(regularMarketPrice) × averageDailyVolume3Month 기준 달러거래량 필터
- 기본 OFF: 미지정 시 기존 경로 byte 동일 (회귀 0 검증)
- 지정 시 min_avg_volume을 Yahoo에 전달하지 않음 (고가·저주식수 우량주
  BLK/KLAC가 소스에서 잘리는 것 방지) → 달러거래량 게이트가 대체
- _collect_all_quotes로 전체 매칭 집합 페이지네이션(24p/6000행 상한,
  초과 시 truncated 플래그) 후 post-filter + 서버측 재정렬
- @with_cache key_params에 min_dollar_volume 등록 (미지정/지정 캐시 분리)
- 검증: min_dollar_volume=1e8 시 BLK/KLAC(p1)·URI/GRMN(p2) 전부 포함,
  exclude_types·market_cap_min·price_min 동시 정상 적용

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3 months ago
I Luk Kim e2389eba31 feat: GET /stocks/gainers/snapshots — 과거 5분봉 gainer snapshot 조회 엔드포인트 추가
gainer_snapshots 테이블을 읽는 엔드포인트가 없어 백테스팅이 불가능했던 문제 해결.
?at=<ISO8601> 생략 시 최신 snapshot, as-of semantics로 장외 시각도 자연스럽게 처리.
openapi.json 동기화.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
I Luk Kim cc024ecbe1 refactor: day_gainers → Yahoo Finance API 직접 호출 (curl_cffi 우회)
- yfinance_plus/preset 제거, query1.finance.yahoo.com 직접 호출
- curl_cffi 세션 풀(4개) + 브라우저 지문 로테이션으로 429 우회
- count=200 반환 확인 (yfinance preset 25개 한계 해소)
- /stocks/gainers: page/page_size → count 파라미터로 변경
- collector: fetch_day_gainers_sync(200) 사용

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
I Luk Kim 1d087b54a4 feat: day_gainers 5분봉 수집 + News v2 ingest + overlay 정리
주요 변경사항:

gainer snapshot (신규)
- gainer_snapshots 테이블: 5분 단위 intraday top-100 day_gainers 스냅샷
- APScheduler CronTrigger (*/5 min, Mon-Fri ET) + 장중 guard (9:30~16:00)
- alembic migration p7g8h9i0j1k2

/stocks/gainers 엔드포인트 (신규)
- yfinance day_gainers preset, 실시간(캐시 없음)
- yfinance_plus screen() wrapper — 모든 screen() 호출에 세션 풀 + 브라우저 지문 우회 적용

News v2 ingest (신규)
- Alpaca News + StockTwits + Finnhub 수집 파이프라인
- news_headlines 테이블 + scheduler (5분 realtime poll, 일 1회 backfill)

Overlay 정리
- 미사용 서브시스템 제거: wikimedia, youtube, google_trends, feature_builder, overlay_scorer
- Yahoo RSS 어댑터 유지, 파이프라인 단순화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 months ago
I Luk Kim f19d31f125 feat: /form4 auto-fetch fallback + targeted backfill 지원
- /form4/{ticker}, /form4/aggregate/{ticker}: DB에 ticker 데이터 없으면 SEC에서 800일치 자동 fetch (index_form4s)
- force_refresh 파라미터 추가로 on-demand 재인덱싱 지원
- Form4Response, Form4AggregateResponse에 metadata 필드 추가 (auto_fetched, fetched_count)
- bootstrap_form4_by_ticker.py: --tickers 옵션 추가로 특정 ticker만 targeted backfill 가능
- audit_form4_coverage.py: universe 커버리지 검증 스크립트 신규

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 93a81fd35a fix: Form 4 buy_only=P-only + 10분기 backfill 설정
- buy_only=true 필터를 P+A → P(open-market purchase) 단독으로 변경
  - get_form4_pit / get_form4_by_date / get_form4_aggregate 3곳 동일 수정
  - aggregate의 buy_dollar_total / cluster_size / csuite_count / avg_pct_of_holding
    모두 P-only 기준으로 정확해짐 (A코드 awards 제외)
- _parse_transaction_element: purchase_pct_of_holding 계산도 P-only로 정합
- SEC_FORM4_BOOTSTRAP_QUARTERS: 8 → 10 (2024Q1~)
- Form4AggregateResponse docstring에 P-only 명시

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim db9abd7786 feat: SEC Form 4 PIT + SC 13D/G activist ownership API
## 신규 엔드포인트
- GET /insider/form4/{ticker} — PIT-safe Form 4 거래 목록 (as_of 필수)
- GET /insider/form4/by-date/{date} — 특정 공시일 cross-ticker 거래
- GET /insider/form4/aggregate/{ticker} — 내부자 매수 집계 (window_days)
- GET /ownership/13dg/{ticker} — SC 13D/G activist 이벤트 (as_of 필수)
- GET /ownership/13dg/active — 현재 활성 activist 포지션 (window function PIT)

## 데이터 인프라
- activist_ownership_events 테이블 (42,329행, 최근 2년 bootstrap 완료)
- insider_transactions: is_ceo/is_cfo/is_c_suite/purchase_pct_of_holding/owner_relationship 컬럼 추가
- SECFullIndexService: company.idx fixed-width 파서, form345.zip 파서
- ActivistOwnershipService: SGML header issuer 해석 + cover-page XML/HTML enrich
- SEC ingest scheduler: 매 영업일 09:00 ET daily ingest + 30분 enrich job
- scripts: bootstrap_form4_by_ticker.py, bootstrap_form4_2y.py, bootstrap_13dg.py

## 버그 수정 (enrich 품질)
- filing_url double edgar/ prefix 제거
- XML: classPercent + amountBeneficiallyOwned + reportingPerson... 태그 추가
- HTML: 태그 제거 후 regex 적용 (CSS 10pt → shares 오인식 방지)
- HTML: ownership_pct > 100 방어 로직

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 1f0017a4ea fix: company metadata placeholder 제거 — sector/industry 실제 값 보장
- financial.py: 캐시 히트 시 company 블록을 CMS에서 항상 refresh
  (financial:data:* 캐시에 stale company 블록이 임베드된 문제 해결)
- financial_service.py: _is_placeholder() 확장 — exchange=null인
  Technology/Software 종목도 placeholder로 감지 (AVGO-류 미검출 해결)
- company_metadata_service.py: _sync_fetch() 개선 — yfinance 첫 번째
  호출은 auth 토큰 없이 partial 응답을 반환할 수 있으므로 sector가
  없는 EQUITY 종목에 대해 한 번 retry (auth 캐시 후 full data 획득)
- docker-compose.yml: 컨테이너 시작 시 yfinance_plus 로컬 버전을
  site-packages에 자동 복사 (docker restart 후 override 소실 방지)

검증: USAS/CPRX/HE/ACHR/AVGO 모두 실제 sector/industry/exchange 반환

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 7f28266bd6 feat: company metadata 안정화 — 실제 sector/industry/exchange 보강
- CompanyMetadataService 신규: Redis → registry → yfinance 순서 조회,
  semaphore(5) 보호, 24h cache, companies + registry UPSERT
- GET /company/{ticker} + POST /company/bulk (최대 100개, 세션 격리)
- companies 테이블에 exchange/country/market_cap 컬럼 추가 (alembic)
- CompanyInfo 스키마 exchange/country/market_cap 필드 추가
- _is_placeholder() 체크: 기존 "Technology/Software/XYZ Corporation" 행 재보강
- financial endpoint: financials/price 실패 시 company block은 유지 (500 방지)
- bulk endpoint: 각 ticker마다 독립 AsyncSession으로 동시 세션 충돌 방지
- scripts/backfill_registry_sector.py: 9376개 NULL-sector 일괄 보강 스크립트

검증: AU→Basic Materials, USAS→Basic Materials, CPRX→Healthcare,
      HE→Utilities, ACHR→Industrials, ZZZZZZ→404, AAPL 기존 데이터 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 59f78cd3ee fix: OOM → zombie worker 근본 수정 + 대량 스캔 방어 벡터 추가
- docker-compose: uvicorn --reload 제거 (PID 1 직접 실행) + mem_limit 2g→3g
  → OOM 시 컨테이너 종료 → restart:unless-stopped가 자동 복구
- /attention/event: Semaphore(8) + 10s fast-fail + wiki collector rate limit
- wiki_collector: Semaphore(2) + 0.25s min-interval + 429 log-burst 억제
- /filings/events: Semaphore(4) + 10s fast-fail (SEC HTML 다운로드 벡터 차단)
- sec_http_client: CIK 조회 실패 negative cache (1h) — 10MB dict 반복 스캔 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 0c4446a6ec fix: 대량 스캔 시 서버 사망 방지 — 세마포어 fast-fail + limit-concurrency 조정
- Alpaca intraday: 세마포어 대기 10초 제한, 초과 시 429 즉시 반환
  (기존: 120초 대기하며 커넥션 슬롯 점유 → 이벤트 루프 포화)
- Filing search: 세마포어(8) + 10초 fast-fail 추가
  (기존: 동시성 제한 없이 A-Z 스캔 시 수백 개 요청 쌓임)
- limit-concurrency 50→25 (실제 처리량은 세마포어가 제한하므로 여유 슬롯 불필요)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 86b28330b6 feat: request log 상태코드 범위 필터 추가 (min/max_status_code)
5xx 에러 필터를 status_code=500 (exact) 대신
min_status_code=500으로 변경해 500~599 전체 포함.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim c0e656b226 refactor: Alpaca intraday 120초 타임아웃 + limit-concurrency 50 복원
asyncio.wait_for로 120초 타임아웃 명시, _do_fetch 내부 함수로 semaphore 래핑.
limit-concurrency를 20→50으로 복원 (세마포어 5개로 충분히 제어됨).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 8d1216aa81 fix: 서버 안정성 종합 — BaseHTTPMiddleware 제거 + gc.collect() + Semaphore(5)
반복적인 서버 응답불능의 근본 원인 2가지를 해결:

1. BaseHTTPMiddleware → Pure ASGI 미들웨어 (app/middleware/error_logger.py)
   - BaseHTTPMiddleware.call_next()가 요청당 asyncio task 2개 생성
   - 20 동시연결 × 2 = 40 tasks → event loop scheduler 포화 → health check 타임아웃
   - __call__(scope, receive, send) + send_wrapper 패턴으로 교체
   - 요청당 단일 task, X-Request-ID 헤더 주입, 에러 응답 body 캡처 유지
   - 불필요한 의존성 제거: BaseHTTPMiddleware, Request, Callable, get_db, AsyncSession

2. gc.collect() 추가 + --limit-max-requests 제거 (alpaca.py, docker-compose.yml)
   - --limit-max-requests 500: 500요청 후 단일 worker 종료 → 서비스 gap 발생
   - 대신 intraday 요청 처리 후 gc.collect()로 Python heap 명시적 회수
   - 장시간 백필 중 메모리 누적 방지, worker 재시작 없이 안정 운영

3. Semaphore(3 → 5): BaseHTTPMiddleware 제거로 task 수 절반 → 처리량 복원

유지: mem_limit 2g, --limit-concurrency 20, Phase 4 경량 쿼리, request_logs 7일 retention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim abfab0fa57 fix: OOM kill 방지 — Phase 4 경량화 + Semaphore 3으로 제한
Worker가 OOM kill로 죽는 것을 확인 (docker inspect: OOMKilled=true).
호스트 메모리: 물리 RAM ~68MB 여유, 스왑 97% 사용 상태.

원인: Phase 4에서 SELECT AlpacaPriceData (전체 ORM 객체) 로드.
- 75 ticker × 78 5분봉 = 5,850개 ORM 객체/요청
- SQLAlchemy ORM instrumentation으로 dict 대비 ~10x 메모리 소비
- Semaphore(10) → 최대 10개 요청 동시 처리 = 피크 수백 MB ~ 1GB

수정:
- Phase 4: SELECT * → SELECT 7 columns only (ticker, date, OHLCV)
  → result.all()로 경량 Row namedtuple 반환, ORM 객체 생성 없음
  → SQLAlchemy Row는 속성 접근(row.date, row.open 등) 지원 — 엔드포인트 호환
- Semaphore(10) → Semaphore(3): 동시 처리 3개로 제한
  → 피크 메모리 ~70% 감소

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim e25f7295a6 fix: 대규모 Alpaca 백필 시 uvicorn 이벤트 루프 포화 방지
동시 Alpaca intraday 요청이 50개 이상 쌓이면 BaseHTTPMiddleware의
task 스케줄링 오버헤드로 이벤트 루프가 응답불능 상태가 되는 현상 수정.

- alpaca.py: _INTRADAY_SEMAPHORE(10) 추가 — /intraday, /intraday/today
  양쪽 핸들러를 감쌈. 11번째 이후 요청은 세마포어 대기(비용 없음)
- docker-compose.yml: --limit-concurrency 100 추가 — 100개 초과 시
  uvicorn이 503 반환, health 엔드포인트 보호

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim dd3e38fbf5 fix: GET /price/data — Alpaca → yfinance 교체 + bulk end_date 버그 2건 수정
- GET /price/data를 AlpacaPriceService 대신 PriceDataService(yfinance)로 교체
- ?ticker= alias 추가 (기존 ?tickers= 유지, 인터페이스 호환)
- _bulk_fetch_price_data: yfinance end 파라미터 exclusive 미반영 (+1일 누락) 수정
- get_multi_ticker_daily_bars: end_dt를 min.time(00:00) → max.time(23:59:59)으로 수정
  (DB 쿼리 date <= end_dt 에서 당일 레코드가 필터링되던 버그)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim c203f3920b refactor: session-per-phase — DB 커넥션을 외부 API 호출 중 해제
서비스 메서드가 db: AsyncSession을 인자로 받아 외부 API 호출(yfinance 30-60s,
Alpaca HTTP) 중에도 DB 커넥션을 잡고 있던 구조를 제거.

변경 패턴 (Session-per-phase):
  Before: Endpoint(db) → Service(db) → DB check → API call(30s 세션 유지) → DB store
  After:  Endpoint()   → Service()   → DB check(세션1) → API call(세션 없음) → DB store(세션2)

변경 파일:
- alpaca_price_service.py: fetch_and_store_bars, get_or_fetch_multi_bars에서 db 제거
- price_data_service.py: get_or_update_price_data, get_multiple_tickers_data_optimized에서
  db 제거; _fetch_price_data(새), _bulk_fetch_price_data(새, lambda 클로저 버그 수정);
  _fetch_and_store_price_data, _bulk_fetch_and_store_price_data, get_multiple_tickers_data 제거
- alpaca.py, price.py: Depends(get_db) 제거 (GET /latest 제외)
- financial_service.py, real_sec_financial_service.py: 호출 인자 정리

결과: "idle in transaction" 커넥션 0개, 풀 고갈 원인 근본 해결

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 52e2e4ed7d feat: /alpaca/intraday — 장 마감(16:00 ET) 후 당일 SIP 조회 허용
기존: end_date >= today 이면 무조건 400
변경: 장 마감(오후 4시 ET) 후에는 당일 날짜도 SIP로 조회 가능

- zoneinfo.ZoneInfo("America/New_York") 기반 ET 시각 체크
- 장 중 당일 요청 시 /intraday/today 안내 메시지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim f528ac751b refactor: Alpaca 엔드포인트 정리 — 4개로 통합
제거: /bars/{ticker}, /data/{ticker}, /intraday/{ticker}, /snapshot/{ticker}
유지:
  - GET /alpaca/status
  - GET /alpaca/intraday        (SIP, 과거, 멀티 종목)
  - GET /alpaca/intraday/today  (IEX, 당일, 멀티 종목)
  - GET /alpaca/snapshot        (IEX, 단일/멀티 통합)

openapi.json 업데이트 (95 → 91 endpoints)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 800bb9b4a6 fix: /alpaca/intraday/{ticker} SIP 피드로 변경 + 어제까지 날짜 제한
- fetch_bars_raw()에 feed 파라미터 추가
- /intraday/{ticker}: IEX → SIP, end_date >= 오늘이면 400 에러
- 캐시 TTL: 5분 → 24시간 (과거 데이터는 변하지 않음)
- openapi.json 업데이트

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 75b3a356d0 docs: Alpaca 엔드포인트 전체 IEX/SIP 피드 정보 명시
- /bars/{ticker}, /data/{ticker}: interval별 자동 피드 선택 표 추가
- /intraday/{ticker}: IEX 피드, 5분 캐시, DB 없음 명시
- /snapshot, /snapshot/{ticker}: IEX 강제 (무료 플랜 제한) 명시
- openapi.json 업데이트 (95 endpoints)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 8e58fdffa5 feat: Alpaca intraday 엔드포인트 분리 — /intraday (SIP, 과거) + /intraday/today (IEX, 당일)
- GET /alpaca/intraday: SIP 피드로 변경, end_date >= 오늘이면 400 에러, 백테스트용
- GET /alpaca/intraday/today: 신규, IEX 피드, 오늘 당일 실시간 전용, force_refresh=True
- AlpacaPriceService.get_or_fetch_multi_bars()에 feed 파라미터 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 7db2e28691 docs: Alpaca 100-symbol batch limit — endpoint description + 502 error msg
Both GET /price/data and GET /alpaca/intraday:
- description에 배치 제한 설명 추가 (100개/요청, 자동 분할, 응답시간 선형 증가)
- 502 에러 메시지에 심볼 수 초과 가능성 힌트 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 22c1d975d4 docs: add intraday data limitations to endpoint descriptions
GET /price/intraday (Yahoo Finance):
- 15분 지연, 1m=7일/5m-30m=60일/1h=730일 한계
- 백테스트 용도, 실시간 전략 부적합

GET /alpaca/intraday (Alpaca IEX):
- 실시간, IEX 피드 2~5% 커버리지
- 거래량 낮게 표시, 가격 레벨은 유사
- ORB 등 당일 실시간 전략 용도

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 5ef4d1790d feat: GET /price/intraday — multi-ticker intraday via Yahoo Finance
Alpaca free plan blocks recent SIP data (403 on same-day requests).
Replace with Yahoo Finance which has no subscription requirement.

- PriceDataService.get_multi_intraday(): yf.download() in chunks of 50,
  handles both single (flat DataFrame) and multi-ticker (MultiIndex) cases
- GET /price/intraday?tickers=...&interval=5m&start_date=...&end_date=...
  → same AlpacaMultiBarsResponse format (bars: {sym → [{timestamp,ohlcv}]})
  → source="YAHOO_FINANCE", Redis 5-min TTL cache
- /alpaca/intraday still exists for historical data (works on free plan)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
I Luk Kim 4edb0a244a fix: multi-ticker intraday — DB storage + DB-first fetch logic
Same get_or_fetch_multi_bars() approach as daily bars:
- stores intraday rows in AlpacaPriceData (full timestamp as PK component)
- subsequent requests for historical periods served from DB
- same-day requests during market hours always hit Alpaca (max_date < end_dt)
- force_refresh=true bypasses DB check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 months ago
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 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 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 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 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