| --- |
| 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](https://github.com/KiUngSong/TerraFin/blob/main/src/TerraFin/analytics/analysis/README.md) |
| |