mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-15 19:38:07 +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>
117 lines
3.6 KiB
Python
117 lines
3.6 KiB
Python
"""Sensitivity analysis for DCF valuation.
|
||
|
||
Provides:
|
||
- WACC vs Terminal Growth sensitivity matrix
|
||
- Tornado chart data (variable impact ranking)
|
||
"""
|
||
|
||
from typing import Dict, List, Any
|
||
|
||
from server.services.dcf_engine import excel_style_dcf
|
||
|
||
|
||
def build_sensitivity_matrix(
|
||
fcf: float,
|
||
total_debt: float,
|
||
cash: float,
|
||
shares: float,
|
||
base_wacc: float,
|
||
base_tg: float,
|
||
fcf_growth: float,
|
||
wacc_steps: int = 6,
|
||
tg_steps: int = 5,
|
||
wacc_range: float = 0.02,
|
||
tg_range: float = 0.01,
|
||
) -> Dict[str, Any]:
|
||
"""Build a 2-D sensitivity matrix: WACC (rows) x Terminal Growth (cols).
|
||
|
||
Returns
|
||
-------
|
||
dict
|
||
wacc_values : list[float] – row headers (percentages, e.g. 8.0)
|
||
tg_values : list[float] – column headers (percentages, e.g. 2.5)
|
||
matrix : list[list[float | None]] – per-share intrinsic values
|
||
"""
|
||
# Generate evenly-spaced WACC and TG values centred on base
|
||
wacc_values = [
|
||
round(base_wacc - wacc_range + (2 * wacc_range / max(wacc_steps - 1, 1)) * i, 4)
|
||
for i in range(wacc_steps)
|
||
]
|
||
tg_values = [
|
||
round(base_tg - tg_range + (2 * tg_range / max(tg_steps - 1, 1)) * i, 4)
|
||
for i in range(tg_steps)
|
||
]
|
||
|
||
matrix: List[List[Any]] = []
|
||
for w in wacc_values:
|
||
row: List[Any] = []
|
||
for tg in tg_values:
|
||
if w <= tg or w <= 0 or shares <= 0:
|
||
row.append(None)
|
||
else:
|
||
result = excel_style_dcf(fcf, w, tg, fcf_growth, total_debt, cash, shares)
|
||
vps = result.get("value_per_share")
|
||
row.append(round(vps, 2) if vps is not None else None)
|
||
matrix.append(row)
|
||
|
||
return {
|
||
"wacc_values": [round(w * 100, 2) for w in wacc_values],
|
||
"tg_values": [round(tg * 100, 2) for tg in tg_values],
|
||
"matrix": matrix,
|
||
}
|
||
|
||
|
||
def build_tornado_data(
|
||
fcf: float,
|
||
wacc: float,
|
||
tg: float,
|
||
growth: float,
|
||
debt: float,
|
||
cash: float,
|
||
shares: float,
|
||
) -> List[Dict[str, Any]]:
|
||
"""Compute tornado-chart data by varying each input ±10 %.
|
||
|
||
Returns a list sorted descending by impact range (high − low).
|
||
Each entry: {"variable", "low", "high", "base"}.
|
||
"""
|
||
if shares <= 0:
|
||
return []
|
||
|
||
def _val(f, w, t, g, d, c) -> float | None:
|
||
if w <= t or w <= 0:
|
||
return None
|
||
r = excel_style_dcf(f, w, t, g, d, c, shares)
|
||
return r.get("value_per_share")
|
||
|
||
base_val = _val(fcf, wacc, tg, growth, debt, cash)
|
||
if base_val is None:
|
||
return []
|
||
|
||
variables = [
|
||
("WACC", lambda sign: _val(fcf, wacc * (1 + sign * 0.10), tg, growth, debt, cash)),
|
||
("FCF Growth", lambda sign: _val(fcf, wacc, tg, growth * (1 + sign * 0.10), debt, cash)),
|
||
("Terminal Growth", lambda sign: _val(fcf, wacc, tg * (1 + sign * 0.10), growth, debt, cash)),
|
||
("Base FCF", lambda sign: _val(fcf * (1 + sign * 0.10), wacc, tg, growth, debt, cash)),
|
||
("Total Debt", lambda sign: _val(fcf, wacc, tg, growth, debt * (1 + sign * 0.10), cash)),
|
||
("Cash", lambda sign: _val(fcf, wacc, tg, growth, debt, cash * (1 + sign * 0.10))),
|
||
]
|
||
|
||
results: List[Dict[str, Any]] = []
|
||
for name, func in variables:
|
||
val_up = func(0.10)
|
||
val_dn = func(-0.10)
|
||
if val_up is None or val_dn is None:
|
||
continue
|
||
low = round(min(val_up, val_dn), 2)
|
||
high = round(max(val_up, val_dn), 2)
|
||
results.append({
|
||
"variable": name,
|
||
"low": low,
|
||
"high": high,
|
||
"base": round(base_val, 2),
|
||
})
|
||
|
||
results.sort(key=lambda d: d["high"] - d["low"], reverse=True)
|
||
return results
|