mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-24 07:58:05 +00:00
feat: add Atlas Terminal — Next.js 14 + FastAPI full-stack migration
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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
56a9561f71
commit
b2acda81ee
@@ -0,0 +1,116 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user