Auf GitHub ansehen

MACD Sample Classic Strategy

The strategy reproduces the MetaTrader 4 "MACD Sample" expert advisor using StockSharp's high-level API. It trades both directions on a single instrument and mirrors the original logic: take trades when the MACD line crosses its signal line on the correct side of zero while a trend EMA confirms the direction. Protective orders are converted to StockSharp's built-in risk manager with optional trailing stops.

Trading Logic

  1. Wait for at least 100 finished candles so that MACD and EMA contain enough history.
  2. Calculate a standard MACD (12, 26, 9) together with its signal line and a 26-period exponential moving average that acts as a directional filter.
  3. Long entry – allowed only when no position exists. The MACD must be below zero yet crossing above the signal line, the previous MACD value was below its signal, the absolute MACD value exceeds the configurable MacdOpenLevel threshold (in price points) and the trend EMA is rising.
  4. Short entry – the symmetric setup: MACD above zero crossing below its signal, previous MACD was above the signal, the current value exceeds the MacdOpenLevel threshold and the trend EMA is falling.
  5. Long exit – when MACD crosses back under the signal on the positive side of zero and the value is above MacdCloseLevel. The position can also be closed earlier by the trailing stop or take-profit managed by StartProtection.
  6. Short exit – when MACD crosses back over the signal on the negative side and the absolute MACD value exceeds MacdCloseLevel, or by the protective modules.

The strategy never holds more than one position at a time. Every entry uses market orders sized by the Volume property. Protective logic relies on StockSharp's risk controller so take-profit distances and trailing stops remain synchronized with the instrument tick size.

Parameters

Name Description Default Notes
FastEmaPeriod Fast EMA period used by MACD. 12 Optimizable range 6…18.
SlowEmaPeriod Slow EMA period used by MACD. 26 Optimizable range 20…32.
SignalPeriod Signal EMA period within MACD. 9 Optimizable range 5…13.
TrendMaPeriod EMA length for the directional filter. 26 Optimizable range 20…40.
MacdOpenLevel Entry threshold expressed in MACD points (price steps). 3 Equivalent to MACDOpenLevel in MT4 code.
MacdCloseLevel Exit threshold expressed in MACD points. 2 Equivalent to MACDCloseLevel.
TakeProfitPoints Take profit in price points (multiplied by the instrument tick size). 50 Set to 0 to disable take profit.
TrailingStopPoints Trailing stop in price points. 30 Set to 0 to disable trailing stop.
CandleType Candle series used for indicator updates. 5-minute time frame Supports any StockSharp candle type.

Implementation Notes

  • The MACD and EMA indicators are bound to the candle subscription through BindEx/Bind, letting StockSharp feed ready-to-use values without manual caching.
  • Positions are opened only when the platform reports IsFormedAndOnlineAndAllowTrading(), preventing trades while historical data is still loading or the connection is offline.
  • All thresholds that refer to "points" are automatically scaled by the instrument price step, mimicking MetaTrader's Point constant.
  • StartProtection converts MetaTrader's fixed take-profit and trailing stop into exchange-side protective orders. Enable or disable each module by changing the corresponding parameter.
  • Extensive logging (LogInfo) documents each trade decision, simplifying comparison with the original expert advisor during migration validation.

Usage Tips

  • The original EA targets forex majors on intraday time frames. Start with similar symbols and adjust parameters if the instrument uses a different tick size.
  • When testing symbols with exotic tick values, verify Security.PriceStep is configured; otherwise the default of 1.0 will be used.
  • Combine with StockSharp's portfolio protection features if you need account-level money management beyond per-position stops.

Tags

  • Trend following
  • Momentum
  • MACD crossover
  • Intraday (default 5-minute)
  • Trailing stop + take profit
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>
/// MACD strategy that replicates the original MetaTrader "MACD Sample" expert advisor behaviour.
/// </summary>
public class MacdSampleClassicStrategy : Strategy
{

	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _trendMaPeriod;
	private readonly StrategyParam<decimal> _macdOpenLevel;
	private readonly StrategyParam<decimal> _macdCloseLevel;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _minimumHistoryCandles;

	private decimal _pointSize;
	private decimal? _prevMacd;
	private decimal? _prevSignal;
	private decimal? _trendMaCurrent;
	private decimal? _trendMaPrevious;
	private int _finishedCandles;
	private DateTimeOffset? _lastProcessedTime;

	/// <summary>
	/// Fast EMA period for the MACD indicator.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for the MACD indicator.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Period of the trend EMA used as direction filter.
	/// </summary>
	public int TrendMaPeriod
	{
		get => _trendMaPeriod.Value;
		set => _trendMaPeriod.Value = value;
	}

	/// <summary>
	/// Threshold for MACD entries expressed in points (price steps).
	/// </summary>
	public decimal MacdOpenLevel
	{
		get => _macdOpenLevel.Value;
		set => _macdOpenLevel.Value = value;
	}

	/// <summary>
	/// Threshold for MACD exits expressed in points (price steps).
	/// </summary>
	public decimal MacdCloseLevel
	{
		get => _macdCloseLevel.Value;
		set => _macdCloseLevel.Value = value;
	}

	/// <summary>
	/// Take profit distance measured in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance measured in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	/// <summary>
	/// Number of finished candles required before the strategy begins trading.
	/// </summary>
	public int MinimumHistoryCandles
	{
		get => _minimumHistoryCandles.Value;
		set => _minimumHistoryCandles.Value = value;
	}

