This strategy applies the Kalman Filter to the open and close prices of each candle. The resulting smoothed candles are classified as bullish or bearish depending on whether the smoothed close is above or below the smoothed open. Positions are opened when the candle color changes:
Bullish (pink) → opens a long position and closes any short position.
Bearish (blue) → opens a short position and closes any long position.
Parameters
Process Noise – smoothing factor for the Kalman Filter.
Candle Type – timeframe of candles used in the strategy.
How It Works
For every finished candle, the open and close prices are individually smoothed using separate Kalman Filters.
A bullish signal is generated when the smoothed close exceeds the smoothed open. A bearish signal occurs when the smoothed close is below the smoothed open.
The strategy enters a long position on a bullish signal and a short position on a bearish signal. Opposite positions are closed automatically.
The strategy is intended as an example of combining multiple Kalman Filters to form a simple trend‑following system.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Kalman filtered candle colors.
/// </summary>
public class KalmanFilterCandlesStrategy : Strategy
{
private readonly StrategyParam<decimal> _processNoise;
private readonly StrategyParam<DataType> _candleType;
private KalmanFilter _openFilter;
private KalmanFilter _closeFilter;
private int _prevColor;
private bool _hasPrev;
public decimal ProcessNoise { get => _processNoise.Value; set => _processNoise.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public KalmanFilterCandlesStrategy()
{
_processNoise = Param(nameof(ProcessNoise), 1m)
.SetDisplay("Process Noise", "Kalman filter smoothing factor", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for candles", "Common");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevColor = 1;
_hasPrev = false;
_openFilter = default;
_closeFilter = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_openFilter = new KalmanFilter { ProcessNoise = ProcessNoise, MeasurementNoise = ProcessNoise };
_closeFilter = new KalmanFilter { ProcessNoise = ProcessNoise, MeasurementNoise = ProcessNoise };
Indicators.Add(_openFilter);
Indicators.Add(_closeFilter);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var openInput = new DecimalIndicatorValue(_openFilter, candle.OpenPrice, candle.OpenTime) { IsFinal = true };
var closeInput = new DecimalIndicatorValue(_closeFilter, candle.ClosePrice, candle.OpenTime) { IsFinal = true };
var openRes = _openFilter.Process(openInput);
var closeRes = _closeFilter.Process(closeInput);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var openVal = openRes.ToDecimal();
var closeVal = closeRes.ToDecimal();
var color = openVal < closeVal ? 2 : openVal > closeVal ? 0 : 1;
if (_hasPrev)
{
if (color == 2 && _prevColor != 2)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (color == 0 && _prevColor != 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
_prevColor = color;
_hasPrev = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import KalmanFilter
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class kalman_filter_candles_strategy(Strategy):
def __init__(self):
super(kalman_filter_candles_strategy, self).__init__()
self._process_noise = self.Param("ProcessNoise", 1.0) \
.SetDisplay("Process Noise", "Kalman filter smoothing factor", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for candles", "Common")
self._open_filter = None
self._close_filter = None
self._prev_color = 1
self._has_prev = False
@property
def process_noise(self):
return self._process_noise.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(kalman_filter_candles_strategy, self).OnReseted()
self._prev_color = 1
self._has_prev = False
self._open_filter = None
self._close_filter = None
def OnStarted2(self, time):
super(kalman_filter_candles_strategy, self).OnStarted2(time)
self._open_filter = KalmanFilter()
self._open_filter.ProcessNoise = self.process_noise
self._open_filter.MeasurementNoise = self.process_noise
self._close_filter = KalmanFilter()
self._close_filter.ProcessNoise = self.process_noise
self._close_filter.MeasurementNoise = self.process_noise
self.Indicators.Add(self._open_filter)
self.Indicators.Add(self._close_filter)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
open_res = process_float(self._open_filter, candle.OpenPrice, candle.OpenTime, True)
close_res = process_float(self._close_filter, candle.ClosePrice, candle.OpenTime, True)
open_val = float(open_res)
close_val = float(close_res)
if open_val < close_val:
color = 2
elif open_val > close_val:
color = 0
else:
color = 1
if self._has_prev:
if color == 2 and self._prev_color != 2:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif color == 0 and self._prev_color != 0:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._prev_color = color
self._has_prev = True
def CreateClone(self):
return kalman_filter_candles_strategy()