mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-07-28 02:57:44 +00:00
d337c63976
Architecture (3,909-line monolith → 28 focused modules, all < 300 lines):
- config/: constants.py (company lists, row maps, Damodaran baselines), theme.py (CSS/HTML)
- utils/: prefs, formatting, ticker, dcf, charts, ui_helpers
- data/: sec_parser, sec_fetcher, sec_downloader, financials, fundamentals,
valuation, ratios, scores, scores_ai, market
- ai/: gemini_core, gemini_sec, gemini_insights
- views/: sidebar, tab1_quant, tab1_ai, tab1_filings, tab2_dcf,
tab3_comps, tab4_news, tab5_markets, tab6_crypto, tab7_technical
- app.py: thin orchestrator (~118 lines)
- Strict unidirectional dependency graph (no circular imports)
- All @st.cache_data TTLs and st.session_state keys preserved identically
SEC filing viewer fix:
- Rebuilt EDGAR fetch chain: company_tickers.json → CIK → submissions API
→ filings.recent.primaryDocument[] (replaces deprecated directory.item)
- Filing type selectbox (10-K, 10-Q, 8-K, 20-F, 6-K) connected to backend
- Native HTML rendered via streamlit.components.v1.html() with CSS reset
- Errors surfaced explicitly with st.error()
- DART direct links restored for Korean-listed companies
.gitignore: data/ → data/*.json + data/*.html (preserve Python modules)
README: full rewrite for master's portfolio — 7-tab layout, architecture
diagram, modular structure tree, technical challenges, design rationale
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
"""
|
|
DCF valuation models and Damodaran WACC mapping.
|
|
"""
|
|
from config.constants import DAMODARAN_WACC
|
|
|
|
|
|
def dcf_intrinsic_value(fcf: float, wacc: float, terminal_growth: float, fcf_growth: float, years: int = 5) -> float:
|
|
"""5-year DCF: project FCF with fcf_growth, then terminal value; discount at WACC. Returns enterprise value. Robust: avoids div by zero."""
|
|
if fcf is None or fcf <= 0:
|
|
return 0.0
|
|
if wacc <= terminal_growth or wacc <= 0:
|
|
return 0.0
|
|
pv = 0.0
|
|
fcft = float(fcf)
|
|
for t in range(1, years + 1):
|
|
pv += fcft / ((1 + wacc) ** t)
|
|
fcft *= (1 + fcf_growth)
|
|
terminal_fcf = fcft
|
|
tv = terminal_fcf * (1 + terminal_growth) / (wacc - terminal_growth)
|
|
pv += tv / ((1 + wacc) ** years)
|
|
return pv
|
|
|
|
|
|
def dcf_10y_2stage(fcf: float, wacc: float, term_growth: float, fcf_growth: float) -> float:
|
|
"""10-Year 2-Stage DCF. Stage 1 (Y1-5): FCF grows at fcf_growth. Stage 2 (Y6-10): growth linearly fades from fcf_growth to term_growth by Y10. TV at Y10; discount all to PV."""
|
|
if fcf is None or fcf <= 0:
|
|
return 0.0
|
|
if wacc <= term_growth or wacc <= 0:
|
|
return 0.0
|
|
pv = 0.0
|
|
fcft = float(fcf)
|
|
for t in range(1, 6):
|
|
pv += fcft / ((1 + wacc) ** t)
|
|
fcft *= (1 + fcf_growth)
|
|
for t in range(6, 11):
|
|
fade = (t - 6) / 4.0
|
|
g_t = fcf_growth + fade * (term_growth - fcf_growth)
|
|
fcft *= (1 + g_t)
|
|
pv += fcft / ((1 + wacc) ** t)
|
|
tv = fcft * (1 + term_growth) / (wacc - term_growth)
|
|
pv += tv / ((1 + wacc) ** 10)
|
|
return pv
|
|
|
|
|
|
def excel_style_dcf(fcf_base: float, wacc: float, term_growth: float, fcf_growth: float, total_debt: float, cash: float, shares: float) -> dict:
|
|
"""10Y 2-Stage DCF: EV = PV(FCF Y1-10) + PV(TV); Equity = EV - Debt + Cash; Value per share = Equity / Shares."""
|
|
ev = dcf_10y_2stage(fcf_base, wacc, term_growth, fcf_growth)
|
|
equity = ev - total_debt + cash
|
|
shares_safe = float(shares) if (shares is not None and float(shares) > 0) else None
|
|
value_per_share = (equity / shares_safe) if shares_safe else None
|
|
return {"ev": ev, "equity_value": equity, "value_per_share": value_per_share, "shares": shares_safe}
|
|
|
|
|
|
def _damodaran_wacc_for_sector(sector: str) -> float:
|
|
"""Map yfinance sector string to closest Damodaran WACC. Default 8.0%."""
|
|
if not sector:
|
|
return 8.0
|
|
s = (sector or "").lower()
|
|
if "software" in s or "technology" in s or "internet" in s:
|
|
return DAMODARAN_WACC.get("Software", 8.5)
|
|
if "hardware" in s or "semiconductor" in s:
|
|
return DAMODARAN_WACC.get("Hardware", 9.0)
|
|
if "retail" in s or "consumer" in s or "cyclical" in s:
|
|
return DAMODARAN_WACC.get("Retail", 7.5)
|
|
if "financial" in s or "bank" in s or "insurance" in s:
|
|
return DAMODARAN_WACC.get("Financials", 8.0)
|
|
if "health" in s or "pharma" in s:
|
|
return DAMODARAN_WACC.get("Healthcare", 7.2)
|
|
if "industrial" in s:
|
|
return DAMODARAN_WACC.get("Industrial", 7.8)
|
|
if "energy" in s or "oil" in s:
|
|
return DAMODARAN_WACC.get("Energy", 8.2)
|
|
if "utilities" in s:
|
|
return DAMODARAN_WACC.get("Utilities", 6.5)
|
|
return 8.0
|