Kalman
A Kalman filter maintains an estimate of an underlying state while new noisy measurements arrive. Each step combines the previous estimate, the model uncertainty, and the latest observed features.
How It Works
The filter alternates between two operations:
- Predict: project the state forward and add process noise (Q).
- Update: blend the prediction with the new measurement, weighted by the Kalman gain.
Predict: P = P + Q
Update:
K = P / (P + R) // Kalman gain
x = x + K * (z - x) // state update
P = (1 - K) * P // error covariance update
Where x is the estimated price, z is the noisy measurement, P is the
error covariance (uncertainty), Q is the process noise, and R is the
measurement noise.
The Kalman gain K controls how much the estimate moves toward the latest measurement. Higher R
makes the filter trust the prior estimate more. Higher Q makes it react more quickly to new observations.
Interactive Demo
The chart compares market candles, the filtered estimate, and the next-step prediction. Use normal chart gestures to pan or zoom the time axis. The lower panels show prediction error and a simple edge PnL.
Each slider has a sparkline below it showing how the active strategy’s PnL changes when sweeping that parameter from minimum to maximum, holding all other sliders fixed. The horizontal dotted line marks the PnL at the parameter’s minimum value, so you can see at a glance whether increasing the parameter helps or hurts. A colored marker tracks the current slider position along the curve. These sparklines update whenever you change features, strategies, tickers, or dates.
Interpreting the Parameters
Q — Process Noise
Q controls how quickly the hidden state is allowed to move. Lower Q produces smoother estimates. Higher Q tracks new measurements more closely.
R — Measurement Noise
R controls how noisy the selected measurements are assumed to be. Lower R gives the current features more influence. Higher R shifts weight back to the prior estimate.
The ratio Q/R determines the behavior. When Q dominates, the filter follows the measurements more quickly. When R dominates, the filter smooths more aggressively.
The feature toggles change the measurement fed into the update step. Estimate RMSE measures fit to the current close, while next-step RMSE measures how well the current estimate lines up with the following close.
Backtest Mechanics
All five strategies run on the same backtest engine. The timing is the same for every strategy:
- After each one-second bar closes, the Kalman filter produces an estimate of the “true” price using the selected measurement features.
- The strategy evaluates its rule against that estimate and the bar’s close, and decides a position
— +1 (long one share), −1 (short), or 0
(flat). The decision carries forward: if bar
ireturns +1 and bari+1also returns +1, the position persists. A position exits only when the strategy returns a different value. - The position is held from this bar’s close through the next bar. Profit is
position × (close[next] − close[current]), minus transaction costs on any change of position. - Cost is deducted as
|position − previous position| × costPerShare. Flipping from +1 to −1 costs 2 × costPerShare. Staying flat costs nothing.
The first 30 minutes of the trading day are reserved for calibrating non-price features (volume, trades) into price-equivalent units. PnL accumulation starts after that window. The default edge threshold of $0.01 means the filter must see at least a one-cent gap between estimate and close before taking any position. This prevents churning on tiny, noise-driven deviations.
The position panel at the bottom of the chart stack shows the raw signal over time (green for long, red for short, gray for flat). Below it, the cumulative PnL and drawdown panels track how the strategy performs, with a buy-and-hold benchmark in orange for comparison.
Strategy Descriptions
Edge trend
The simplest strategy. If the Kalman estimate is significantly above the current close, the filter thinks the price is “too low” relative to its smoothed view — go long. If the estimate is below by more than the threshold, go short. If neither condition holds, go flat. This decision is re-evaluated fresh on every bar, so a long position exits to flat as soon as the estimate and close converge within the threshold.
diff = estimate − close
if |diff| > threshold:
position = sign(diff)
else:
position = 0
This is a trend-following strategy: it assumes the estimate is a better read of the underlying value, and that price will drift toward it. The Kalman gain and process noise control how quickly the estimate chases new prices, which in turn determines how often the edge threshold is triggered.
Mean revert
The inverse of the edge trend. When the close is significantly above the estimate, the price is “extended” relative to the filter’s view — go short, expecting a pullback. When the close is below, go long. Otherwise go flat. Like edge trend, this is evaluated independently each bar, so a position exits to flat as soon as the deviation shrinks inside the threshold.
diff = close − estimate
if |diff| > threshold:
position = sign(diff)
else:
position = 0
Where edge trend treats the estimate as a target that price will gravitate toward, mean reversion treats the estimate as a center line that price is likely to snap back to. Which one works better depends on whether the current price regime is trending or range-bound.
Z-score
Instead of a fixed dollar threshold, this strategy normalizes the latest estimate residual (estimate − close) by its recent history. It maintains a rolling 300-bar window of past residuals and computes their mean and standard deviation. A position is taken only when the z-score exceeds ±2.0. When the z-score falls back within ±2.0, the position returns to flat.
window = past 300 residuals
z = (current residual − mean(window)) / std(window)
if z > 2.0: position = −1 # residual is unusually large → short
if z < −2.0: position = +1 # residual is unusually small → long
else: position = 0
The first 60 bars of the rolling window are skipped to allow the statistics to stabilize. This strategy is adaptive: as volatility changes throughout the day, the z-score threshold adjusts automatically. A one-cent deviation during a quiet period might trigger a position, while the same deviation during high volatility would not.
Prediction momentum
This strategy looks at the change in the Kalman prediction from one bar to the next rather than the level of the estimate. If the predicted price is rising sharply (more than 5× the edge threshold), go long. If it is falling sharply, go short. If the prediction change is small, go flat.
predChange = prediction[i] − prediction[i−1]
if |predChange| > threshold × 5:
position = sign(predChange)
else:
position = 0
This is a pure momentum signal on the filter’s own state trajectory. It ignores the absolute level of the estimate and the current price entirely. It tends to fire less frequently than the edge strategies because the prediction changes more smoothly than raw prices, and it only triggers when the filter’s internal model shifts abruptly.
Cross band
A band-based strategy that triggers only on crossings. A band is set at
estimate ± threshold × 3. When the close crosses above the upper band (but was below
it on the previous bar), the strategy goes long. When it crosses below the lower band (but was
above), it goes short. Positions persist until the close crosses back inside the band.
band = threshold × 3
above = close > estimate + band
below = close < estimate − band
if above and not wasAbove: position = +1
if below and not wasBelow: position = −1
elif prevPos == +1 and not above: position = 0
elif prevPos == −1 and not below: position = 0
else: position = prevPos # hold
This is the only strategy that explicitly holds a position across bars using prior state. The others re-evaluate independently each bar and go flat when no signal is present. Cross band avoids the “whipsaw” problem of the simpler strategies, where a price zigzagging right at the threshold can cause rapid flips between long and flat. The band adds hysteresis: once you are in a position, you stay in until the price fully crosses back inside the band.
Comparing Strategies
Each strategy exposes a different assumption about how the Kalman estimate relates to future price movements. The tunable parameters (Q, R, edge threshold, cost) affect all five strategies, but the switching behavior and sensitivity differ sharply:
- Edge trend, mean revert, z-score, and prediction momentum all re-evaluate independently on every bar. If the condition is met, they return +1 or −1; if not, they return 0 (flat). A position persists only as long as the condition continuously holds. The moment the signal weakens, they exit to flat rather than holding and waiting for a reversal.
- Cross band is the exception. It uses hysteresis: once entered, it holds the position until the price crosses back inside the band, ignoring interim flat signals. This gives it less turnover and fewer small losers, but it can also hold through drawdowns that the other strategies would have avoided by going flat.
- Edge trend and mean revert trade most frequently when Q is high (the estimate chases prices) and the edge threshold is low.
- Z-score adapts to changing volatility, so it may trade more during high-volatility periods and less during quiet ones, without any slider adjustment.
- Prediction momentum fires only on sharp turns in the filter’s own trajectory. It is the least active strategy by design.
The position panel and the cumulative PnL chart together tell the story: watch when the histogram switches from green to red, and whether the PnL curve rises steadily or in sharp jumps. Switching between strategies (using the buttons in the control panel) shows how the same Kalman estimate can produce radically different trading patterns depending on the decision rule applied to it.
New Subcharts
Three additional panels help reveal the filter's internal mechanics:
- Kalman gain (K) — How much weight each new measurement receives. Values near 1 mean the filter trusts the measurement more than its prior. Values near 0 mean it relies on the previous estimate. The gain automatically drops when the filter converges (P shrinks) and rises when process noise (Q) re-widens the uncertainty.
-
Error covariance (P) — The filter's internal uncertainty estimate.
It starts at 1.0 and evolves with each step: predict adds Q, update shrinks it by
the factor
(1 − K). Low P means the filter is confident in its estimate. Watch how P behaves when you toggle features or change Q and R. - Position — The raw trading signal (+1 long, −1 short, 0 flat) shown as a histogram. The color shows direction (green long, red short). This makes it easy to see when the filter triggers a position change and whether the entries and exits align with price moves in the main chart above.
More features do not automatically improve prediction. VWAP is usually close to OHLC, so it often adds little independent information. Volume and trades are activity measures, not prices; this demo maps them into price units using the calibration window, and weak or unstable relationships can add noise instead of signal.