Swing_Quant_Engine / backend /data /fundamentals.py
SiddharthVenba's picture
Initial commit for HF Space
75d9b3c
Raw
History Blame Contribute Delete
4 kB
"""
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))