""" Fundamentals — Fetch key fundamental data from yfinance. Provides value, growth, and quality metrics for alpha scoring. """ import logging import yfinance as yf logger = logging.getLogger(__name__) def fetch_fundamentals(ticker: str) -> dict: """ Fetch key fundamental metrics for a ticker. Returns a dict with value, growth, and quality indicators. """ try: stock = yf.Ticker(ticker) info = stock.info or {} fundamentals = { "ticker": ticker, # Valuation "pe_ratio": info.get("trailingPE") or info.get("forwardPE"), "pb_ratio": info.get("priceToBook"), "ps_ratio": info.get("priceToSalesTrailing12Months"), "ev_ebitda": info.get("enterpriseToEbitda"), "peg_ratio": info.get("pegRatio"), # Growth "revenue_growth": info.get("revenueGrowth"), "earnings_growth": info.get("earningsGrowth"), "earnings_quarterly_growth": info.get("earningsQuarterlyGrowth"), # Profitability "profit_margin": info.get("profitMargins"), "operating_margin": info.get("operatingMargins"), "roe": info.get("returnOnEquity"), "roa": info.get("returnOnAssets"), # Dividend "dividend_yield": info.get("dividendYield"), # Size & Liquidity "market_cap": info.get("marketCap"), "avg_volume": info.get("averageVolume"), "avg_volume_10d": info.get("averageDailyVolume10Day"), # Analyst "target_mean": info.get("targetMeanPrice"), "target_low": info.get("targetLowPrice"), "target_high": info.get("targetHighPrice"), "recommendation": info.get("recommendationKey"), "num_analysts": info.get("numberOfAnalystOpinions"), # Info "sector": info.get("sector", "Unknown"), "industry": info.get("industry", "Unknown"), "name": info.get("shortName", ticker), "currency": info.get("currency", "USD"), } logger.info(f"{ticker}: Fetched fundamentals (PE={fundamentals['pe_ratio']}, sector={fundamentals['sector']})") return fundamentals except Exception as e: logger.error(f"{ticker}: Failed to fetch fundamentals: {e}") return {"ticker": ticker, "error": str(e)} def compute_fundamental_score(fundamentals: dict) -> float: """ Compute a composite fundamental quality score (0-100). Blends value, growth, and quality. """ score = 50.0 # Start neutral # Value component (lower PE = more value, capped) pe = fundamentals.get("pe_ratio") if pe and pe > 0: if pe < 15: score += 10 elif pe < 25: score += 5 elif pe > 50: score -= 10 # Growth component rev_growth = fundamentals.get("revenue_growth") if rev_growth: if rev_growth > 0.20: score += 15 elif rev_growth > 0.10: score += 8 elif rev_growth < 0: score -= 10 earn_growth = fundamentals.get("earnings_growth") if earn_growth: if earn_growth > 0.20: score += 10 elif earn_growth > 0.10: score += 5 elif earn_growth < -0.10: score -= 10 # Profitability roe = fundamentals.get("roe") if roe: if roe > 0.20: score += 10 elif roe > 0.15: score += 5 elif roe < 0: score -= 15 margin = fundamentals.get("profit_margin") if margin: if margin > 0.20: score += 5 elif margin < 0: score -= 10 # Analyst sentiment rec = fundamentals.get("recommendation") if rec: rec_scores = {"strongBuy": 10, "buy": 5, "hold": 0, "sell": -10, "strongSell": -15} score += rec_scores.get(rec, 0) return max(0.0, min(100.0, score))