Analytics Notes¶
Package paths on this page drop the leading src/TerraFin/: data/ means src/TerraFin/data/.
These notes mirror the implementation guidance kept near the analytics code so maintainers can reach it from the formal docs site as well.
DCF¶
DCF code lives under analytics/analysis/fundamental/dcf/.
| Model | What it is trying to answer | Main checked-in data |
|---|---|---|
| S&P 500 DCF | A year-end target framework | analytics/analysis/fundamental/dcf/sp500_defaults.json |
| Stock DCF | A spot equity valuation framework | derived from live company inputs plus explicit defaults |
S&P 500 DCF¶
The index model is a year-end target model, not a spot fair-value model.
Refresh sp500_defaults.json in this order:
- set a defensible public strategist target range
- refresh the nominal base-year EPS context
- refresh the explicit growth fade schedule
- refresh payout and buyback assumptions
- refresh ERP and terminal discounting assumptions
- verify the resulting target still lands in a reasonable public range
Stock DCF¶
The stock model needs one starting growth assumption (Base Growth %) and one
starting cash-flow level (Base FCF / Share). Both can be overridden; both have
fallback cascades when blank.
Current fallback order for Base Growth % in build_stock_template():
- user override
- EPS growth from
forwardEpsversustrailingEps - annual revenue CAGR
- annual FCF CAGR
- default
6%
Why revenue comes before FCF CAGR:
- revenue is usually more stable
- FCF is more sensitive to capex timing and working-capital noise
- revenue is often the better default growth anchor when explicit EPS guidance is missing
Base FCF source cascade¶
Base FCF / Share is selected by _select_stock_fcf_base() in
analytics/analysis/fundamental/dcf/inputs.py. Four sources, with
explicit picks via the fcf_base_source override on StockDCFOverrides:
| Source | Helper | Notes |
|---|---|---|
auto (default) |
cascade | 3yr_avg → annual → ttm → missing |
3yr_avg |
_three_year_avg_fcf |
Mean of last 3 valid annual FCF/share rows. Returns None if fewer than 2 valid years. |
latest_annual |
_latest_annual_fcf |
First non-NaN annual FCF/share row. |
ttm |
_quarterly_ttm_fcf |
Sum of last 4 quarterly FCF rows; None if fewer than 4 valid quarters. |
The auto cascade prefers normalized over recent because DCF capitalizes the
base into perpetuity — single-period TTM can be distorted by working-capital
swings or one-off capex. McKinsey Valuation explicitly recommends a
multi-year normalized FCF as the DCF base.
Explicit latest_annual/ttm/3yr_avg picks do not fall back when the
chosen source has no data: the call returns (None, "missing") so the UI can
surface an accurate insufficient-data message instead of silently using a
different basis.
A user-supplied base_cash_flow_per_share override always wins over the source
cascade.
Projection horizon¶
projection_years (5 / 10 / 15) controls the explicit forecast length. The
treasury curve is sampled accordingly (yield_at(1) … yield_at(N)); the
terminal discount rate uses the 30-year long-term rate regardless of horizon.
Turnaround mode¶
When all three turnaround fields are supplied — breakeven_year,
breakeven_cash_flow_per_share, post_breakeven_growth_pct — the template
builds an explicit per-year FCF schedule via _build_turnaround_schedule()
instead of the single-base × growth-curve path. Schedule shape:
- Years 1 … breakeven_year: linear interpolation from the current TTM
FCF/share (which may be negative — that's the whole point) to
breakeven_cash_flow_per_shareat yearbreakeven_year. - Years breakeven_year+1 … horizon: compound at
post_breakeven_growth_pctwith a linear fade towardterminal_growth_pctacross the remaining years.
Negative cash flows in the early years are reflected honestly in the DCF: each
year's present value is summed, so cash-burn periods reduce intrinsic value.
Status flips to ready when the year-N (horizon) FCF is positive; otherwise
the template stays at insufficient_data with an explanatory warning.
Bear/base/bull scenario shifts in turnaround mode apply a cumulative
year-over-year compounding bump (growth_shift_pct per year) so the three
scenarios diverge meaningfully even on a user-supplied schedule.
Related defaults:
Base FCF / Share: source cascade (see above)Beta: provider beta first, then TerraFin's computed beta, then1.0Equity Risk Premium %: default5.0%Terminal Growth %: default3.0%Projection Years: default5FCF Base Source: defaultauto
Override the automatic growth input when:
- the business is in a regime shift
- provider EPS is stale
- revenue CAGR is acquisition-driven
- FCF is distorted by one-off capex or working-capital swings
- the company is a financial firm where FCF-style DCF is not a good framing
Use turnaround mode (instead of overrides) when:
- current FCF is negative but the investment thesis is on a future turn
- you need the schedule's losses to count against intrinsic value rather than being silently clipped to zero
- you can name a defensible breakeven year (typically 1–5 for operational turnarounds; longer is usually better modeled as a different framing)
Beta¶
Risk code lives under analytics/analysis/risk/.
Current built-in methods:
beta_5y_monthlybeta_5y_monthly_adjusted
Default Method¶
beta_5y_monthly is the reference method:
- lookback: 5 years
- frequency: month-end closes
- formula:
Cov(stock_returns, benchmark_returns) / Var(benchmark_returns)
beta_5y_monthly_adjusted is the stability variant:
- start from
beta_5y_monthly - shrink toward
1.0 - formula:
0.67 * beta_5y_monthly + 0.33 * 1.0
Benchmark Mapping¶
| Market | Benchmark |
|---|---|
| U.S. / default | ^SPX |
Korea .KS |
^KS11 |
Korea .KQ |
^KQ11 |
Japan .T |
^N225 |
If TerraFin cannot map a ticker confidently, it returns
unsupported_benchmark instead of forcing a proxy.
Code-Adjacent Source¶
The full code-adjacent source remains on GitHub: