Jayadev Rana Get a custom script
Free indicator · Pine Script v6

Kalman vs EMA

Lesson 28 of the Pine Script course. A Kalman filter written with var state: the 1-D version with fixed noise settles into an EMA, and the price plus velocity version is compared against an EMA on the same chart with a table that measures distance to price and slope flips.

IndicatorPine Script v6OverlayFree

Get the codeDownload .pine.txt

Snapshots

Kalman vs EMA on gold 1h: the two-state Kalman line coloured by slope, the 1-D Kalman and the EMA, and a table comparing average distance to close, slope flips, and the 1-D gain against the EMA's alpha.
Kalman vs EMA on gold 1h: the two-state Kalman line coloured by slope, the 1-D Kalman and the EMA, and a table comparing average distance to close, slope flips, and the 1-D gain against the EMA's alpha.
The 1-D Kalman filter sitting on top of the EMA 20, as the maths predicts.
The 1-D Kalman filter sitting on top of the EMA 20, as the maths predicts.
The two-state predict and update steps and the measurement table.
The two-state predict and update steps and the measurement table.

How it works

  1. Predict then update: the gain K = P / (P + R) decides how far the estimate moves toward each new close.
  2. With fixed Q and R the 1-D gain settles to a constant, which makes it an EMA; the table shows the settled gain next to the EMA's alpha.
  3. The price plus velocity version tracks the slope as well, and the table measures its lag and whipsaws against the EMA after the first 200 bars.

Settings

SettingDefaultWhat it does
Q, process noise0.01How much the filter trusts its model to change
R, measurement noise1.0How much it distrusts each new price
Velocity noise0.00001Process noise of the velocity state
EMA to compare against20Length of the comparison EMA

Source code

In TradingView: open the Pine Editor, create a new indicator, paste the code, then click "Add to chart".

kalman-filter.pine.txtGitHubDownload
//@version=6
indicator("Kalman vs EMA", overlay = true)

qIn = input.float(0.01, "Q, process noise", minval = 0.00001, step = 0.001)
rIn = input.float(1.0, "R, measurement noise", minval = 0.00001, step = 0.1)
qvIn = input.float(0.00001, "Velocity noise", minval = 0.0, step = 0.00001)
emaLen = input.int(20, "EMA to compare against", minval = 2)

var float x1 = na
var float p1 = 1.0

if na(x1)
    x1 := close
p1 := p1 + qIn
float k1 = p1 / (p1 + rIn)
x1 := x1 + k1 * (close - x1)
p1 := (1 - k1) * p1

ema = ta.ema(close, emaLen)
plot(ema, "EMA", color.new(color.gray, 0), 4)
plot(x1, "Kalman 1D", color.new(color.orange, 0), 1)

var float x = na
var float v = 0.0
var float p00 = 1.0
var float p01 = 0.0
var float p11 = 1.0

if na(x)
    x := close
x := x + v
p00 := p00 + 2 * p01 + p11 + qIn
p01 := p01 + p11
p11 := p11 + qvIn

float s = p00 + rIn
float k0 = p00 / s
float kv = p01 / s
float y = close - x
x := x + k0 * y
v := v + kv * y
p11 := p11 - kv * p01
p01 := (1 - k0) * p01
p00 := (1 - k0) * p00

rising = x > x[1]
plot(x, "Kalman", rising ? color.teal : color.red, 3)

var float distK = 0.0
var float distE = 0.0
var int nBars = 0
var int flipK = 0
var int flipE = 0
if bar_index >= 200
    nBars += 1
    distK += math.abs(close - x)
    distE += math.abs(close - ema)
    if (x - x[1]) * (x[1] - x[2]) < 0
        flipK += 1
    if (ema - ema[1]) * (ema[1] - ema[2]) < 0
        flipE += 1

var table t = table.new(position.top_right, 3, 4, bgcolor = color.black,
  frame_width = 1, frame_color = color.gray, border_width = 1)
row(r, name, a, b) =>
    table.cell(t, 0, r, name, text_color = color.white)
    table.cell(t, 1, r, a, text_color = color.teal)
    table.cell(t, 2, r, b, text_color = color.gray)

if barstate.islast and nBars > 0
    row(0, "measured", "Kalman", "EMA")
    row(1, "avg distance", str.tostring(distK / nBars, format.mintick),
      str.tostring(distE / nBars, format.mintick))
    row(2, "slope flips", str.tostring(flipK), str.tostring(flipE))
    row(3, "1D gain vs alpha", str.tostring(k1, "#.####"),
      str.tostring(2.0 / (emaLen + 1), "#.####"))

Watch it built

This script is written and explained step by step in the video lesson.

Need a custom indicator or strategy?

Non-repainting Pine Script v6, backtested with real costs, alert and webhook ready. Fixed quote within 24 hours.

Free and open source under the Mozilla Public License 2.0. Educational content only, not financial advice. Backtest results are historical and include the costs stated; past performance does not predict future results. © Jayadev Rana · Privacy · Terms