View on GitHub

MACD Waterline Cross Expectator Strategy

This strategy goes long when the MACD signal line crosses above the zero level and goes short when it crosses below. Risk management uses a stop loss and a configurable risk‑reward multiplier to set the take profit distance.

Logic

  • Calculate the MACD indicator with configurable fast EMA, slow EMA and signal periods.
  • Track the signal line value on each finished candle.
  • When the signal line crosses from negative to positive and the strategy is ready to buy, a long market order is placed.
  • When the signal line crosses from positive to negative and the strategy is ready to sell, a short market order is placed.
  • Protective stop loss and take profit levels are set automatically for each new position.

Parameters

  • FastEmaPeriod – length of the fast EMA used in MACD.
  • SlowEmaPeriod – length of the slow EMA used in MACD.
  • SignalPeriod – length of the signal line EMA.
  • StopLoss – distance to the stop loss in absolute price units.
  • Volume – order size used for new positions.
  • RiskBenefitRatios – preset ratios from 1:5 to 1:1 which define take profit distance.
  • CandleType – time frame of candles used by the strategy.

Notes

  • The strategy alternates between long and short trades using an internal flag.
  • Trades are executed at market prices and always close and reverse the current position.
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 that trades on MACD signal line crossing the zero level.
/// </summary>
public class MacdWaterlineCrossExpectatorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _rrMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private bool _shouldBuy;
	private bool _hasPrev;
	private decimal _prevSignal;

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

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

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

	/// <summary>
	/// Stop loss percentage.
	/// </summary>
	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	/// <summary>
	/// Risk reward multiplier.
	/// </summary>
	public decimal RRMultiplier
	{
		get => _rrMultiplier.Value;
		set => _rrMultiplier.Value = value;
	}

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

	/// <summary>
	/// Initializes <see cref="MacdWaterlineCrossExpectatorStrategy"/>.
	/// </summary>
	public MacdWaterlineCrossExpectatorStrategy()
	{
		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetDisplay("Signal", "Signal line period", "Indicators");
		_stopLossPct = Param(nameof(StopLossPct), 1m)
			.SetDisplay("Stop Loss %", "Stop loss percent", "Risk")
			.SetGreaterThanZero();
		_rrMultiplier = Param(nameof(RRMultiplier), 2m)
			.SetDisplay("RR Multiplier", "Risk reward multiplier", "Risk")
			.SetGreaterThanZero();
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle", "Candle time frame", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_shouldBuy = true;
		_hasPrev = false;
		_prevSignal = 0;
	}

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

		StartProtection(
			takeProfit: new Unit(StopLossPct * RRMultiplier, UnitTypes.Percent),
			stopLoss: new Unit(StopLossPct, UnitTypes.Percent)
		);

		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

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

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

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

		if (!macdValue.IsFinal || !macdValue.IsFormed)
			return;

		var typed = (IMovingAverageConvergenceDivergenceSignalValue)macdValue;
		var signal = typed.Signal;

		if (signal == null)
			return;

		var signalVal = signal.Value;

		if (!_hasPrev)
		{
			_prevSignal = signalVal;
			_hasPrev = true;
			return;
		}

		var crossedAbove = _prevSignal < 0 && signalVal > 0;
		var crossedBelow = _prevSignal > 0 && signalVal < 0;

		if (crossedAbove && _shouldBuy)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_shouldBuy = false;
		}
		else if (crossedBelow && !_shouldBuy)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_shouldBuy = true;
		}

		_prevSignal = signalVal;
	}
}