- 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>
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>
날짜별 전체 심볼의 공매도량 + 조정종가 조인 결과 반환.
pit_universe_membership ⋈ alpaca_price_data(interval='1d') 조인.
생존편향-0: 상폐 종목도 그날 거래됐으면 포함.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
직접 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>
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>
[사안 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>
과거 OHLC 행은 on_conflict_do_nothing, 오늘 행만 on_conflict_do_update.
Yahoo Finance의 배당·분할 소급 조정으로 인한 기존 PIT 데이터 훼손 방지.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gainer_snapshots 테이블을 읽는 엔드포인트가 없어 백테스팅이 불가능했던 문제 해결.
?at=<ISO8601> 생략 시 최신 snapshot, as-of semantics로 장외 시각도 자연스럽게 처리.
openapi.json 동기화.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /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>
- 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>
500+ 티커 bulk 스캔 시 ensure_future로 수백 개의 SEC EDGAR 태스크가
동시 생성되어 이벤트 루프 포화 → 서버 응답 불가.
_BACKGROUND_REINDEX_SEMAPHORE(3)으로 동시 실행을 최대 3개로 제한.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
동시 요청 시 둘 다 빈 existing_accs 읽고 중복 INSERT → UniqueViolationError 발생.
pg_insert().on_conflict_do_nothing()으로 원자적 upsert 교체,
staleness 재인덱스를 background task로 이동해 요청 차단 제거.
auto-parse는 최근 20개로 제한.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
날짜만 보내면(2026-04-15) Alpaca가 ET 자정(23:59 ET = 03:59 UTC)으로 해석해
당일 장 마감 후에도 "미래 end" 로 판정 → SIP 403 에러 발생.
RFC-3339 datetime 형식(2026-04-15T23:59:59Z)으로 변경하면
Alpaca가 UTC 기준으로 정확히 해석해 15분 규칙을 통과함.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
반복적인 서버 응답불능의 근본 원인 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>
Python CPython 메모리 할당자는 freed 블록을 OS에 반환하지 않음.
수백 건의 백필 요청 처리 후 heap이 계속 성장 → 시스템 전체 OOM.
변경:
- --limit-max-requests 500: 500요청 후 uvicorn worker 자동 재시작
→ heap 초기화, 메모리 누수 방지
- mem_limit/memswap_limit 2g: 컨테이너 메모리 2GB 하드 캡
→ OS 전체가 아닌 컨테이너 안에서 OOM 제어
→ Docker restart: unless-stopped로 자동 복구
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
원인: request_logs에 cleanup 정책이 없어 65M행이 쌓였다가 DELETE되면서
VACUUM FULL 없이 18GB dead page bloat 발생.
PostgreSQL 메모리/디스크 압박 → API worker OOM kill의 간접 원인.
수정:
- _request_log_cleanup() 백그라운드 코루틴 추가
→ 1시간마다 실행, 7일 초과 request_logs 자동 DELETE
→ start_request_log_flusher()에서 함께 스케줄
- request_logs autovacuum scale_factor 0.2 → 0.01
→ 1% 변경 시 autovacuum 즉시 실행, dead tuple 빠르게 회수
즉각 조치: TRUNCATE request_logs 실행 (25GB → 56kB)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BaseHTTPMiddleware는 요청당 2개 asyncio task를 생성한다.
--limit-concurrency 100은 여전히 200개 task가 event loop를 포화시켜
health check 포함 모든 요청이 block됐다.
변경 사항:
- docker-compose.yml: --limit-concurrency 100 → 20
(세마포어 10개 처리 + 10개 대기, 나머지 20초과 시 503으로 즉시 반환)
- alpaca_price_service.py: upsert 루프 청크 사이 + Phase 4 ORM 일괄 생성 후
await asyncio.sleep(0) 추가 — 동기 CPU 블로킹 중 event loop yield 보장
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
동시 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>
- 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>
서비스 메서드가 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>
외부 API 호출(yfinance, Alpaca) 중 DB 세션을 유지하는 구조로 인해
idle in transaction 커넥션이 쌓여 풀(50개) 고갈 → 서버 응답 불가.
- pool_timeout: 30s → 5s (풀 고갈 시 빠른 실패, 30s 행 방지)
- PostgreSQL idle_in_transaction_session_timeout: 90s 설정
(90초 이상 방치된 트랜잭션 자동 종료)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
기존: 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>
- interval 누락으로 uq_alpaca_price_data constraint 위반 방지
- 단일 INSERT → CHUNK=2300 분할 (14 params/row, asyncpg 한도 대비)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
제거: /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>