mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-07-28 02:57:44 +00:00
b2acda81ee
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend. Frontend (Next.js 14): - 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings - Terminal Noir dark theme with custom Tailwind config - TradingView Lightweight Charts for candlestick/volume - Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF - Financial Statements table with YoY growth badges and margin rows - SEC EDGAR inline filing viewer with section tabs - News split-view with iframe article embedding - Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages - Earnings beat/miss visualization - AI Copilot chat panel with Gemini integration Backend (FastAPI): - 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx - Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators - yfinance + yahooquery data sources with fallback pattern - SQLite caching layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
2.6 KiB
Python
89 lines
2.6 KiB
Python
"""
|
|
Analyst estimates — earnings & revenue forecasts, consensus targets.
|
|
"""
|
|
import streamlit as st
|
|
import pandas as pd
|
|
|
|
try:
|
|
import yfinance as yf
|
|
except ImportError:
|
|
yf = None
|
|
|
|
|
|
@st.cache_data(ttl=600)
|
|
def get_analyst_estimates(ticker: str) -> dict:
|
|
"""Fetch analyst earnings and revenue estimates from yfinance."""
|
|
if not yf or not ticker:
|
|
return {}
|
|
try:
|
|
t = yf.Ticker(ticker)
|
|
result = {}
|
|
|
|
# Earnings estimates
|
|
ee = getattr(t, "earnings_estimate", None)
|
|
if ee is not None and not ee.empty:
|
|
result["earnings_estimate"] = ee
|
|
|
|
# Revenue estimates
|
|
re = getattr(t, "revenue_estimate", None)
|
|
if re is not None and not re.empty:
|
|
result["revenue_estimate"] = re
|
|
|
|
# EPS trend
|
|
et = getattr(t, "eps_trend", None)
|
|
if et is not None and not et.empty:
|
|
result["eps_trend"] = et
|
|
|
|
# Earnings history
|
|
eh = getattr(t, "earnings_history", None)
|
|
if eh is not None and not eh.empty:
|
|
result["earnings_history"] = eh
|
|
|
|
# Growth estimates
|
|
ge = getattr(t, "growth_estimates", None)
|
|
if ge is not None and not ge.empty:
|
|
result["growth_estimates"] = ge
|
|
|
|
# Price targets
|
|
info = t.info or {}
|
|
result["targets"] = {
|
|
"current": info.get("currentPrice") or info.get("regularMarketPrice"),
|
|
"mean": info.get("targetMeanPrice"),
|
|
"high": info.get("targetHighPrice"),
|
|
"low": info.get("targetLowPrice"),
|
|
"median": info.get("targetMedianPrice"),
|
|
"recommendation": info.get("recommendationKey", "N/A"),
|
|
"num_analysts": info.get("numberOfAnalystOpinions"),
|
|
}
|
|
|
|
return result
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
@st.cache_data(ttl=600)
|
|
def get_earnings_dates(ticker: str) -> pd.DataFrame:
|
|
"""Fetch historical and upcoming earnings dates with surprise data."""
|
|
if not yf or not ticker:
|
|
return pd.DataFrame()
|
|
try:
|
|
t = yf.Ticker(ticker)
|
|
dates = t.earnings_dates
|
|
if dates is not None and not dates.empty:
|
|
return dates.head(12)
|
|
except Exception:
|
|
pass
|
|
return pd.DataFrame()
|
|
|
|
|
|
def format_estimate_table(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Format estimate DataFrame for display with proper number formatting."""
|
|
if df is None or df.empty:
|
|
return pd.DataFrame()
|
|
display = df.copy()
|
|
for col in display.columns:
|
|
display[col] = display[col].apply(
|
|
lambda v: f"{v:,.2f}" if isinstance(v, (int, float)) and v == v else "N/A"
|
|
)
|
|
return display
|