	/// <summary>
	/// Initialize default parameters for the MACD Sample strategy.
	/// </summary>
	public MacdSampleClassicStrategy()
	{
		Volume = 1;

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
		.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
		
		.SetOptimize(6, 18, 2);

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
		.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
		
		.SetOptimize(20, 32, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
		.SetDisplay("Signal EMA", "Signal EMA period for MACD", "Indicators")
		
		.SetOptimize(5, 13, 2);

		_trendMaPeriod = Param(nameof(TrendMaPeriod), 26)
		.SetDisplay("Trend EMA", "EMA period used for directional filter", "Indicators")
		
		.SetOptimize(20, 40, 2);

		_macdOpenLevel = Param(nameof(MacdOpenLevel), 0m)
		.SetDisplay("MACD Open", "Entry threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 5m, 1m);

		_macdCloseLevel = Param(nameof(MacdCloseLevel), 0m)
		.SetDisplay("MACD Close", "Exit threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 4m, 1m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
		.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
		
		.SetOptimize(20m, 100m, 10m);

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
		.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
		
		.SetOptimize(10m, 60m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used for analysis", "General");
		_minimumHistoryCandles = Param(nameof(MinimumHistoryCandles), 30)
			.SetDisplay("Warm-up candles", "Number of finished candles required before trading starts", "General")
			.SetGreaterThanZero();
	}

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

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

		_pointSize = 0m;
		_prevMacd = null;
		_prevSignal = null;
		_trendMaCurrent = null;
		_trendMaPrevious = null;
		_finishedCandles = 0;
		_lastProcessedTime = null;
	}

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

		_pointSize = Security?.PriceStep ?? 1m;

		// Configure indicators exactly as in the original expert advisor.
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

		var trendMa = new ExponentialMovingAverage { Length = TrendMaPeriod };

		// Subscribe to candles and bind indicators for automatic updates.
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, ProcessMacdValues);
		subscription.Bind(trendMa, ProcessTrendMaValue);
		subscription.Start();

		// Visualize price, indicators and trades when a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, macd);
			DrawIndicator(area, trendMa);
			DrawOwnTrades(area);
		}

		var takeProfitDistance = TakeProfitPoints * _pointSize;
		var trailingDistance = TrailingStopPoints * _pointSize;

		if (takeProfitDistance > 0m || trailingDistance > 0m)
		{
			StartProtection(
			takeProfitDistance > 0m ? new Unit(takeProfitDistance, UnitTypes.Absolute) : null,
			trailingDistance > 0m ? new Unit(trailingDistance, UnitTypes.Absolute) : null,
			isStopTrailing: trailingDistance > 0m);
		}
	}

	private void ProcessTrendMaValue(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Keep current and previous EMA values for the directional filter.
		_trendMaPrevious = _trendMaCurrent;
		_trendMaCurrent = maValue;
	}

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

		// Avoid duplicate processing for the same candle.
		if (_lastProcessedTime != candle.OpenTime)
		{
			_lastProcessedTime = candle.OpenTime;
			_finishedCandles++;
		}

		// indicators checked below

		if (_finishedCandles < MinimumHistoryCandles)
		return;

		var macdSignal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		if (macdSignal.Macd is not decimal macdCurrent ||
		macdSignal.Signal is not decimal signalCurrent)
		{
			return;
		}

		if (_prevMacd is not decimal macdPrevious ||
		_prevSignal is not decimal signalPrevious ||
		_trendMaCurrent is not decimal trendMaCurrent ||
		_trendMaPrevious is not decimal trendMaPrevious)
		{
			_prevMacd = macdCurrent;
			_prevSignal = signalCurrent;
			return;
		}

		var macdOpenThreshold = MacdOpenLevel * _pointSize;
		var macdCloseThreshold = MacdCloseLevel * _pointSize;

		// Determine trend direction using EMA slope.
		var isTrendUp = trendMaCurrent > trendMaPrevious;
		var isTrendDown = trendMaCurrent < trendMaPrevious;

		var buySignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdOpenThreshold &&
		isTrendUp;

		var sellSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdOpenThreshold &&
		isTrendDown;

		var exitLongSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdCloseThreshold;

		var exitShortSignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdCloseThreshold;

		if (buySignal && Position == 0m)
		{
			// MACD crossed up in negative territory and EMA confirms uptrend.
			BuyMarket();
			LogInfo($"Open long: MACD {macdCurrent:F5} above signal {signalCurrent:F5}.");
		}
		else if (sellSignal && Position == 0m)
		{
			// MACD crossed down in positive territory and EMA confirms downtrend.
			SellMarket();
			LogInfo($"Open short: MACD {macdCurrent:F5} below signal {signalCurrent:F5}.");
		}
		else if (exitLongSignal && Position > 0m)
		{
			// MACD crossed back below the signal line in positive zone - close long.
			SellMarket();
			LogInfo($"Close long: MACD {macdCurrent:F5} dropped under signal {signalCurrent:F5}.");
		}
		else if (exitShortSignal && Position < 0m)
		{
			// MACD crossed back above the signal line in negative zone - close short.
			BuyMarket();
			LogInfo($"Close short: MACD {macdCurrent:F5} rose above signal {signalCurrent:F5}.");
		}

		_prevMacd = macdCurrent;
		_prevSignal = signalCurrent;
	}
}