""" Gainer snapshot collector — fetches top 100 day_gainers and stores per 5-min slot. """ import logging from datetime import datetime, timezone logger = logging.getLogger(__name__) def _floor_to_5min(dt: datetime) -> datetime: """Round down to the nearest 5-minute boundary.""" return dt.replace(minute=dt.minute - (dt.minute % 5), second=0, microsecond=0) async def collect_gainer_snapshot() -> int: """Fetch 100 day_gainers and bulk-insert into gainer_snapshots. Returns inserted count.""" from app.services.screener_service import screener_service from app.core.database import AsyncSessionLocal from app.models.gainer_snapshot import GainerSnapshot from sqlalchemy.dialects.postgresql import insert as pg_insert snapshot_at = _floor_to_5min(datetime.now(timezone.utc)) result = await screener_service.screen_preset("day_gainers", page=1, page_size=100) stocks = result.get("stocks", []) if not stocks: logger.warning("[Gainers] No stocks returned from day_gainers preset") return 0 rows = [ { "snapshot_at": snapshot_at, "rank": rank, "symbol": s["symbol"], "name": s.get("name"), "exchange": s.get("exchange"), "price": s.get("price"), "change_percent": s.get("change_percent"), "volume": s.get("volume"), "avg_volume_3m": s.get("avg_volume_3m"), "market_cap": s.get("market_cap"), "pe_ratio": s.get("pe_ratio"), "forward_pe": s.get("forward_pe"), "eps_ttm": s.get("eps_ttm"), "dividend_yield": s.get("dividend_yield"), "fifty_two_week_high": s.get("fifty_two_week_high"), "fifty_two_week_low": s.get("fifty_two_week_low"), } for rank, s in enumerate(stocks, start=1) ] async with AsyncSessionLocal() as db: stmt = pg_insert(GainerSnapshot).values(rows) stmt = stmt.on_conflict_do_nothing(constraint="uq_gainer_snapshot_symbol") await db.execute(stmt) await db.commit() logger.info("[Gainers] snapshot %s — %d rows", snapshot_at.isoformat(), len(rows)) return len(rows)