mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-23 15:48:05 +00:00
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
3.4 KiB
Python
89 lines
3.4 KiB
Python
"""Earnings router -- earnings history, upcoming dates, and transcripts."""
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _safe_float(val, default=None):
|
|
if val is None:
|
|
return default
|
|
try:
|
|
import math
|
|
f = float(val)
|
|
return default if math.isnan(f) or math.isinf(f) else f
|
|
except (TypeError, ValueError):
|
|
return default
|
|
|
|
|
|
@router.get("/{ticker}/history", summary="Earnings history (EPS actual vs estimate)")
|
|
async def earnings_history(ticker: str) -> Dict[str, Any]:
|
|
try:
|
|
import yfinance as yf
|
|
t = yf.Ticker(ticker.upper())
|
|
earnings = t.earnings_history
|
|
if earnings is None or (hasattr(earnings, 'empty') and earnings.empty):
|
|
return {"ticker": ticker.upper(), "history": []}
|
|
|
|
history: List[Dict[str, Any]] = []
|
|
if hasattr(earnings, 'iterrows'):
|
|
for idx, row in earnings.iterrows():
|
|
history.append({
|
|
"date": str(idx)[:10],
|
|
"eps_actual": _safe_float(row.get("epsActual", row.get("Reported EPS"))),
|
|
"eps_estimate": _safe_float(row.get("epsEstimate", row.get("EPS Estimate"))),
|
|
"surprise": round(_safe_float(row.get("surprisePercent", row.get("Surprise(%)")), 0) * 100, 2),
|
|
})
|
|
return {"ticker": ticker.upper(), "history": history[-12:]}
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Earnings history failed: {exc}") from exc
|
|
|
|
|
|
@router.get("/{ticker}/calendar", summary="Upcoming earnings date")
|
|
async def earnings_calendar(ticker: str) -> Dict[str, Any]:
|
|
try:
|
|
import yfinance as yf
|
|
t = yf.Ticker(ticker.upper())
|
|
cal = t.calendar
|
|
if cal is None:
|
|
return {"ticker": ticker.upper(), "next_earnings": None}
|
|
|
|
if isinstance(cal, dict):
|
|
earnings_date = cal.get("Earnings Date")
|
|
if isinstance(earnings_date, list) and earnings_date:
|
|
earnings_date = str(earnings_date[0])[:10]
|
|
elif earnings_date:
|
|
earnings_date = str(earnings_date)[:10]
|
|
return {
|
|
"ticker": ticker.upper(),
|
|
"next_earnings": earnings_date,
|
|
"revenue_estimate": _safe_float(cal.get("Revenue Average")),
|
|
"eps_estimate": _safe_float(cal.get("Earnings Average")),
|
|
}
|
|
return {"ticker": ticker.upper(), "next_earnings": None}
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Earnings calendar failed: {exc}") from exc
|
|
|
|
|
|
@router.get("/{ticker}/quarterly", summary="Quarterly earnings data")
|
|
async def quarterly_earnings(ticker: str) -> Dict[str, Any]:
|
|
try:
|
|
import yfinance as yf
|
|
t = yf.Ticker(ticker.upper())
|
|
|
|
quarterly = t.quarterly_earnings
|
|
data: List[Dict[str, Any]] = []
|
|
if quarterly is not None and hasattr(quarterly, 'iterrows'):
|
|
for idx, row in quarterly.iterrows():
|
|
data.append({
|
|
"period": str(idx),
|
|
"revenue": _safe_float(row.get("Revenue")),
|
|
"earnings": _safe_float(row.get("Earnings")),
|
|
})
|
|
|
|
return {"ticker": ticker.upper(), "quarterly": data}
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Quarterly earnings failed: {exc}") from exc
|