View on GitHub

Stat Euclidean Metric Strategy

Overview

The strategy reproduces the behaviour of the MetaTrader expert advisor Stat_Euclidean_Metric.mq4. It monitors MACD reversals on a single instrument and timeframe. When the MACD line forms a local turning point the strategy either opens a position immediately (training mode) or validates the setup with a k-nearest neighbours (k-NN) classifier that compares the current market structure with historical feature vectors stored in binary files.

Trading logic

  1. Subscribe to the configured candle type and calculate the MACD indicator on the typical price ( (High + Low + Close) / 3 ).
  2. Detect a bearish reversal when the last three completed MACD values satisfy MACD[2] <= MACD[1] and MACD[1] > MACD[0].
  3. Detect a bullish reversal when MACD[2] >= MACD[1] and MACD[1] < MACD[0].
  4. Depending on the selected mode:
    • Training mode (TrainingMode = true) – open a market order in the direction of the reversal after optionally closing the current position. This mimics the original EA behaviour when it gathers new samples.
    • Classifier mode (TrainingMode = false) – compute five ratios of simple moving averages of the typical price and evaluate the probability of success with a k-NN model. Place orders only if the probability crosses the configured thresholds.
  5. Apply the built-in StartProtection module to attach stop-loss and take-profit levels in instrument steps.

Feature vector for classification

The k-NN model uses the following ratios calculated on the just closed candle:

  • SMA(89) / SMA(144)
  • SMA(144) / SMA(233)
  • SMA(21) / SMA(89)
  • SMA(55) / SMA(89)
  • SMA(2) / SMA(55)

Each sample stored in the dataset files contains six double values: the five ratios above and a label (0 for an unfavourable outcome, 1 for a successful trade). During evaluation the strategy selects the closest NeighborCount samples, averages their labels and interprets the result as the probability of success.

Dataset files

  • BuyDatasetPath – path to the binary file with vectors collected after bullish trades.
  • SellDatasetPath – path to the binary file with vectors collected after bearish trades.

If a path is relative it will be resolved against Environment.CurrentDirectory. Missing files are reported in the log and treated as an empty dataset. This implementation reads datasets but does not update or append new samples automatically; exporting new vectors must be handled externally when running in training mode.

Parameters

  • TrainingMode – switch between pure MACD trading and classifier-assisted trading.
  • BuyThreshold / SellThreshold – minimal probability returned by the classifier to open trades in the primary direction.
  • AllowInverseEntries – enables contrarian trades when the probability is extremely low.
  • InverseBuyThreshold / InverseSellThreshold – maximal probability that still triggers an opposite-direction trade.
  • FastLength / SlowLength / SignalLength – MACD EMA lengths.
  • TakeProfitPoints / StopLossPoints – protective levels expressed in instrument steps.
  • ClosePositionsOnSignal – close the current net position before sending a new order.
  • BuyDatasetPath / SellDatasetPath – binary files that store historical vectors.
  • NeighborCount – number of neighbours used in the k-NN vote.
  • CandleType – candle series used for all indicators.

Usage recommendations

  • Provide absolute or working-directory-relative paths to the dataset files before enabling classifier mode.
  • Collect high-quality samples by running the strategy in training mode on historical data and exporting vectors manually.
  • Optimise the thresholds and the neighbour count to adapt the classifier to new markets or instruments.
  • Keep the instrument Volume parameter aligned with the risk model because the strategy always opens Volume + |Position| lots to reverse the net position when necessary.

Differences from the MQL4 version

  • The classifier datasets are only read; the original EA writes new samples during deinitialisation. Here the user must update the files manually after analysing the trade history.
  • All protective orders are attached via StockSharp StartProtection instead of manual OrderSend parameters.
  • Order closing in classifier mode always exits the entire position when ClosePositionsOnSignal is enabled, whereas the MQL4 script closed only profitable orders before taking new signals.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD reversal strategy with moving average ratio filter.
/// Enters on MACD histogram reversals filtered by MA trend alignment.
/// </summary>
public class StatEuclideanMetricStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _trendMaLength;

	private readonly List<decimal> _macdHistory = new();

	public StatEuclideanMetricStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_fastLength = Param(nameof(FastLength), 12)
			.SetDisplay("Fast Length", "Fast EMA period for MACD.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 26)
			.SetDisplay("Slow Length", "Slow EMA period for MACD.", "Indicators");

		_trendMaLength = Param(nameof(TrendMaLength), 50)
			.SetDisplay("Trend MA Length", "Period for trend filter MA.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int TrendMaLength
	{
		get => _trendMaLength.Value;
		set => _trendMaLength.Value = value;
	}

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

		_macdHistory.Clear();
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowLength };
		var trendMa = new SimpleMovingAverage { Length = TrendMaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, trendMa, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, trendMa);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal trendValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var macdLine = fastValue - slowValue;

		_macdHistory.Add(macdLine);
		if (_macdHistory.Count > 5)
			_macdHistory.RemoveAt(0);

		if (_macdHistory.Count < 3)
			return;

		var close = candle.ClosePrice;
		var macd1 = _macdHistory[^1];
		var macd2 = _macdHistory[^2];
		var macd3 = _macdHistory[^3];

		// MACD reversal patterns
		var buyReversal = macd3 >= macd2 && macd2 < macd1; // V-shape bottom
		var sellReversal = macd3 <= macd2 && macd2 > macd1; // inverted V top

		// Exit conditions
		if (Position > 0 && sellReversal)
		{
			SellMarket();
		}
		else if (Position < 0 && buyReversal)
		{
			BuyMarket();
		}

		// Entry conditions with trend filter
		if (Position == 0)
		{
			if (buyReversal && close > trendValue)
			{
				BuyMarket();
			}
			else if (sellReversal && close < trendValue)
			{
				SellMarket();
			}
		}
	}
}