View on GitHub

Kalman Filter Candles Strategy

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

  1. For every finished candle, the open and close prices are individually smoothed using separate Kalman Filters.
  2. 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.
  3. 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;
	}
}