Chan-Compass / research.py
ranranrunforit's picture
Upload 17 files
3d1576e verified
Raw
History Blame Contribute Delete
3.53 kB
"""
research.py — Research Note (beta): a first slice of "Auto Research / auto report".
V1 scope (what this file does today):
input ticker → pull fundamentals via yfinance .info + recent headlines
→ local LLM writes a structured English research note covering
valuation, moat / supply-chain position, bull vs bear case, key risks.
V2 (not built yet, see Automation tab description):
multi-step agentic research (filings, peers, news crawl) and an automatic
trigger that generates a report whenever a NEW ticker enters the pool.
"""
from __future__ import annotations
import traceback
_FIELDS = [
("longName", "Name"), ("sector", "Sector"), ("industry", "Industry"),
("marketCap", "Market cap"), ("trailingPE", "P/E (ttm)"),
("forwardPE", "P/E (fwd)"), ("priceToSalesTrailing12Months", "P/S (ttm)"),
("priceToBook", "P/B"), ("enterpriseToEbitda", "EV/EBITDA"),
("profitMargins", "Net margin"), ("grossMargins", "Gross margin"),
("operatingMargins", "Operating margin"), ("returnOnEquity", "ROE"),
("revenueGrowth", "Revenue growth (yoy)"), ("earningsGrowth", "Earnings growth (yoy)"),
("freeCashflow", "Free cash flow"), ("totalCash", "Total cash"),
("totalDebt", "Total debt"), ("dividendYield", "Dividend yield"),
("beta", "Beta"), ("fiftyTwoWeekHigh", "52w high"), ("fiftyTwoWeekLow", "52w low"),
("currentPrice", "Price"), ("targetMeanPrice", "Analyst mean target"),
("recommendationKey", "Street view"),
]
def _fmt(key: str, v):
if v is None:
return None
try:
if key in ("marketCap", "freeCashflow", "totalCash", "totalDebt"):
v = float(v)
return f"${v/1e9:,.1f}B" if abs(v) >= 1e9 else f"${v/1e6:,.0f}M"
if key in ("profitMargins", "grossMargins", "operatingMargins", "returnOnEquity",
"revenueGrowth", "earningsGrowth", "dividendYield"):
return f"{float(v):.1%}"
if isinstance(v, float):
return f"{v:,.2f}"
except (TypeError, ValueError):
pass
return str(v)
def gather_facts(ticker: str) -> tuple:
"""Return (facts_markdown, facts_plain, error)."""
try:
import yfinance as yf
tk = yf.Ticker(ticker)
info = tk.info or {}
except Exception as e:
traceback.print_exc()
return "", "", f"Could not fetch fundamentals for {ticker}: {e}"
if not info or info.get("regularMarketPrice") is None and info.get("currentPrice") is None \
and not info.get("longName"):
return "", "", f"No fundamentals returned for {ticker} — check the symbol."
rows, plain = [], []
for key, label in _FIELDS:
val = _fmt(key, info.get(key))
if val is None:
continue
rows.append(f"| {label} | {val} |")
plain.append(f"{label}: {val}")
summary = (info.get("longBusinessSummary") or "")[:900]
heads = []
try:
for x in (tk.news or [])[:6]:
c = x.get("content", x)
t = c.get("title")
if t:
heads.append(t)
except Exception:
pass
md = f"**{info.get('longName', ticker)}** ({ticker})\n\n"
md += "| Metric | Value |\n|---|---|\n" + "\n".join(rows)
if heads:
md += "\n\n**Recent headlines:** " + " · ".join(heads[:5])
plain_txt = "\n".join(plain)
if summary:
plain_txt += f"\nBusiness: {summary}"
if heads:
plain_txt += "\nRecent headlines: " + " | ".join(heads)
return md, plain_txt, ""