View on GitHub

MACD + Stochastic Trend Filter Strategy

This strategy recreates the behaviour of the MetaTrader expert advisor from folder MQL/7604. The original script relied on a custom oscillator that produced green and red buffers. In practice the numbers (15, 3, 3) match a classical stochastic oscillator, therefore the StockSharp port uses the built-in Stochastic indicator for the signal confirmation while MACD and an EMA trend filter manage direction.

The strategy trades both long and short. It waits for a stochastic crossover in the direction of the trade, requires the MACD histogram to cross its signal line with enough distance from zero, and demands that the EMA slope agrees with the entry. Risk management mirrors the MQL version: a fixed stop-loss, take-profit, and a point-based trailing stop that tightens the protective level as soon as the trade moves into profit.

Indicators

  • MovingAverageConvergenceDivergenceSignal with parameters fast = 12, slow = 26, signal = 9. The MACD histogram must cross its signal line while staying below zero for long setups and above zero for short setups. Additional thresholds (MacdOpenLevel, MacdCloseLevel) enforce a minimal absolute distance from the zero line.
  • Stochastic oscillator with (Length = 15, KPeriod = 3, DPeriod = 3). The %K line plays the role of the "green" buffer and must be above %D for long trades (below for short trades). The same crossover is used to exit positions.
  • ExponentialMovingAverage with period 26. The EMA provides a directional filter: for a long trade the current EMA value must be above the previous bar's EMA, and conversely for a short trade.

Entry Logic

  1. Long setup
    • Stochastic %K > %D on the current closed candle.
    • MACD histogram < 0 and > signal line on the current bar.
    • MACD histogram < signal line on the previous bar (i.e., bullish crossover now).
    • |MACD| > MacdOpenLevel * price_step.
    • EMA rising (current EMA > previous EMA).
  2. Short setup
    • Stochastic %K < %D on the current candle.
    • MACD histogram > 0 and < signal line on the current bar.
    • MACD histogram > signal line on the previous bar (bearish crossover now).
    • MACD > MacdOpenLevel * price_step.
    • EMA falling (current EMA < previous EMA).

If the account already holds a position, no new orders are generated until the open trade is closed.

Exit Logic

While a position is open the strategy continuously enforces:

  • Indicator exit
    • Long positions close when %K < %D, MACD > 0, MACD < signal, previous MACD was above its signal, and the absolute histogram exceeds MacdCloseLevel * price_step.
    • Short positions close when %K > %D, MACD < 0, MACD > signal, previous MACD was below its signal, and |MACD| > MacdCloseLevel * price_step.
  • Stop-loss: configured by StopLossPoints, converted into price units via the instrument's PriceStep.
  • Take-profit: TakeProfitPoints multiplied by PriceStep.
  • Trailing stop: once the profit exceeds TrailingStopPoints * PriceStep, the stop level is raised (for longs) or lowered (for shorts) so that the trade always locks in at least that amount of profit.

Parameters

Name Description Default
TradeVolume Order size in lots 0.1
TakeProfitPoints Take-profit distance in points 10
StopLossPoints Stop-loss distance in points 50
TrailingStopPoints Trailing stop distance in points 5
MacdOpenLevel Minimal absolute MACD value for entries 3
MacdCloseLevel Minimal absolute MACD value for exits 2
MacdFastPeriod Fast EMA length inside MACD 12
MacdSlowPeriod Slow EMA length inside MACD 26
MacdSignalPeriod MACD signal EMA length 9
EmaPeriod EMA period for the trend filter 26
StochasticLength Stochastic look-back window 15
StochasticKPeriod %K smoothing 3
StochasticDPeriod %D smoothing 3
CandleType Time-frame used for calculations 15m

Notes

  • All calculations use finished candles only, matching the start() loop in the original EA.
  • The PriceStep supplied by the instrument defines one point. When the security does not expose a step the strategy falls back to 1.
  • The code relies purely on StockSharp's high-level API: indicators are bound through SubscribeCandles().BindEx(...), no manual history buffers are created, and orders use BuyMarket/SellMarket like in the MQL version.
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 strategy with stochastic confirmation and EMA trend filter.
/// Buy when MACD crosses above signal with stochastic K > D and price above EMA.
/// Sell when MACD crosses below signal with stochastic K < D and price below EMA.
/// </summary>
public class MacdStochasticFilterStrategy : Strategy
{
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _stochKLength;
	private readonly StrategyParam<int> _stochDLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevMacd;
	private decimal? _prevSignal;

	/// <summary>
	/// Fast EMA period inside MACD.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period inside MACD.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for MACD.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Trend EMA period used as directional filter.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K length.
	/// </summary>
	public int StochKLength
	{
		get => _stochKLength.Value;
		set => _stochKLength.Value = value;
	}

	/// <summary>
	/// Stochastic %D length.
	/// </summary>
	public int StochDLength
	{
		get => _stochDLength.Value;
		set => _stochDLength.Value = value;
	}

	/// <summary>
	/// Type of candles used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MacdStochasticFilterStrategy()
	{
		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetDisplay("MACD Signal", "Signal EMA period for MACD", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 26)
			.SetDisplay("Trend EMA", "EMA period for trend filter", "Indicators");

		_stochKLength = Param(nameof(StochKLength), 14)
			.SetDisplay("Stochastic K", "Look-back length for stochastic K", "Indicators");

		_stochDLength = Param(nameof(StochDLength), 3)
			.SetDisplay("Stochastic D", "Smoothing for D line", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for price data", "General");
	}

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

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

		_prevMacd = null;
		_prevSignal = null;
	}

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

		_prevMacd = null;
		_prevSignal = null;

		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod }
			},
			SignalMa = { Length = MacdSignalPeriod }
		};

		var stochastic = new StochasticOscillator
		{
			K = { Length = StochKLength },
			D = { Length = StochDLength }
		};

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, stochastic, ema, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue stochValue, IIndicatorValue emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!macdValue.IsFinal || !stochValue.IsFinal || !emaValue.IsFinal)
			return;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData)
			return;

		if (macdData.Macd is not decimal macd || macdData.Signal is not decimal signal)
			return;

		if (stochValue is not StochasticOscillatorValue stochData)
			return;

		if (stochData.K is not decimal kVal || stochData.D is not decimal dVal)
			return;

		var emaVal = emaValue.ToDecimal();

		if (_prevMacd is not decimal prevMacd || _prevSignal is not decimal prevSignal)
		{
			_prevMacd = macd;
			_prevSignal = signal;
			return;
		}

		// MACD crossover signals
		var macdBullishCross = prevMacd <= prevSignal && macd > signal;
		var macdBearishCross = prevMacd >= prevSignal && macd < signal;

		// Long: MACD bullish cross + stochastic K > D + price above EMA
		if (Position <= 0 && macdBullishCross && kVal > dVal && candle.ClosePrice > emaVal)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Short: MACD bearish cross + stochastic K < D + price below EMA
		else if (Position >= 0 && macdBearishCross && kVal < dVal && candle.ClosePrice < emaVal)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMacd = macd;
		_prevSignal = signal;
	}
}