The read itself, and why fit and apply are different types
The as-of join is the whole discipline, and it is easy to get subtly wrong
In the study this package was extracted from, one join de-duplicated on the event key alone and broadcast a single formation day’s signal across the whole holding window. 580 of 586 daily returns changed. Every unit test still passed. Only a comparison against the original scripts caught it.
That is why there is one shared anchoring primitive here instead of careful joins in ten places, and why a method never sees the raw panel.
An event’s own timestamp is the common reading moment, and a value knowable at that instant is usually the value under study. Including it by default is how a backtest quietly reads its own answer. The inclusive case exists, and it is opt-in and named.
Maximum age is part of the join, not a later filter
A forecast nobody has revised in a year is not a current view. Carrying a frozen value forward turns silence into a signal.
The caps are research decisions, so they are arguments, never constants buried in the library.
Staleness has to survive the read
Note what just happened above: the stale model disappeared. That is the right answer when you are choosing a value, and the wrong answer when a method has to report why a subject is unpublishable — the row vanishes, and “stale prediction” collapses into “missing input”.
So the pipeline that feeds a method anchors without a cap and measures age instead:
Age arrives as a value. A feed consumer can act on the difference: a stale model means fix the model pipeline, a missing one means the company is not covered.
fit learns, apply does not
Look-ahead arrives through convenience
The legacy pre-report analysis winsorised the full sample before splitting it, so a test-period outlier moved a training-period bound. Nobody intended it. It was one line in the wrong order.
Hence two types. fit takes the training rows and returns a frozen object. apply takes any rows and cannot refit. A look-ahead has to be written deliberately.
True, and it has to stay True. Every fitted method carries a test that proves it: fitting on the training slice alone gives the same answer as fitting on everything. A method that peeks fails it.
The anchored estimate is a frozen-parameter method — its coefficients were fitted offline and shipped with a version — so fit verifies the panel and learns nothing at all. That is the strongest version of the same guarantee.
Degenerate input
Each of these crashed the study before it was fixed at the root, and each is a test here now.
Show the code
empty = pfc.latest_before(estimates.iloc[:0], AS_OF)print("empty panel comes back typed and empty:", empty.shape, list(empty.columns))zero = subjects.assign(consensus=0.0, consensus_revision=0.0)refused = pfc.AnchoredEstimate(forecast_source="model").fit(zero).apply(zero)print("a zero denominator yields a refusal, never an infinity:", refused[pfc.ELIGIBILITY_REASON_COLUMN].tolist(),)
empty panel comes back typed and empty: (0, 7) ['entity', 'period', 'target', 'period_end', 'source', 'value', 'known_at']
a zero denominator yields a refusal, never an infinity: ['missing_input']
The rules, in one list
Strictly before. The inclusive read is opt-in and named.
Maximum age is part of the join when you are choosing a value, and a value when a method has to explain itself.
First reported, never restated. An actual revised since the event is not what the market saw.
Anchor before reading. A method sees the anchored frame, never the raw panel, and the reading moment is always explicit.
fit learns, apply does not, and there is a test for it.
Publish nothing rather than a fallback. A stale input and a real number must never become indistinguishable downstream.