TerraFin / docs /analytics-notes.md
sk851's picture
docs: analytics/interface/api-reference touch-ups + mkdocs nav
a746412
metadata
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:

  1. set a defensible public strategist target range
  2. refresh the nominal base-year EPS context
  3. refresh the explicit growth fade schedule
  4. refresh payout and buyback assumptions
  5. refresh ERP and terminal discounting assumptions
  6. 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():

  1. user override
  2. EPS growth from forwardEps versus trailingEps
  3. annual revenue CAGR
  4. annual FCF CAGR
  5. 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_share at year breakeven_year.
  • Years breakeven_year+1 … horizon: compound at post_breakeven_growth_pct with a linear fade toward terminal_growth_pct across 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, then 1.0
  • Equity Risk Premium %: default 5.0%
  • Terminal Growth %: default 3.0%
  • Projection Years: default 5
  • FCF Base Source: default auto

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_monthly
  • beta_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:

src/TerraFin/analytics/analysis/README.md