LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/Kalman Filter
Statisticalid · kalman
K

Kalman Filter

Adaptive fair-value tracker. Smooth and self-correcting.

In plain English

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.

No jargon. Just what this bot does.
The longer version

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.

The math
formula
x = x + K · (price − x); K = P/(P+R)
parameters
processProcess noise (Q)range 0.001 → 1default · 0.05
obsObservation noise (R)range 0.1 → 10default · 1.5
Live demo

Real Kalman Filter bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.

symbolloading…
loading AMZN bars…
Source code · public

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.

lib/quant/bots.ts·lines 469529
TypeScript · MIT-licensed
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,
    };
  },
};
what each piece means
  • 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.
use this code yourself
  1. Copy the whole block above.
  2. On /quant, click + Import your bot in the bot library.
  3. Paste, hit save. It hot-loads into your workspace.
  4. Edit any param defaults or logic to your taste — it's now yours.
Specialty · when it shines, when it fails
✓ Shines when
  • ·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.
✗ Fails when
  • ·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.
How to read its verdict

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.

FAQ
What are Q and R?+
Q (process noise): how much the underlying 'true' price can drift each step. Higher Q = more responsive filter. R (observation noise): how noisy the observed price is. Higher R = filter trusts itself more than the data. Tuning these is most of the art of Kalman filtering.
Why use this instead of an EMA?+
EMA has a fixed smoothing factor; Kalman's smoothing adapts to observed noise. In practice, on liquid stocks, the difference is marginal. On noisier instruments (illiquid bonds, FX crosses), Kalman shines.