Three noisy readings of one unknown, weighted by how well each has done
The anchored estimate treats consensus as the anchor and moves a fixed fraction of the way toward the model. This method treats all three readings symmetrically and lets each one’s own track record decide its weight.
The setup
One unknown: \(\theta\), the company’s revenue growth for the coming quarter. Three noisy readings of it, each with its own variance \(\sigma_j^2\) and precision \(\tau_j = 1/\sigma_j^2\). Symbols follow the notation page.
Reading
Symbol
Precision
Where its variance comes from
Prior
\(\mu_0\)
\(\tau_0\)
the company’s own realised growth history
Model
\(x_M\)
\(\tau_M\)
the model’s error against realised growth
Consensus
\(x_C\)
\(\tau_C\)
consensus’s error, or analyst dispersion
The normal–normal update
Assume each reading is an independent Gaussian observation of \(\theta\). The posterior is proportional to prior times likelihoods, and collecting the exponent as a quadratic in \(\theta\) gives a Gaussian again (Gelman et al. 2013). The posterior growth \(\hat\theta\) is the precision-weighted mean, and its variance \(\sigma_\theta^2\) is one over the total precision \(T\):
Precisions add. Every additional independent source, however weak, reduces computed posterior variance under these assumptions. Predictive accuracy can still worsen when errors are correlated or variances are misspecified.
Weights are precision shares. The weight on each reading is \(w_j = \tau_j / T\)(Bates and Granger 1969), so the three weights sum to one by construction and none can be negative.
Growth space, not level space
The method works on growth over the previous actual \(y_{t-1}\), not on revenue levels: the model reading is \(x_M = m/y_{t-1} - 1\) and the consensus reading \(x_C = c/y_{t-1} - 1\). Two reasons:
Scale. Revenue levels are non-stationary. Sequential growth is far closer to stationary, which is what makes a variance estimated over history meaningful at all.
The prior needs a sample. A prior on “this company’s growth” can be estimated from its own history. A prior on “this company’s revenue in dollars” cannot, without a trend model.
The inputs, measured point-in-time
history supplies the statistics, each read strictly before the moment being forecast.
The error record per source is the same idea: for each past quarter, what that source said before the selected actual became known, against that actual. Preparation uses the latest actual known at the cutoff; select first releases upstream if required.
A source’s estimate for a past quarter has to be the one that stood before that quarter’s selected actual became knowable. First-release behavior requires selecting first actuals before preparation. On a panel that read has to be anchored, once per quarter. A test asserts that a revision published after the actual is never read.
The prior carries most of the weight here, because eight quarters of steady growth are a much sharper reading than either estimate’s error record. That is the method working as intended, not a bug: a company whose growth is stable is mostly predicted by its own history.
\(\nu = 1.5\) degrees of freedom is far below the Gaussian limit, \(\nu\to\infty\). The posterior variance formula would give a Gaussian band, and a Gaussian band on financial forecast errors is famously too narrow (Mandelbrot 1963).
Figure 2: A Student-t with 1.5 degrees of freedom against the normal, both scaled to unit interquartile range. A 90% band from the t is much wider in the tails.
Degrading gracefully, and refusing outright
The two are different, and the distinction is the interesting part of the port.
A model whose record is too short to estimate a precision drops out of the blend and the subject is still published on prior and consensus, with an interval. Its model precision and weight remain missing. A model whose value is missing refuses the subject outright: there is nothing to weigh.
pd.DataFrame([{"reason": reason.value} for reason in PosteriorReason])
reason
0
publishable
1
missing_input
2
insufficient_history
3
no_consensus_precision
4
missing_prediction
5
nonfinite_result
Neither branch is a fallback in the forbidden sense — no unpublishable subject ever gets a substituted number. Both are declared method behaviour, and both carry tests.
This configuration:BAYESIAN_DEFAULTS preserves the source signal’s chosen compatibility settings, with fitted_through=None.
Research presets: Other configurations carry fitted interval shapes and support a fitted-through date. The research cutoff is currently unknown; see training provenance.
Audit: Retain the complete settings, not just their version. A null training date does not establish that a historical evaluation is out of sample.
For comparison, also set the anchored intercept and revision contribution to zero. The simplified anchored level is \(\hat y = c + b_\Delta (m - c)\). Only under these simplifications do the two share a functional form, with
Same functional form, different estimand. The regression estimates that ratio from the joint distribution of gap and surprise, across companies. The Bayesian computes it from each source’s own error history, per company.
Show the code
ratio = np.logspace(-1, 1.6, 400)curve = pd.DataFrame({"ratio": ratio, "weight": 1/ (1+ ratio)})figure, axis = plt.subplots(figsize=(7.5, 3.8))sns.lineplot(data=curve, x="ratio", y="weight", ax=axis, color=examples.ACCENT)fitted = pf.ANCHORING_2025_01.gap_weightaxis.scatter([(1- fitted) / fitted], [fitted], s=60, color=examples.WARM, zorder=3)axis.annotate(f"the anchored estimate's fitted\ngap weight, {fitted}", ((1- fitted) / fitted, fitted), xytext=(12, 22), textcoords="offset points", fontsize=8.5, color=examples.WARM,)axis.set_xscale("log")axis.set(xlabel=r"$\sigma_M^2/\sigma_C^2$, log scale", ylabel="weight on the model")axis.set_title("Two routes to the same functional form")figure.tight_layout()
Figure 3: The weight on the model as a function of the error-variance ratio. The anchored estimate’s fitted gap weight of 0.129 is one point on this curve.
A measured median error of 2.99% for the model against 1.54% for consensus implies a weight near 0.21 if the two errors were independent. The regression says 0.129. Error correlation can contribute to this difference: both track the same company, so their errors move together, and a naive precision weighting over-weights the model (Clemen and Winkler 1985). The independent variant is the default anyway, because covariance estimates are materially less stable than scalar variances when two sources are strongly collinear (Smith and Wallis 2009).
What to take away
Precisions add, weights are precision shares. Both facts fall out of the algebra, and both are asserted per subject in the tests.
Growth space is what makes a variance meaningful. Levels are non-stationary.
Point-in-time is harder here than for the anchored estimate. Every error in the record has to be measured against what a source said before the actual landed.
Degrading and refusing are different. A short record drops a source; a missing value refuses the subject.
Settings carry a version and optional training date. Fitted calibration needs its own provenance, independently of the per-company history.
References
Bates, J. M., and C. W. J. Granger. 1969. “The Combination of Forecasts.”Operational Research Quarterly 20 (4): 451–68. https://doi.org/10.2307/3008764.
Clemen, Robert T., and Robert L. Winkler. 1985. “Limits for the Precision and Value of Information from Dependent Sources.”Operations Research 33 (2): 427–42. https://doi.org/10.1287/opre.33.2.427.
Gelman, Andrew, John B. Carlin, Hal S. Stern, David B. Dunson, Aki Vehtari, and Donald B. Rubin. 2013. Bayesian Data Analysis. 3rd ed. Chapman; Hall/CRC. https://doi.org/10.1201/b16018.
Mandelbrot, Benoit. 1963. “The Variation of Certain Speculative Prices.”The Journal of Business 36 (4): 394–419. https://doi.org/10.1086/294632.
Smith, Jeremy, and Kenneth F. Wallis. 2009. “A Simple Explanation of the Forecast Combination Puzzle.”Oxford Bulletin of Economics and Statistics 71 (3): 331–55. https://doi.org/10.1111/j.1468-0084.2008.00541.x.