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.
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
"""
|
|
Gainer snapshot collector — fetches top 200 day_gainers every 5-min slot.
|
|
Uses direct Yahoo Finance API via yahoo_client (no yfinance_plus).
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _floor_to_5min(dt: datetime) -> datetime:
|
|
return dt.replace(minute=dt.minute - (dt.minute % 5), second=0, microsecond=0)
|
|
|
|
|
|
async def collect_gainer_snapshot() -> int:
|
|
"""Fetch 200 day_gainers and bulk-insert into gainer_snapshots. Returns inserted count."""
|
|
from app.services.gainers.yahoo_client import fetch_day_gainers_sync, _parse_quote
|
|
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))
|
|
|
|
loop = asyncio.get_event_loop()
|
|
raw_quotes, total, error = await loop.run_in_executor(
|
|
None, fetch_day_gainers_sync, 200, 0
|
|
)
|
|
|
|
if error:
|
|
logger.error("[Gainers] fetch failed: %s", error)
|
|
return 0
|
|
if not raw_quotes:
|
|
logger.warning("[Gainers] No stocks returned (total=%d)", total)
|
|
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((_parse_quote(q) for q in raw_quotes), 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/%d rows", snapshot_at.isoformat(), len(rows), total)
|
|
return len(rows)
|