State models are attractive because they turn a noisy sequence into an interpretable evolving belief. They are dangerous for exactly the same reason: a smooth line or decisive regime label can look like fact even when it is only the output of a fragile configuration. This tutorial shows how to separate a noisy observation from a recursively estimated linear latent state while keeping the information boundary and uncertainty visible.
The deliverable is practical. You will calculate one update, implement the recursion in Python and TypeScript, inspect eight model-specific scenarios, and use a guided lab to see why the state changes. The result is an educational filter, not an investment recommendation or a claim of predictive power.
The question this model answers
Kalman Filter answers a conditional question: given the selected model, its fixed configuration, and observations available through the current timestamp, what state summary should be carried forward?
That wording matters. It does not ask what the state “really was” after seeing the future. It does not estimate parameters. It does not decide whether a trading action is profitable. The pattern is useful for teaching latent fair-value, trend, spread, or risk-state estimation without pretending the state is directly observed.
Intuition before notation
Prediction carries yesterday's belief forward; the gain then allocates the new disagreement according to model and measurement uncertainty. Every update therefore needs a ledger. The learner should be able to point to the prior, the prediction, the new evidence, and the posterior. If one of those pieces is hidden, the final curve becomes an assertion rather than an explanation.
The state update visual is canonical: its labels correspond directly to the trace produced by both implementations.
The frozen definition
This article uses a linear Gaussian scalar filter. Scalar, discrete-time, linear-Gaussian filtering with known coefficients and Joseph-form covariance update.
The core recursion is:
m⁻ₜ = a mₜ₋₁; P⁻ₜ = a²Pₜ₋₁+q; Kₜ=P⁻ₜh/(h²P⁻ₜ+r); mₜ=m⁻ₜ+Kₜ(zₜ-hm⁻ₜ).
The package excludes multivariate systems, smoothing, diffuse initialization, parameter fitting, missing observations, and control inputs. Those are legitimate extensions, but adding them silently would make the arithmetic and information set ambiguous.
The package convention that changes the answer
The Joseph covariance update is retained even in one dimension so the teaching contract mirrors the numerically safer matrix form.
That sentence belongs beside the formula because two implementations can both be called Kalman Filter and still disagree when they initialize, index, or parameterize the recursion differently.
When this model is the right abstraction
Use the linear Kalman filter when both transition and measurement maps are linear. Move to EKF or UKF only when the nonlinear map is material and documented.
A smoother line is not automatically a better state estimate; excessive smoothing can hide systematic lag. The practical selection test is not which curve looks smoothest. It is which latent representation, observation model, and information clock match the decision you must audit.
Filtering is not smoothing
A filtered state at t conditions on observations through t. A smoothed state at t can use later observations. Current statsmodels state-space documentation makes the same distinction between filtered and smoothed output (statsmodels 0.14.6 state-space guide).
For an online dashboard, alert, or causal backtest, silently substituting smoothed probabilities is a future-information leak. A retrospective research chart may use smoothing, but it must say so.
Inputs and clocks
The fixture uses 160 daily UTC observations per scenario. They are deterministic and synthetic. The timestamps are observation and availability times together; no exchange calendar, revision process, or vendor feed is implied.
The latent truth or true regime appears only for evaluation. The filter receives the observation values and fixed configuration. This separation lets us ask whether the recursion detects the generator without giving it the answer.
The configuration is part of the claim
{
"a": 0.96,
"h": 1.0,
"q": 0.08,
"r": 0.64,
"initial_mean": 0.0,
"initial_variance": 2.0
}
These values are implementation choices, not fitted estimates. A production claim would need a pre-declared fitting sample, likelihood or loss, optimizer, parameter constraints, model-selection process, and out-of-sample evaluation.
One update by hand
The first canonical observation is 0.17403715. Applying the published recursion gives predicted mean = 0.00000, innovation = 0.17404, kalman gain = 0.75031, filtered mean = 0.13058, filtered variance = 0.48020.
The calculation retains full precision and rounds only for display. The validator repeats this first update independently rather than asking the Python implementation to grade itself.
What the state ledger reveals
For this topic, the most useful fields are predicted mean, innovation, kalman gain, filtered mean, filtered variance. Read them together. A posterior without its predicted state hides persistence; a posterior without its innovation or likelihood hides the observation's contribution.
Eight scenarios that target this algorithm
The scenarios are not a generic family checklist. Each one changes a specific assumption that matters to Kalman Filter:
- Clean tracking: Matched linear dynamics and moderate measurement noise establish the control. The lab asks: Does filtering reduce state error relative to the raw observation?
- Abrupt level shift: A persistent jump at row 80 exposes adaptation delay. The lab asks: How many rows pass before the estimate catches the new level?
- Noise burst: Measurement noise rises only between rows 55 and 105. The lab asks: Does uncertainty explain why the filter should move less during the burst?
- Single outlier: One extreme row at 92 tests recovery without a permanent state story. The lab asks: How quickly does the posterior return after one bad observation?
- Fast drift: The latent state changes faster than the frozen transition expects. The lab asks: When does a smooth estimate become systematic lag?
- Process mismatch: Truth receives much larger process shocks than the frozen q allows. The lab asks: Can a low covariance coexist with a biased state estimate?
- Persistent sensor bias: A measurement offset begins at row 70 and never enters the state equation. The lab asks: Will the filter absorb an observation bias as if it were state?
- Recurring levels: The latent target revisits earlier levels several times. The lab asks: Does the recursion adapt repeatedly without using future rows?
This is the difference between a demo and a learning reference: the reader can name the stressed assumption before looking at the output.
The algorithm
- Validate observations and the fixed configuration.
- Initialize the declared prior.
- Predict before reading the current observation.
- score the observation under the predicted state or hypotheses.
- update and normalize.
- record diagnostics and carry the filtered state forward.
This order is causal. Reordering it can leak the observation into the prior or apply the transition twice.
A diagnostic that can disagree with the picture
Start with the innovation, compare its square with the predicted innovation variance, then ask whether posterior error beats the raw observation over the revealed prefix.
The fixture-backed diagnostic is innovation consistency and tracking benchmark. Prefix RMSE is compared with raw-observation RMSE, while mean normalized innovation squared and a synthetic 95% interval hit rate reveal variance mismatch.
For the canonical full path, the published values include filtered state mae 0.1329, filtered state rmse 0.1619, raw observation rmse 0.3838, rmse improvement vs raw 0.2219, mean normalized innovation squared 0.2417, synthetic 95 interval hit rate 1.0000. They are author-derived from known synthetic truth after the causal trace is complete. They are not an empirical result and do not establish usefulness on market data.
The interactive version recalculates the same quantities only over rows revealed through the current step. That makes the score informative without letting future truth influence an earlier filter state.
Python and TypeScript parity
Both implementations accept the same observation list and configuration object. Both return one trace row per observation. The shared JSON fixture compares every primary state field at a tolerance of 1e-9.
Readable reference arithmetic is intentional. Optimization comes after the contract is proved. In production, use stable matrix decompositions, monitored conditioning, and explicit schema/version controls appropriate to the chosen extension.
The model-specific audit discipline
Continuous-state filters must keep covariance nonnegative. Discrete-state filters must keep probabilities normalized. The UKF must publish its sigma-point weights. BOCPD must define whether a change occurs before or after the current observation. MSAR must define transition direction and which regime owns each AR coefficient.
These are not cosmetic implementation details. They determine the answer.
For this topic, the decisive audit is: Start with the innovation, compare its square with the predicted innovation variance, then ask whether posterior error beats the raw observation over the revealed prefix.
The outlier trap
A single extreme observation can produce a large innovation, strong emission ratio, or elevated change probability. That evidence is not the same as a persistent break. Compare the next several observations before attaching a narrative.
The failure map is a decision aid: check model assumptions, sensitivity, and subsequent evidence before treating a filtered state as operational truth.
Weak separation is an honest result
When regimes have similar emissions or observation noise dominates state movement, posterior probabilities should remain mixed. Forcing a hard label throws away the most important output: the model is uncertain.
The same principle applies to continuous filters. A narrow covariance is only meaningful if process and measurement variances are credible.
Historical evidence decision
The historical-example decision is not useful. A named market episode would add narrative appeal but would not validate a frozen latent-state model. The package therefore uses deterministic synthetic data so the hidden truth, observation clock, and state transition are auditable. A historical case would require licensed point-in-time observations, a parameter-estimation cutoff, revision policy, benchmark, and redistribution permission.
This avoids an especially common error: showing one named episode, fitting or tuning with knowledge of the whole episode, and then presenting a hindsight state chart as causal detection evidence.
Explore the guided lab
The self-contained playground implements choose → step → observe → explain → compare. Change the scenario and one meaningful parameter, then watch the trace, chart, guidance, and audit table recompute together.
Reset restores the canonical first row. Step and Play share one transition. In reduced-motion mode, Play advances exactly one observation and never starts a timer.
How to validate a production extension
- Freeze the information clock and revision policy.
- Separate fitting, filtering, forecasting, smoothing, and decision evaluation.
- Use time-ordered holdouts or rolling origins.
- Benchmark against a simpler state or regime rule.
- Test parameter sensitivity and transition/emission misspecification.
- Monitor numerical conditioning and probability normalization.
- Keep uncertainty visible in the user interface.
- Never convert a latent-state estimate directly into a performance claim.
Family map
The next tutorial is Extended Kalman Filter. The representation changes, but the audit question stays constant: exactly what did the model know when it produced this state?
Summary
You can now implement and audit Kalman Filter under a precise selected contract. The essential skill is not drawing a smooth state line. It is preserving the prior, prediction, evidence, posterior, and information boundary so another analyst can reproduce the result and challenge the assumptions.
Primary methodological records: A New Approach to Linear Filtering and Prediction Problems, An Introduction to the Kalman Filter, Bayesian Filtering and Smoothing, Time Series Analysis by State Space Methods. The complete source-role and limitation ledger is in the canonical package.
Rendered from the canonical Mermaid sources linked by this article.
Kalman Filter Causal Update Flow
This diagram shows the information order that every implementation and visual preserves.
Takeaway: the observation enters after prediction, and no row after t can affect the filtered state at t.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
The canonical package distinguishes sourced definitions from implementation choices and synthetic inputs.
KALMAN1960 — A New Approach to Linear Filtering and Prediction Problems
- Organization or authors: R. E. Kalman
- Source type: Original peer-reviewed paper
- Publication or effective date: 1960-03-01
- Version: Journal of Basic Engineering 82(1), 35–45
- URL or DOI: https://doi.org/10.1115/1.3662552
- Accessed: 2026-07-27
- Jurisdiction: Methodological; not jurisdiction-specific
- Supports: Recursive linear state estimation and covariance propagation.
- Limitations: Original notation differs from this scalar teaching contract.
- Evidence role: Public authoritative definition or maintained implementation context
- Publication boundary: No source dataset or copyrighted figure is redistributed
WELCH2006 — An Introduction to the Kalman Filter
- Organization or authors: Greg Welch and Gary Bishop
- Source type: University technical report
- Publication or effective date: 2006-07-24
- Version: UNC-Chapel Hill TR 95-041
- URL or DOI: https://www.cs.unc.edu/~welch/media/pdf/kalman_intro.pdf
- Accessed: 2026-07-27
- Jurisdiction: Methodological; not jurisdiction-specific
- Supports: Discrete Kalman predict/update equations and EKF linearization.
- Limitations: Tutorial evidence; it does not make nonlinear EKF estimates exact.
- Evidence role: Public authoritative definition or maintained implementation context
- Publication boundary: No source dataset or copyrighted figure is redistributed
SARKKA2023 — Bayesian Filtering and Smoothing
- Organization or authors: Simo Särkkä and Lennart Svensson
- Source type: Authoritative graduate textbook
- Publication or effective date: 2023-05-31
- Version: Second edition, Cambridge University Press
- URL or DOI: https://doi.org/10.1017/9781108917407
- Accessed: 2026-07-27
- Jurisdiction: Methodological; not jurisdiction-specific
- Supports: Unified Bayesian filtering terminology and modern treatment of linear, extended, and sigma-point filters.
- Limitations: The scalar functions and parameter values in this package remain local teaching choices.
- Evidence role: Public authoritative definition or maintained implementation context
- Publication boundary: No source dataset or copyrighted figure is redistributed
STATSMODELS_STATE — Time Series Analysis by State Space Methods
- Organization or authors: statsmodels developers
- Source type: Maintained official documentation
- Publication or effective date: 2025-12-05
- Version: statsmodels 0.14.6
- URL or DOI: https://www.statsmodels.org/stable/statespace.html
- Accessed: 2026-07-27
- Jurisdiction: Methodological; not jurisdiction-specific
- Supports: Current state-space terminology and the distinction between filtering and smoothing.
- Limitations: Library capabilities and defaults are not universal mathematical definitions.
- Evidence role: Public authoritative definition or maintained implementation context
- Publication boundary: No source dataset or copyrighted figure is redistributed
Full dependency-light reference implementations in both supported languages.
export function runFilter(observations, config) {
if (!Array.isArray(observations) || !observations.length || observations.some(v => !Number.isFinite(v))) throw new Error("observations must be finite and non-empty");
const c = config; if (c.q < 0 || c.r <= 0 || c.initial_variance < 0) throw new Error("invalid variance");
let mean=c.initial_mean, variance=c.initial_variance; const trace=[];
observations.forEach((observation,index)=>{
const predicted_mean=c.a*mean, predicted_variance=c.a*c.a*variance+c.q;
const predicted_observation=c.h*predicted_mean, innovation=observation-predicted_observation;
const innovation_variance=c.h*c.h*predicted_variance+c.r;
const kalman_gain=predicted_variance*c.h/innovation_variance;
mean=predicted_mean+kalman_gain*innovation;
variance=(1-kalman_gain*c.h)**2*predicted_variance+kalman_gain**2*c.r;
trace.push({index,predicted_mean,predicted_variance,predicted_observation,innovation,innovation_variance,kalman_gain,filtered_mean:mean,filtered_variance:variance});
}); return trace;
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.