Files
All-in-one-Financial-Analysis/atlas-terminal/server/services/monte_carlo.py
T
shawnkim1997andClaude Opus 4.6 b2acda81ee 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>
2026-03-21 02:10:10 +00:00

125 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Monte Carlo simulation for DCF valuation.
Runs N random DCF scenarios by sampling WACC and FCF growth from
normal distributions, then reports distributional statistics.
"""
from typing import Dict, Any, List
import numpy as np
def run_monte_carlo_dcf(
fcf: float,
wacc_mean: float,
wacc_std: float,
growth_mean: float,
growth_std: float,
term_growth: float,
total_debt: float,
cash: float,
shares: float,
n_simulations: int = 5000,
current_price: float | None = None,
) -> Dict[str, Any]:
"""Run a Monte Carlo DCF simulation.
Parameters
----------
fcf : float
Base free cash flow.
wacc_mean / wacc_std : float
Mean and standard deviation for WACC sampling (decimal, e.g. 0.09).
growth_mean / growth_std : float
Mean and standard deviation for FCF growth sampling (decimal).
term_growth : float
Terminal growth rate (constant across simulations).
total_debt, cash, shares : float
Balance-sheet items for equity bridge.
n_simulations : int
Number of Monte Carlo iterations (default 5 000).
current_price : float | None
Current market price; used to compute prob_above_current.
Returns
-------
dict
values list of per-share intrinsic values (sorted)
percentile_10 10th percentile
median 50th percentile
percentile_90 90th percentile
mean arithmetic mean
prob_above_current probability the simulated value exceeds current_price
current_price echo back
n_simulations echo back
"""
if shares <= 0 or fcf <= 0:
return {
"values": [],
"percentile_10": None,
"median": None,
"percentile_90": None,
"mean": None,
"prob_above_current": None,
"current_price": current_price,
"n_simulations": n_simulations,
}
rng = np.random.default_rng()
# Sample WACC and growth; clip to sensible bounds
waccs = rng.normal(wacc_mean, max(wacc_std, 1e-6), n_simulations)
waccs = np.clip(waccs, 0.01, 0.40)
growths = rng.normal(growth_mean, max(growth_std, 1e-6), n_simulations)
growths = np.clip(growths, -0.30, 0.60)
projection_years = 10
values: List[float] = []
for w, g in zip(waccs, growths):
if w <= term_growth:
continue
# 10-year two-stage DCF (simplified: constant growth then terminal)
pv = 0.0
fcft = float(fcf)
for t in range(1, projection_years + 1):
fcft *= (1 + g)
pv += fcft / ((1 + w) ** t)
tv = fcft * (1 + term_growth) / (w - term_growth)
pv += tv / ((1 + w) ** projection_years)
equity = pv - total_debt + cash
per_share = equity / shares
if per_share > 0:
values.append(round(per_share, 2))
if not values:
return {
"values": [],
"percentile_10": None,
"median": None,
"percentile_90": None,
"mean": None,
"prob_above_current": None,
"current_price": current_price,
"n_simulations": n_simulations,
}
arr = np.array(values)
arr.sort()
prob_above = None
if current_price is not None and current_price > 0:
prob_above = round(float(np.mean(arr > current_price) * 100), 1)
return {
"values": arr.tolist(),
"percentile_10": round(float(np.percentile(arr, 10)), 2),
"median": round(float(np.median(arr)), 2),
"percentile_90": round(float(np.percentile(arr, 90)), 2),
"mean": round(float(np.mean(arr)), 2),
"prob_above_current": prob_above,
"current_price": current_price,
"n_simulations": n_simulations,
}