Concepts
Temporal safety
A backtest is only honest if every row was knowable when the decision was made. The SDK enforces that in one place per surface: mr.align guards composed joins, and the temporal toolkit audits any frame that carries a knowledge_time.
The three primitives
Section titled “The three primitives”from mostlyright.core import KnowledgeView, TimePointfrom mostlyright.core.temporal import LeakageDetector, assert_no_leakageTimePoint: a UTC-aware timestamp wrapper (DST-proof, nanosecond-truncation aware). Cutoffs are TimePoints, not strings; passing a raw string raises aTypeErrorrather than guessing a timezone.KnowledgeView(df, as_of): the silent filter. Construction validates the frame (it requires aknowledge_timecolumn;SchemaValidationErrorotherwise), and.dataframe()returns a defensive copy of the rows whereknowledge_time <= as_of.assert_no_leakage/LeakageDetector: the loud audit. Same question, but raising with the offending rows named instead of filtering them out.
Executed example on real econ data:
from datetime import datefrom mostlyright import econfrom mostlyright.core import KnowledgeView, TimePoint
s = econ.series("cpi", date(2024, 1, 1), date(2025, 6, 1))view = KnowledgeView(s, TimePoint("2025-04-01T00:00:00Z"))len(s), len(view.dataframe())# (5, 3): the March and April vintages had not been published by the cutoffWhere the guard lives on composed frames
Section titled “Where the guard lives on composed frames”Frames built by weather.pairs() / mr.align do not carry knowledge_time per row. They carry the spine’s decision_time and label_available_time, because the guard already ran: align enforces source.knowledge_time <= spine.decision_time for every source at join time and raises a typed mr.LeakageError (naming the source id, the offending rows, both timestamps, and a fix) when violated.
So the division of labor is:
| Frame | Temporal columns | Guard |
|---|---|---|
| Source frames (econ vintages, forecast runs) | knowledge_time | KnowledgeView / assert_no_leakage |
Composed frames (pairs, align output) | decision_time, label_available_time | Enforced inside align |
What knowledge_time means
Section titled “What knowledge_time means”Not when a thing happened; when it became knowable. An observation’s knowledge time tracks its report. A forecast’s is its issuance. An econ number’s is its vintage date, and revisions are new rows with new knowledge times, which is why econ vintages never leak. Two timestamps for the same fact is the entire trick: event_time says what period a row describes, knowledge_time says when you could have known it.
The walk-forward discipline
Section titled “The walk-forward discipline”A leakage-safe frame does not make an evaluation honest by itself. The pattern that does:
- Split by time, never randomly. Train on a window, score strictly after it, roll forward.
- Recompute features inside each fold. A rolling mean computed once over the full history leaks the future into early folds even though every row is individually legitimate.
- Audit the fold, not the vibe. Run
assert_no_leakageon knowledge-time-bearing sources at each fold’s cutoff; for composed frames, thedecision_timecolumn is the fold boundary to respect. - Group when you generalize. Cross-station claims need station-grouped folds; the quickstart’s training-table section shows the trap and the fix with real numbers.
Rolling and derived features remain deliberately user-land (pandas/polars own them; the SDK’s transforms helpers exited with 1.18, see the upgrade guide): the SDK guards joins and audits knowledge, and refuses to pretend a DSL could guard your feature engineering for you.
See also
Section titled “See also”- Sources & provenance: the other reproducibility half
- Econ: vintages as first-class knowledge times
- Compose a dataset: where
decision_timecomes from - API surface: domains and signatures