AXIOM: Honest Equity Valuation Engine
A browser-based equity valuation tool. Type a US ticker, AXIOM routes the company through one of 9 archetype frameworks (DCF for mature businesses, P/B for banks, P/FFO for REITs), runs the math, and shows the fair value alongside multiples-as-context, never blended in. Originally shipped with ML correction multipliers stacked on the headline; tore it down when I realised the displayed fair value didn't match the underlying DCF. Rebuilt around one rule: the headline number has to mean what it says.
See it in action
Live ticker, circular gauge, portfolio stats, the state the app sits in.
Walkthrough
Three short clips. Watch them in order and you’ve seen the whole product.
01 · Type a ticker, get a fair value
AXIOM fair value, Wall Street consensus, and live price, side by side, never blended.
02 · A clean 10-year DCF
Change one assumption, the headline recalculates live. Same math, no fudge factors.
03 · Multiples as labelled context
Drag the multiple slider, comp moves, DCF headline stays put. The two views never blend.
Pipeline Architecture
Fundamentals from Yahoo Finance · archetype router selects the formula (DCF / P/B / P/FFO depending on business type) · clean 10-year DCF produces the headline · multiples sit beside it as context · factor model in research mode tracks IC and t-stat walk-forward · output is a fair value plus a bear/base/bull range.
What’s actually happening at each stage
Each stage is explained twice, first for the finance reader, then for the engineer.
1. The Pivot: Tearing Down the Dishonest Version
Finance lens
Axiom shipped first as a typical retail valuation tool: pull a company's financials, run a DCF, blend it with EV/EBITDA and P/E multiples, layer an ML "correction multiplier" on top, and call the output "DCF Fair Value." It wasn't. The displayed fair value could read $531 while the underlying DCF was $380, the gap was ML fudging and silent multiple expansion, and the sensitivity table inside the same app contradicted the headline. So I tore it down and rebuilt around one rule: the number has to mean what it says.
Engineering lens
The blending pipeline, the ML correction multiplier applied to fair value, and the headline-vs-internals inconsistency were all removed in one pass. The DCF became a single deterministic Python function with an audited input contract; comparables were pulled out of the headline number entirely and re-rendered as labelled side context. A HARDCODED_VALUES.md file was added inventorying every remaining hand-set constant in the codebase, with a phased plan to migrate them to live peer-comp estimates, so what's assumed is auditable, and what's computed is real.
2. Clean 10-Year DCF with a Credit-Spread WACC
Finance lens
The core is a 10-year Discounted Cash Flow, the same instrument a banker builds in Excel for an IPO pitch, but coded as a deterministic pipeline. Free cash flow is projected with a three-stage growth fade (current → industry → terminal), discounted at a WACC built from CAPM equity costs and a synthetic credit-rating spread (Damodaran-style interest-coverage-to-rating mapping, so highly-covered firms automatically get cheaper debt, no hand-typed numbers in the spread table). Net debt comes off enterprise value, divide by shares, done.
Engineering lens
Terminal growth is capped strictly below WACC so Gordon Growth can't blow up. The same DCF math powers both the headline number and the sensitivity table, every figure rendered in the UI is internally consistent. One formula, no blending, no ML on the output.
3. 9-Archetype Router + 54-Tag Sub-Sector Table
Finance lens
A vanilla DCF works for Microsoft and dies on Tesla, banks, REITs, distressed names, hyper-growth, and "story stocks" don't share a single valuation language. Axiom routes every ticker through one of 9 archetype frameworks (FINANCIAL, GROWTH, MATURE, CYCLICAL, HIGH_CAPEX, HYPER_GROWTH, TURNAROUND, DISTRESSED, STABLE_GROWTH) which decides the formula: banks get P/B, REITs get P/FFO, mature operators get a clean DCF. On top of that, a 54-tag sub-sector table calibrates the multiples shown alongside.
Engineering lens
Archetype routing maps revenue profile, margin shape, leverage, and sector tags at request time. Each archetype has its own DCF inputs (terminal growth, fade schedule, reinvestment assumptions); financials and REITs route to entirely separate model functions. The 54-tag multiplier table lives in subsector_multiples.json and acts as a Bayesian prior on the comp multiples, not the answer, the starting point.
4. V/M/Q Factor Model: In Research Mode
Finance lens
The original ML layer learned to patch its own past errors, a circular feedback loop that doesn't actually predict the cross-section of returns. The rebuilt version works the way a real quant shop would: it scores every company on three independent factors, Value (DCF upside vs. market), Momentum (Jegadeesh-Titman 12-1 month sector-relative return), and Quality (FCF yield), Z-scored within sub-sector so Google is compared to internet peers, not oil majors. Crucially: this signal is not yet shown to users. It's being measured first.
Engineering lens
Information Coefficient + quintile hit rates are computed walk-forward in ml/monitor.py, the metrics quant funds actually use to evaluate predictive signals, not MAE. A "✓ SKILL SIGNAL" annotation appears in the monitoring output once t-stat ≥ 2 holds across enough OOS periods. The factor model exists in the codebase but is intentionally absent from the user-facing UI until it earns its place there. The model is allowed to say "I don't have a signal yet."
5. Honest by Default: Including the Limits
Finance lens
Layperson reading: the app gives "is this stock cheap or expensive" with a number, a colour, and a comparison to Wall Street. Finance reading: a fully-instrumented DCF + multi-factor research pipeline. Same product, two reading levels. The demo site has a dedicated "What this isn't (yet)" section that names the limitations: yfinance can be stale, several constants are still hand-set, the factor model isn't live, US-only coverage. Owning the gaps is part of the product.
Engineering lens
Architecturally a Flask + Postgres app with a vanilla-JS frontend, yfinance as the data spine, and an XGBoost-style walk-forward evaluation harness behind the scenes. Deployable on a single container. Every hand-set constant is named in HARDCODED_VALUES.md. Yahoo balance-sheet ratios known to be unreliable (ROE, ROIC, D/E, Altman Z) are currently hidden from the UI rather than shown wrong, visible-honest beats invisible-broken.
Methodology notes
Clean 10-year DCF as one deterministic Python function: three-stage growth fade, CAPM equity cost, credit-spread debt cost, terminal growth strictly below WACC. The same math drives the headline fair value and the sensitivity table, no separate "display" number.
9-archetype router selects the framework (DCF for mature, P/B for banks, P/FFO for REITs, separate inputs for hyper-growth and distressed). On top, a 54-tag sub-sector table calibrates comp multiples as a Bayesian prior, not as the answer.
Factor model (Value / Momentum / Quality) is wired up but lives in research mode: IC, std, t-stat, and quintile hit rates are computed walk-forward in ml/monitor.py. The user-facing recommendation pill is driven by the DCF output, not the factor model, until the model demonstrates skill OOS.
HARDCODED_VALUES.md inventories every hand-set constant with a phased plan (P2 → P6) to replace each with live peer-comp computation, ML correction-layer changes, scenario parameters, or panel-regression-derived values. What's assumed is auditable; what's computed is real.
What this isn’t (yet)
The honest limits. A page called “honest” with no limitations would be a credibility own-goal.
Data quality
Fundamentals come from yfinance, free, but rate-limited and occasionally stale. Several Yahoo balance-sheet ratios (ROE, ROIC, D/E, Altman Z) return on a wrong scale and are currently hidden from the UI rather than displayed inaccurately.
Hardcoded constants
Sub-sector multiples and a handful of WACC inputs are still hand-set. Every one is named in HARDCODED_VALUES.md alongside the phase plan to replace it with live peer-comp computation.
Factor model status
The V/M/Q signal exists in the codebase. It is not yet a user-facing recommendation. IC and t-stat tracking run walk-forward in ml/monitor.py; the model is held back from the UI until it clears the gate.
Coverage
US-listed equities only. International tickers, ADRs without US listings, and derivatives are out of scope. Banks and REITs use simpler P/B and P/FFO models, no full DCF for those archetypes.
Not investment advice
The fair value depends entirely on the assumptions. Change one input and the headline changes. AXIOM is a tool for thinking through valuation, not a recommendation engine.