Ver no GitHub

Macd Pattern Trader DoubleTop Strategy

Overview

Port of the MetaTrader 4 expert advisor MacdPatternTraderv04cb. The strategy scans a configurable MACD main line for bearish double-top and bullish double-bottom patterns. When the second swing fails to exceed the first one while the MACD remains beyond a positive or negative trigger level, the strategy opens a market position in the direction of the anticipated reversal. Protective orders reproduce the original fixed 100 pip stop-loss and 300 pip take-profit distances.

Trading rules

  1. Subscribe to the selected candle series (default: 30-minute time frame) and calculate the MACD main line with the configured fast, slow and signal periods (defaults: 5, 13 and 1).
  2. Track the last three finished MACD values. A bearish setup is armed once the MACD stays above the TriggerLevel, forms a local high and then declines. The setup validates when the next MACD high is lower than the previously stored high while the MACD is still above the trigger. A market sell is sent at that moment.
  3. Mirror the same logic below zero. When the MACD remains below -TriggerLevel, forms a trough and the following trough is higher than the previous one, the strategy opens a market buy.
  4. Reset the stored peaks and troughs whenever the MACD line crosses back inside the [-TriggerLevel, TriggerLevel] range. This matches the original EA behaviour that cancels the pattern search when the momentum loses strength.
  5. Position sizes start from the configured TradeVolume. When switching direction, the strategy adds enough volume to flatten the opposite exposure before establishing the new trade.
  6. Call StartProtection once on start so that both the 100 pip stop-loss and the 300 pip take-profit are managed by the platform even after restarts.

Parameters

Name Description
FastPeriod Fast EMA length used by MACD.
SlowPeriod Slow EMA length used by MACD.
SignalPeriod Signal line smoothing length for MACD.
TriggerLevel Absolute MACD level required to arm the double-top/double-bottom detection.
StopLossPips Distance of the protective stop in pips (default 100).
TakeProfitPips Distance of the take-profit in pips (default 300).
TradeVolume Base order volume for new positions.
CandleType Candle series used for indicator calculations.

Notes

  • The stop-loss and take-profit are converted from pips into instrument steps before they are passed to StartProtection, keeping the behaviour identical to the original MQL4 expert.
  • All indicator and trading comments inside the C# source code are written in English, as required by the repository guidelines.
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;

/// <summary>
/// Port of the MetaTrader 4 expert advisor MacdPatternTraderv04cb.
/// Detects bearish and bullish double-top/double-bottom patterns on the MACD main line.
/// The second swing needs to be weaker than the first one before a market order is sent.
/// Fixed protective orders replicate the original 100/300 pip stop-loss and take-profit distances.
/// </summary>
public class MacdPatternTraderDoubleTopStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _triggerLevel;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private decimal? _previousMacd;
	private decimal? _previousMacd2;
	private decimal? _firstPeak;
	private decimal? _firstTrough;
	private bool _sellPatternArmed;
	private bool _buyPatternArmed;

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public MacdPatternTraderDoubleTopStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast moving average length used by MACD", "MACD")
		
		.SetOptimize(3, 15, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 13)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow moving average length used by MACD", "MACD")
		
		.SetOptimize(10, 30, 1);

		_signalPeriod = Param(nameof(SignalPeriod), 1)
		.SetGreaterThanZero()
		.SetDisplay("Signal EMA", "Signal smoothing period for MACD", "MACD")
		
		.SetOptimize(1, 9, 1);

		_triggerLevel = Param(nameof(TriggerLevel), 50m)
		.SetNotNegative()
		.SetDisplay("Trigger Level", "Absolute MACD level that arms the pattern logic", "MACD");

		_stopLossPips = Param(nameof(StopLossPips), 100m)
		.SetNotNegative()
		.SetDisplay("Stop-Loss (pips)", "Stop-loss distance expressed in pips", "Risk Management")
		
		.SetOptimize(50m, 200m, 10m);

		_takeProfitPips = Param(nameof(TakeProfitPips), 300m)
		.SetNotNegative()
		.SetDisplay("Take-Profit (pips)", "Take-profit distance expressed in pips", "Risk Management")
		
		.SetOptimize(150m, 500m, 10m);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
		.SetGreaterThanZero()
		.SetDisplay("Trade Volume", "Order volume used for new entries", "Trading")
		
		.SetOptimize(0.05m, 1m, 0.05m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for MACD calculations", "General");
	}

