Ver no GitHub

Consolidation Breakout Strategy

This strategy reproduces the core behaviour of the original Consolidation Breakout expert advisor for MetaTrader. It looks for tight consolidations confirmed by momentum and MACD filters, then opens a position in the direction of the breakout. Risk is managed through fixed take-profit and stop-loss distances measured in price steps (pips).

How it works

  1. The primary timeframe is defined by the CandleType parameter. All trend and consolidation checks are evaluated on these candles.
  2. Two linear weighted moving averages (LWMAs) computed on the typical price provide the directional filter. Long setups require the fast LWMA to stay above the slow LWMA, while short setups need the opposite alignment.
  3. A consolidation is detected when the low of the candle two bars ago remains below the high of the previous candle (long case) or when the previous low sits below the high from two bars ago (short case). This mirrors the overlapping-bar logic from the MQL version.
  4. Momentum must confirm the move. The absolute momentum value (relative to zero) needs to exceed the respective buy or sell threshold. This approximates the original expert's momentum filter around the 100 level.
  5. A separate MACD calculated on the MacdCandleType timeframe must agree with the trade direction. The strategy checks whether the MACD line leads the signal line on both the positive and negative sides of the axis, reproducing the multi-timeframe confirmation from the source code.
  6. When all filters align and the account is flat or positioned in the opposite direction, the strategy submits a market order sized by TradeVolume. Protective levels are immediately recalculated in price steps so that intrabar extremes can trigger exits.
  7. Every finished candle also monitors active positions. If the candle range touches either the stop-loss or the take-profit level, the strategy closes the position at market and resets the protection targets.

Indicators

  • Linear Weighted Moving Average (fast and slow, typical price)
  • Momentum
  • MACD (with 12/26/9 periods on a higher timeframe)

Parameters

  • CandleType – primary timeframe used for breakout detection.
  • MacdCandleType – timeframe used for the confirming MACD filter.
  • FastMaPeriod – length of the fast LWMA.
  • SlowMaPeriod – length of the slow LWMA.
  • MomentumLength – lookback for the momentum filter.
  • MomentumBuyThreshold – minimum positive momentum required for long trades.
  • MomentumSellThreshold – minimum negative momentum required for short trades (expressed as absolute value).
  • StopLossPips – protective stop distance in price steps.
  • TakeProfitPips – profit target distance in price steps.
  • TradeVolume – volume submitted with each market order.

The defaults mirror the published expert advisor: LWMA periods of 6 and 85, momentum length 14, buy/sell thresholds of 0.3, stop-loss of 20 pips, and take-profit of 50 pips. Adjust the pip-based distances when trading instruments with different price steps.

Notes

  • Trailing stops, break-even moves, and money-management modules from the MQL script are intentionally omitted to keep the StockSharp port focused on the core breakout logic.
  • Always ensure the selected timeframes are supported by your data feed. If the higher timeframe produces sparse data, consider switching to a lower MacdCandleType to keep the MACD filter responsive.
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 ConsolidationBreakoutStrategy : 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 ConsolidationBreakoutStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).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;
	}
}