Ver en GitHub

MACD Cleaner Strategy

Overview

The MACD Cleaner strategy is a conversion of the "MACD Cleaner" MetaTrader 5 expert advisor. It analyses completed candles from a single timeframe and places trades when the MACD main line increases or decreases monotonically during three consecutive closed bars. The system always keeps at most one directional position and flips when the momentum reverses.

Trading Logic

  • On every finished candle the strategy reads the MACD line calculated with the configured fast, slow, and signal periods.
  • If the last three MACD values are non-decreasing, the strategy prepares a long entry. If a short position exists it is closed first, then a new long position is opened.
  • If the last three MACD values are non-increasing, the strategy prepares a short entry. Existing long positions are flattened before opening the short.
  • Protective stop-loss and take-profit levels are evaluated on candle highs and lows using the pip-based offsets.
  • When trailing parameters are enabled the stop is pulled in the trade direction once the price progresses by at least the configured trailing step.
  • All exit orders are issued as market orders using the aggregated position volume to ensure the entire position is closed.

Parameters

Name Default Description
CandleType 1 hour candles Timeframe used for MACD calculation and order evaluation.
TradeVolume 1 Base volume submitted for a new position. If the opposite side is open the absolute position volume is added to close it before reversing.
StopLossPips 35 Stop-loss distance in pips from the entry price. Set to zero to disable the stop.
TakeProfitPips 30 Take-profit distance in pips from the entry price. Set to zero to disable the target.
TrailingStopPips 0 Trailing stop distance. When zero the trailing logic is disabled.
TrailingStepPips 5 Minimum favourable move (in pips) required before the trailing stop is adjusted. Ignored when the trailing stop is disabled.
MacdFastPeriod 15 Fast EMA length for the MACD indicator.
MacdSlowPeriod 33 Slow EMA length for the MACD indicator.
MacdSignalPeriod 11 Signal EMA length for the MACD indicator.

Order Management

  • Long exits: the strategy issues a market sell order when the stop-loss, take-profit, or trailing level is hit.
  • Short exits: a market buy order closes the position under the same conditions, mirrored for short trades.
  • After the position is fully closed the trailing state is reset so that the next trade starts with fresh levels.

Notes

  • Pip size is automatically derived from the instrument. For symbols with 3 or 5 decimal places the pip equals ten minimal price steps, mimicking the original MetaTrader implementation.
  • The logic only evaluates completed candles and does not act on intrabar changes.
  • To disable risk management set the corresponding pip distances to zero. Trailing requires both TrailingStopPips and a positive TrailingStepPips to work.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class MacdCleanerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MacdCleanerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}