Kalman Filter
Adaptive fair-value tracker. Smooth and self-correcting.
A Kalman filter is what guides your phone's GPS — it blends what it expects with what it sees. Here it tracks the 'true' price hiding under the noise. When today's price wanders far from that filtered fair value, it tends to come back.
A Kalman filter is what guides your phone's GPS — it blends what it expects with what it sees. Here it tracks the 'true' price hiding under the noise. The filter adapts: when noise is high, it leans on its expectation; when measurement is clean, it follows the data. Big deviations from filtered fair value are the trade.
x = x + K · (price − x); K = P/(P+R)
Real Kalman Filter bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.
This is the actual code the bot runs — not a re-explanation, not a simplified version. Whatever ships here is what executes when you press Run All in the workbench. Read it, copy it, fork it, build a better one.
const kalmanBot: BotDef = {
id: "kalman",
name: "Kalman Filter",
category: "stats",
glyph: "K",
tagline: "Adaptive fair-value tracker. Smooth and self-correcting.",
formula: "x = x + K · (price − x); K = P/(P+R)",
params: [
{ key: "process", label: "Process noise (Q)", kind: "number", default: 0.05, min: 0.001, max: 1, step: 0.005 },
{ key: "obs", label: "Observation noise (R)", kind: "number", default: 1.5, min: 0.1, max: 10, step: 0.1 },
],
run: (ctx, p): BotResult => {
const q = num(p, "process", 0.05);
const r = num(p, "obs", 1.5);
const px = closes(ctx.candles);
const fair = kalman(px, q, r);
const last = px[px.length - 1];
const fairLast = fair[fair.length - 1] ?? last;
const dev = (last - fairLast) / fairLast;
const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
const signals: Signal[] = [];
for (let i = 1; i < px.length; i++) {
const f = fair[i] as number;
const fp = fair[i - 1] as number;
const d = (px[i] - f) / f;
const dp = (px[i - 1] - fp) / fp;
if (dp < -0.02 && d >= -0.02) {
signals.push({ i, kind: "buy", price: px[i], label: "back to fair" });
sigArr[i] = 1;
} else if (dp > 0.02 && d <= 0.02) {
signals.push({ i, kind: "sell", price: px[i], label: "back to fair" });
sigArr[i] = -1;
}
}
const bt = backtestLongOnly(px, sigArr);
return {
signals,
metrics: [
{ key: "fair", label: "Fair", value: fmtNum(fairLast), tone: "info" },
{ key: "dev", label: "Deviation", value: fmtPct(dev), tone: dev > 0.03 ? "bear" : dev < -0.03 ? "bull" : "neutral" },
{ key: "trades", label: "Trades", value: String(bt.trades) },
{ key: "ret", label: "Return", value: fmtPct(bt.equity[bt.equity.length - 1] - 1), tone: "info" },
],
overlay: [{ values: fair, color: "var(--cyan)", label: "Kalman" }],
summary: `Kalman fair value ${fairLast.toFixed(2)}, deviation ${(dev * 100).toFixed(1)}%.`,
beginner:
"A Kalman filter is what guides your phone's GPS — it blends what it expects with what it sees. Here it tracks the 'true' price hiding under the noise. Big deviations are the trade.",
verdict: {
side: dev < -0.03 ? "buy" : dev > 0.03 ? "sell" : "hold",
text:
dev < -0.03
? `Price ${(Math.abs(dev) * 100).toFixed(1)}% below filtered fair value.`
: dev > 0.03
? `Price ${(dev * 100).toFixed(1)}% above filtered fair value.`
: "Price hugging fair value — no edge.",
confidence: Math.min(1, Math.abs(dev) * 20),
},
equity: bt.equity,
};
},
};id— unique key the workbench uses to find the bot.params— the sliders + inputs you see on the cell.run(ctx, p)— the function that gets called with candles + your params and returns the verdict.verdict— the BUY / SELL / HOLD pill at the top of the cell.metrics— the small stat boxes shown in the cell body.
- Copy the whole block above.
- On /quant, click + Import your bot in the bot library.
- Paste, hit save. It hot-loads into your workspace.
- Edit any param defaults or logic to your taste — it's now yours.
- ·Adaptive smoothing. SMAs and EMAs use fixed windows; Kalman re-weights based on observed noise. More robust in changing volatility.
- ·Pair trading spreads. Kalman estimates the slowly-drifting hedge ratio between two assets.
- ·Real-time fair-value tracking. Lower latency than equivalent-quality smoothing methods.
- ·Misspecified noise parameters. If process noise (Q) and observation noise (R) are off, the filter chases the wrong target.
- ·Non-Gaussian shocks. The filter assumes Gaussian noise; fat-tailed shocks blow it up briefly.
- ·Highly trending markets. Kalman tracks the trend, so 'deviation from fair value' mostly fluctuates around zero — no signal.
BUY when price just rebounded from > 2% below filtered fair value. SELL when it rejected from > 2% above. Confidence scales with the absolute deviation. The 'fair value' overlay on the chart shows where the filter thinks the true price is right now.
What are Q and R?+
Why use this instead of an EMA?+
Buy when fast moving average crosses above slow.
Linear regression line + std-deviation envelopes.
Bet on snap-back when the price wanders too far.
Volatility envelopes around a moving average.
Is this market trending, mean-reverting, or random?