feat: add fundamental data providers (Alpha Vantage, FMP, Yahoo Finance) with rotation
This commit is contained in:
parent
2398e8faf6
commit
aa47e896dd
7 changed files with 704 additions and 0 deletions
64
shared/fundamentals/fmp.py
Normal file
64
shared/fundamentals/fmp.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""Financial Modeling Prep (FMP) fundamental data provider."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
|
||||
from shared.fundamentals.base import FundamentalsProvider, FundamentalsSnapshot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_BASE_URL = "https://financialmodelingprep.com/api/v3/key-metrics-ttm"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class FMPProvider(FundamentalsProvider):
|
||||
"""Fetches fundamental data from Financial Modeling Prep key-metrics-ttm endpoint."""
|
||||
|
||||
def __init__(self, api_key: str, timeout: float = 10.0) -> None:
|
||||
self._api_key = api_key
|
||||
self._client = httpx.AsyncClient(timeout=timeout)
|
||||
|
||||
async def fetch(self, ticker: str) -> FundamentalsSnapshot | None:
|
||||
"""Fetch TTM key metrics from FMP."""
|
||||
try:
|
||||
resp = await self._client.get(
|
||||
f"{_BASE_URL}/{ticker}",
|
||||
params={"apikey": self._api_key},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
if not isinstance(data, list) or len(data) == 0:
|
||||
logger.warning("FMP returned empty response for %s", ticker)
|
||||
return None
|
||||
|
||||
item = data[0]
|
||||
|
||||
return FundamentalsSnapshot(
|
||||
ticker=ticker,
|
||||
eps=_safe_float(item.get("netIncomePerShareTTM")),
|
||||
pe_ratio=_safe_float(item.get("peRatioTTM")),
|
||||
peg_ratio=_safe_float(item.get("pegRatioTTM")),
|
||||
revenue_growth=_safe_float(item.get("revenueGrowth")),
|
||||
profit_margin=_safe_float(item.get("netProfitMarginTTM")),
|
||||
debt_to_equity=_safe_float(item.get("debtToEquityTTM")),
|
||||
market_cap=_safe_float(item.get("marketCapTTM")),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("FMP fetch failed for %s", ticker)
|
||||
return None
|
||||
|
||||
async def close(self) -> None:
|
||||
await self._client.aclose()
|
||||
Loading…
Add table
Add a link
Reference in a new issue