/// <summary>
/// Fast EMA length used by MACD.
/// </summary>
public int FastPeriod
{
	get => _fastPeriod.Value;
	set => _fastPeriod.Value = value;
}

/// <summary>
/// Slow EMA length used by MACD.
/// </summary>
public int SlowPeriod
{
	get => _slowPeriod.Value;
	set => _slowPeriod.Value = value;
}

/// <summary>
/// Signal EMA length used by MACD.
/// </summary>
public int SignalPeriod
{
	get => _signalPeriod.Value;
	set => _signalPeriod.Value = value;
}

/// <summary>
/// Absolute MACD level that enables pattern tracking.
/// </summary>
public decimal TriggerLevel
{
	get => _triggerLevel.Value;
	set => _triggerLevel.Value = value;
}

/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
	get => _stopLossPips.Value;
	set => _stopLossPips.Value = value;
}

/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
	get => _takeProfitPips.Value;
	set => _takeProfitPips.Value = value;
}

/// <summary>
/// Trading volume used for new market orders.
/// </summary>
public decimal TradeVolume
{
	get => _tradeVolume.Value;
	set => _tradeVolume.Value = value;
}

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

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

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

	_previousMacd = null;
	_previousMacd2 = null;
	_firstPeak = null;
	_firstTrough = null;
	_sellPatternArmed = false;
	_buyPatternArmed = false;
}

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

	Volume = TradeVolume;

	_macd = new MovingAverageConvergenceDivergenceSignal
	{
		Macd =
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		},
	SignalMa = { Length = SignalPeriod },
};

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

var pipSize = Security?.PriceStep ?? 0.01m;
if (pipSize <= 0m) pipSize = 0.01m;

StartProtection(
	takeProfit: TakeProfitPips > 0m ? new Unit(TakeProfitPips * pipSize, UnitTypes.Absolute) : null,
	stopLoss: StopLossPips > 0m ? new Unit(StopLossPips * pipSize, UnitTypes.Absolute) : null,
	useMarketOrders: true);
}

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

	if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue typed)
	return;

	if (typed.Macd is not decimal macdLine)
	return;

	var previous = _previousMacd;
	var previous2 = _previousMacd2;

	if (previous.HasValue && previous2.HasValue)
	{
		ProcessSellPattern(macdLine, previous.Value, previous2.Value);
		ProcessBuyPattern(macdLine, previous.Value, previous2.Value);
	}

_previousMacd2 = previous;
_previousMacd = macdLine;
}

private void ProcessSellPattern(decimal current, decimal previous, decimal previous2)
{
	if (current > TriggerLevel && current < previous && previous > previous2)
	{
		if (!_sellPatternArmed)
		{
			_firstPeak = previous;
			_sellPatternArmed = true;
		}
	else if (_firstPeak is decimal referencePeak && previous < referencePeak)
	{
		EnterShort();
		ResetSellPattern();
	}
}
else if (current < TriggerLevel)
{
	ResetSellPattern();
}
}

private void ProcessBuyPattern(decimal current, decimal previous, decimal previous2)
{
	var negativeTrigger = -TriggerLevel;

	if (current < negativeTrigger && current > previous && previous < previous2)
	{
		if (!_buyPatternArmed)
		{
			_firstTrough = previous;
			_buyPatternArmed = true;
		}
	else if (_firstTrough is decimal referenceTrough && previous > referenceTrough)
	{
		EnterLong();
		ResetBuyPattern();
	}
}
else if (current > negativeTrigger)
{
	ResetBuyPattern();
}
}

private void EnterShort()
{
	if (!IsFormedAndOnlineAndAllowTrading())
	return;

	var volume = Volume + Math.Max(0m, Position);
	if (volume <= 0m)
	return;

	SellMarket(volume);
}

private void EnterLong()
{
	if (!IsFormedAndOnlineAndAllowTrading())
	return;

	var volume = Volume + Math.Max(0m, -Position);
	if (volume <= 0m)
	return;

	BuyMarket(volume);
}

private void ResetSellPattern()
{
	_sellPatternArmed = false;
	_firstPeak = null;
}

private void ResetBuyPattern()
{
	_buyPatternArmed = false;
	_firstTrough = null;
}

}