63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Yahoo Finance fundamental data provider (via yfinance)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from shared.fundamentals.base import FundamentalsProvider, FundamentalsSnapshot
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _safe_float(val: object) -> float | None:
|
|
"""Convert a value to float, returning None for missing/invalid values."""
|
|
if val is None:
|
|
return None
|
|
try:
|
|
return float(val)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
|
|
|
|
def _safe_float_div100(val: object) -> float | None:
|
|
"""Convert a value to float and divide by 100 (e.g. D/E ratio)."""
|
|
f = _safe_float(val)
|
|
return f / 100.0 if f is not None else None
|
|
|
|
|
|
def _fetch_info(ticker: str) -> dict:
|
|
"""Synchronous helper — runs in a thread pool to avoid blocking the loop."""
|
|
import yfinance # noqa: local import to avoid hard dependency
|
|
|
|
return yfinance.Ticker(ticker).info
|
|
|
|
|
|
class YahooFinanceProvider(FundamentalsProvider):
|
|
"""Fetches fundamental data from Yahoo Finance via the yfinance library."""
|
|
|
|
async def fetch(self, ticker: str) -> FundamentalsSnapshot | None:
|
|
"""Fetch ticker info from Yahoo Finance in a thread pool."""
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
info = await loop.run_in_executor(None, _fetch_info, ticker)
|
|
|
|
if not info:
|
|
logger.warning("Yahoo Finance returned empty info for %s", ticker)
|
|
return None
|
|
|
|
return FundamentalsSnapshot(
|
|
ticker=ticker,
|
|
eps_ttm=_safe_float(info.get("trailingEps")),
|
|
pe_ratio=_safe_float(info.get("trailingPE")),
|
|
peg_ratio=_safe_float(info.get("pegRatio")),
|
|
revenue_growth_yoy=_safe_float(info.get("revenueGrowth")),
|
|
profit_margin=_safe_float(info.get("profitMargins")),
|
|
debt_to_equity=_safe_float_div100(info.get("debtToEquity")),
|
|
market_cap=_safe_float(info.get("marketCap")),
|
|
fetched_at=datetime.now(timezone.utc),
|
|
)
|
|
except Exception:
|
|
logger.exception("Yahoo Finance fetch failed for %s", ticker)
|
|
return None
|