GitHub で見る

Kalman Filter Trend Strategy

This trend-following method uses a Kalman filter to smooth price fluctuations and estimate the underlying direction. The filter dynamically adapts to market noise, offering a refined view of trend strength compared to standard moving averages.

Testing indicates an average annual return of about 112%. It performs best in the forex market.

A long position is opened when the closing price rises above the Kalman filter estimate. Conversely, a short position is taken when the close drops below the filter value. Because the filter updates on every bar, trades flip whenever price crosses the line, providing continuous participation in trending markets.

Traders who prefer systematic approaches may find the Kalman filter useful for reducing whipsaws. A protective stop based on ATR keeps risk limited in case the trend rapidly reverses.

Details

  • Entry Criteria:
    • Long: Close > Kalman Filter
    • Short: Close < Kalman Filter
  • Long/Short: Both sides.
  • Exit Criteria:
    • Long: Exit on close < Kalman Filter
    • Short: Exit on close > Kalman Filter
  • Stops: Yes, ATR-based stop-loss.
  • Default Values:
    • ProcessNoise = 0.01m
    • MeasurementNoise = 0.1m
    • CandleType = TimeSpan.FromMinutes(5)
  • Filters:
    • Category: Trend
    • Direction: Both
    • Indicators: Kalman Filter
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Intraday
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk Level: Medium
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>
/// Kalman Filter Trend strategy.
/// Uses a custom Kalman Filter indicator to track price trend.
/// </summary>
public class KalmanFilterTrendStrategy : Strategy
{
	private readonly StrategyParam<decimal> _processNoiseParam;
	private readonly StrategyParam<decimal> _measurementNoiseParam;
	private readonly StrategyParam<DataType> _candleTypeParam;

	private KalmanFilter _kalmanFilter;
	private AverageTrueRange _atr;

	/// <summary>
	/// Process noise coefficient for Kalman filter.
	/// </summary>
	public decimal ProcessNoise
	{
		get => _processNoiseParam.Value;
		set => _processNoiseParam.Value = value;
	}

	/// <summary>
	/// Measurement noise coefficient for Kalman filter.
	/// </summary>
	public decimal MeasurementNoise
	{
		get => _measurementNoiseParam.Value;
		set => _measurementNoiseParam.Value = value;
	}

	/// <summary>
	/// Candle type for strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleTypeParam.Value;
		set => _candleTypeParam.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public KalmanFilterTrendStrategy()
	{
		_processNoiseParam = Param(nameof(ProcessNoise), 0.01m)
			.SetRange(0.0001m, 1)
			.SetDisplay("Process Noise", "Process noise coefficient for Kalman filter", "Parameters")
			
			.SetOptimize(0.001m, 0.1m, 0.005m);

		_measurementNoiseParam = Param(nameof(MeasurementNoise), 0.1m)
			.SetRange(0.0001m, 1)
			.SetDisplay("Measurement Noise", "Measurement noise coefficient for Kalman filter", "Parameters")
			
			.SetOptimize(0.01m, 1.0m, 0.1m);

		_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for strategy", "Common");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_kalmanFilter = null;
		_atr = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Create indicators
		_kalmanFilter = new KalmanFilter 
		{ 
			ProcessNoise = ProcessNoise,
			MeasurementNoise = MeasurementNoise 
		};
		
		_atr = new AverageTrueRange { Length = 14 };

		// Create subscription and bind indicators
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_kalmanFilter, _atr, ProcessCandle)
			.Start();

		// Setup chart visualization if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _kalmanFilter);
			DrawOwnTrades(area);
		}
		
		// Enable position protection
		StartProtection(
			takeProfit: new Unit(0, UnitTypes.Absolute), // No take profit
			stopLoss: new Unit(2, UnitTypes.Absolute) // Stop loss at 2*ATR
		);
	}

	private void ProcessCandle(ICandleMessage candle, decimal kalmanValue, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;
		
		// Calculate trend direction
		var trend = candle.ClosePrice > kalmanValue ? 1 : -1;
		
		// Trading logic based on price position relative to Kalman filter
		if (trend > 0 && Position <= 0)
		{
			// Buy when price is above Kalman filter (uptrend)
			BuyMarket(Volume + Math.Abs(Position));
		}
		else if (trend < 0 && Position >= 0)
		{
			// Sell when price is below Kalman filter (downtrend)
			SellMarket(Volume + Math.Abs(Position));
		}
	}
}