title: Analytics Notes
summary: >-
DCF and beta implementation notes that sit close to the analytics code but
deserve a stable docs entrypoint.
Analytics Notes
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 src/TerraFin/analytics/analysis/fundamental/dcf/.
| Model | What it is trying to answer | Main checked-in data |
|---|---|---|
| S&P 500 DCF | A year-end target framework | 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
src/TerraFin/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 src/TerraFin/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: