mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-10 11:44:41 +02:00
Panneau « Actions rapides » sur le dashboard : 6 boutons one-click qui renvoient une analyse finlab instantanée (déterministe, gratuite, sans LLM), dans une modale. - 📊 Analyse (action sélectionnée) : technique (RSI/MACD/MM/signaux) + fondamental (PER/PEG/ROE/marges/dette/reco/cible) + plan R:R, combinés en une fiche - 📐 Plan R:R, 📈 Fondamentaux (action sélectionnée) ; 📋 Digest, 🔭 Opportunités, 🔔 Alertes (global) - Backend : endpoints /api/digest, /api/fundamentals, /api/analyze - Fix : fundamental.compare_portfolio() utilisait l'ancien schéma mono-compte (pf["positions"]) → cassé par le multi-comptes ; passe par data.portfolio_tickers() Vérifié : endpoints OK (TestClient), rendu modale au navigateur (Playwright) — fiche Analyse NVDA complète (technique + fondamental + plan). JS node --check OK. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Analyse fondamentale : ratios clés et résultats via yfinance."""
|
|
from __future__ import annotations
|
|
|
|
import pandas as pd
|
|
|
|
from . import data
|
|
|
|
|
|
def _b(x: float | None) -> str:
|
|
"""Formate un grand nombre en milliards."""
|
|
return f"{x/1e9:.1f} Md" if isinstance(x, (int, float)) else "—"
|
|
|
|
|
|
def snapshot(symbol: str) -> dict:
|
|
i = data.info(symbol)
|
|
|
|
def g(*keys):
|
|
for k in keys:
|
|
v = i.get(k)
|
|
if v is not None:
|
|
return v
|
|
return None
|
|
|
|
return {
|
|
"ticker": symbol,
|
|
"nom": g("shortName", "longName"),
|
|
"secteur": g("sector"),
|
|
"cours": g("currentPrice", "regularMarketPrice"),
|
|
"PER": g("trailingPE"),
|
|
"PER_fwd": g("forwardPE"),
|
|
"PEG": g("pegRatio"),
|
|
"P/S": g("priceToSalesTrailing12Months"),
|
|
"P/B": g("priceToBook"),
|
|
"marge_nette_%": round(g("profitMargins") * 100, 1) if g("profitMargins") else None,
|
|
"ROE_%": round(g("returnOnEquity") * 100, 1) if g("returnOnEquity") else None,
|
|
"croiss_CA_%": round(g("revenueGrowth") * 100, 1) if g("revenueGrowth") else None,
|
|
"dette/FP": g("debtToEquity"),
|
|
"div_%": round(g("dividendYield") * 100, 2) if g("dividendYield") else None,
|
|
"capi": _b(g("marketCap")),
|
|
"reco": g("recommendationKey"),
|
|
"cible_moy": g("targetMeanPrice"),
|
|
}
|
|
|
|
|
|
def compare(symbols: list[str]) -> pd.DataFrame:
|
|
rows = []
|
|
for s in symbols:
|
|
try:
|
|
rows.append(snapshot(s))
|
|
except Exception as e:
|
|
rows.append({"ticker": s, "nom": f"erreur: {e}"})
|
|
return pd.DataFrame(rows)
|
|
|
|
|
|
def compare_portfolio() -> pd.DataFrame:
|
|
return compare(data.portfolio_tickers())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
syms = sys.argv[1:]
|
|
df = compare(syms) if syms else compare_portfolio()
|
|
pd.set_option("display.max_columns", None, "display.width", 240)
|
|
print(df.to_string(index=False))
|