Ver no GitHub

Estratégia Momo Trades

Conversão do expert advisor original do MetaTrader "Momo_trades" que opera rompimentos de momentum filtrados por uma média móvel e estrutura do MACD.

Lógica da estratégia

  • Trabalha com velas concluídas do período configurado e processa apenas uma posição líquida por vez.
  • Usa uma média móvel simples com um deslocamento de barra configurável para medir o quanto o preço fechou afastado da média. Operações compradas exigem que o fechamento deslocado esteja acima da SMA em mais do que o limiar de deslocamento de preço; vendidas exigem o contrário.
  • Avalia um padrão de momentum MACD em cascata que espelha as regras MQL: vários valores passados da linha principal MACD devem aumentar através de zero para comprados ou diminuir através de zero para vendidos. Isso evita operações enquanto o momentum está se enfraquecendo.
  • Abre uma ordem a mercado com o volume da estratégia assim que tanto o filtro de distância SMA quanto o padrão MACD se alinham para a mesma direção.

Gestão de risco

  • Stop-loss, take-profit, trailing stop, passo de trailing, break-even e inputs de deslocamento de preço são definidos em pips e automaticamente convertidos para unidades de preço usando o passo do instrumento.
  • Quando valores de take-profit e trailing são fornecidos, o stop só é arrastado após o preço avançar pela distância de trailing mais o passo de trailing, reproduzindo o comportamento MQL.
  • Quando nenhum take-profit está configurado mas uma distância de break-even está definida, o stop é movido para o preço de entrada assim que o gatilho de break-even é atingido.
  • Todos os níveis de stop e take são recalculados a cada vela concluída e fechados por ordens a mercado quando cruzados pelos extremos da vela.

Gestão de sessão

  • A bandeira CloseEndDay corresponde ao expert advisor original e fecha qualquer posição ativa às 23h00 hora da plataforma (21h00 nas sextas-feiras). Após o corte, a estratégia ignora novas entradas até o dia seguinte.

Parâmetros

  • SMA Period / MA Bar Shift – comprimento da média móvel e o índice de barra usado para obter valores de SMA e preço.
  • MACD Fast / Slow / Signal / Bar Shift – configuração do MACD e o deslocamento aplicado aos valores armazenados para verificações de padrão.
  • Stop Loss / Take Profit / Trailing Stop / Trailing Step / Breakeven / Price Shift – distâncias em pips que controlam a saída, o trailing e os filtros de SMA.
  • Close End Of Day – fecha posições após o fim de sessão configurado.
  • Candle Type – período usado para velas e cálculos de indicadores.
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>
/// Momentum strategy based on MACD momentum and distance from SMA similar to the original MQL logic.
/// </summary>
public class MomoTradesStrategy : Strategy
{
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _maBarShift;
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<int> _macdSignal;
	private readonly StrategyParam<int> _macdBarShift;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _breakevenPips;
	private readonly StrategyParam<decimal> _priceShiftPips;
	private readonly StrategyParam<bool> _closeEndDay;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private MovingAverageConvergenceDivergence _macd;
	// Indicators follow the same configuration as in the MQL script.

	private readonly decimal[] _macdHistory = new decimal[64];
	private readonly decimal[] _maHistory = new decimal[64];
	private readonly decimal[] _closeHistory = new decimal[64];
	// Buffers store the recent history required for shifted indicator access.

	private int _macdCount;
	private int _maCount;
	private int _closeCount;

	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal? _breakevenTrigger;
	private decimal? _trailingDistance;
	private decimal? _trailingStep;
	private bool _isLongPosition;
	private int _cooldownCounter;
	// Position management state persists between candles.

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public int MaBarShift
	{
		get => _maBarShift.Value;
		set => _maBarShift.Value = value;
	}

	public int MacdFast
	{
		get => _macdFast.Value;
		set => _macdFast.Value = value;
	}

	public int MacdSlow
	{
		get => _macdSlow.Value;
		set => _macdSlow.Value = value;
	}

	public int MacdSignal
	{
		get => _macdSignal.Value;
		set => _macdSignal.Value = value;
	}

	public int MacdBarShift
	{
		get => _macdBarShift.Value;
		set => _macdBarShift.Value = value;
	}

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	public decimal BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}

	public decimal PriceShiftPips
	{
		get => _priceShiftPips.Value;
		set => _priceShiftPips.Value = value;
	}

	public bool CloseEndDay
	{
		get => _closeEndDay.Value;
		set => _closeEndDay.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MomoTradesStrategy()
	{
		_smaPeriod = Param(nameof(SmaPeriod), 22).SetGreaterThanZero().SetDisplay("SMA Period", "Period of the moving average", "Indicators");
		_maBarShift = Param(nameof(MaBarShift), 6).SetNotNegative().SetDisplay("MA Bar Shift", "Bar shift used for SMA comparison", "Indicators");
		_macdFast = Param(nameof(MacdFast), 12).SetGreaterThanZero().SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicators");
		_macdSlow = Param(nameof(MacdSlow), 26).SetGreaterThanZero().SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicators");
		_macdSignal = Param(nameof(MacdSignal), 9).SetGreaterThanZero().SetDisplay("MACD Signal", "Signal SMA period for MACD", "Indicators");
		_macdBarShift = Param(nameof(MacdBarShift), 2).SetNotNegative().SetDisplay("MACD Bar Shift", "Offset applied to MACD values", "Indicators");
		_stopLossPips = Param(nameof(StopLossPips), 25m).SetNotNegative().SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");
		_takeProfitPips = Param(nameof(TakeProfitPips), 0m).SetNotNegative().SetDisplay("Take Profit", "Take profit distance in pips", "Risk");
		_trailingStopPips = Param(nameof(TrailingStopPips), 0m).SetNotNegative().SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk");
		_trailingStepPips = Param(nameof(TrailingStepPips), 5m).SetNotNegative().SetDisplay("Trailing Step", "Trailing step distance in pips", "Risk");
		_breakevenPips = Param(nameof(BreakevenPips), 10m).SetNotNegative().SetDisplay("Breakeven", "Distance to move stop to breakeven", "Risk");
		_priceShiftPips = Param(nameof(PriceShiftPips), 5m).SetNotNegative().SetDisplay("Price Shift", "Required price distance from SMA", "Filters");
		_closeEndDay = Param(nameof(CloseEndDay), true).SetDisplay("Close End Of Day", "Close positions near session end", "Risk");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Source candles for calculations", "General");

		Volume = 1m;
	}

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

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

		Array.Clear(_macdHistory, 0, _macdHistory.Length);
		Array.Clear(_maHistory, 0, _maHistory.Length);
		Array.Clear(_closeHistory, 0, _closeHistory.Length);

		_macdCount = 0;
		_maCount = 0;
		_closeCount = 0;

		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_breakevenTrigger = null;
		_trailingDistance = null;
		_trailingStep = null;
		_isLongPosition = false;
		_cooldownCounter = 0;
	}

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

		_sma = new SimpleMovingAverage { Length = SmaPeriod };
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = MacdFast },
			LongMa = { Length = MacdSlow },
		};

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(_sma, _macd, ProcessCandle).Start();
	}

	// Process each finished candle to evaluate entries with indicator filters.
	private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal macdValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_sma.IsFormed || !_macd.IsFormed)
			return;

		PushValue(_closeHistory, ref _closeCount, candle.ClosePrice);
		PushValue(_maHistory, ref _maCount, smaValue);
		PushValue(_macdHistory, ref _macdCount, macdValue);
		// Cache the latest values so shifted lookups mimic the MQL buffer usage.

		ManageActivePosition(candle);

		if (_cooldownCounter > 0)
		{
			_cooldownCounter--;
			return;
		}

		if (Position != 0)
			return;

		if (CloseEndDay && ShouldCloseForDay(candle))
			return;

		if (!TryGetHistoryValue(_closeHistory, _closeCount, MaBarShift, out var shiftedClose))
			return;

		if (!TryGetHistoryValue(_maHistory, _maCount, MaBarShift, out var shiftedMa))
			return;

		var priceShift = GetPipValue(PriceShiftPips);

		var emaBuy = shiftedClose - shiftedMa > priceShift;
		var emaSell = shiftedMa - shiftedClose > priceShift;

		var macdBuy = CheckMacdPattern(true);
		var macdSell = CheckMacdPattern(false);

		if (macdBuy && emaBuy)
		{
			EnterLong(candle.ClosePrice);
			_cooldownCounter = 5;
		}
		else if (macdSell && emaSell)
		{
			EnterShort(candle.ClosePrice);
			_cooldownCounter = 5;
		}
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position == 0)
		{
			return;
		}

		if (CloseEndDay && ShouldCloseForDay(candle))
		{
			ClosePosition();
			return;
		}

		var close = candle.ClosePrice;
		// Adjust stop levels according to trailing or breakeven rules before exits.

		if (_trailingDistance.HasValue && _trailingStep.HasValue)
		{
			if (_isLongPosition)
			{
				if (close - _entryPrice > _trailingDistance.Value + _trailingStep.Value)
				{
					var newStop = close - _trailingDistance.Value;
					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}
			else
			{
				if (_entryPrice - close > _trailingDistance.Value + _trailingStep.Value)
				{
					var newStop = close + _trailingDistance.Value;
					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}
		}
		else if (_breakevenTrigger.HasValue)
		{
			if (_isLongPosition)
			{
				if (close > _breakevenTrigger.Value)
				{
					_stopPrice = _entryPrice;
					_breakevenTrigger = null;
				}
			}
			else
			{
				if (close < _breakevenTrigger.Value)
				{
					_stopPrice = _entryPrice;
					_breakevenTrigger = null;
				}
			}
		}

		if (_isLongPosition)
		{
			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
			}
		}
		else
		{
			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
			}
		}
	}

	private void EnterLong(decimal price)
	// Configure protective levels immediately after a long entry.
	{
		BuyMarket(Volume);

		_entryPrice = price;
		_isLongPosition = true;

		var stop = GetPipValue(StopLossPips);
		var take = GetPipValue(TakeProfitPips);
		var trail = GetPipValue(TrailingStopPips);
		var step = GetPipValue(TrailingStepPips);
		var breakeven = GetPipValue(BreakevenPips);

		_stopPrice = StopLossPips > 0m ? price - stop : null;
		_takePrice = TakeProfitPips > 0m ? price + take : null;

		if (TakeProfitPips <= 0m && BreakevenPips > 0m)
			_breakevenTrigger = price + breakeven;
		else
			_breakevenTrigger = null;

		if (TakeProfitPips > 0m && TrailingStopPips > 0m && TrailingStepPips > 0m)
		{
			_trailingDistance = trail;
			_trailingStep = step;
		}
		else
		{
			_trailingDistance = null;
			_trailingStep = null;
		}
	}

	private void EnterShort(decimal price)
	// Configure protective levels immediately after a short entry.
	{
		SellMarket(Volume);

		_entryPrice = price;
		_isLongPosition = false;

		var stop = GetPipValue(StopLossPips);
		var take = GetPipValue(TakeProfitPips);
		var trail = GetPipValue(TrailingStopPips);
		var step = GetPipValue(TrailingStepPips);
		var breakeven = GetPipValue(BreakevenPips);

		_stopPrice = StopLossPips > 0m ? price + stop : null;
		_takePrice = TakeProfitPips > 0m ? price - take : null;

		if (TakeProfitPips <= 0m && BreakevenPips > 0m)
			_breakevenTrigger = price - breakeven;
		else
			_breakevenTrigger = null;

		if (TakeProfitPips > 0m && TrailingStopPips > 0m && TrailingStepPips > 0m)
		{
			_trailingDistance = trail;
			_trailingStep = step;
		}
		else
		{
			_trailingDistance = null;
			_trailingStep = null;
		}
	}

	private bool CheckMacdPattern(bool isLong)
	// MACD momentum pattern replicates the original conditional cascade.
	{
		var baseIndex = MacdBarShift;
		var required = baseIndex + 8;

		if (_macdCount <= required)
			return false;

		var v3 = _macdHistory[baseIndex + 3];
		var v4 = _macdHistory[baseIndex + 4];
		var v5 = _macdHistory[baseIndex + 5];
		var v6 = _macdHistory[baseIndex + 6];
		var v7 = _macdHistory[baseIndex + 7];
		var v8 = _macdHistory[baseIndex + 8];

		if (isLong)
			return v3 > v4 && v4 > v5 && v5 >= 0m && v6 <= 0m && v6 > v7 && v7 > v8;

		return v3 < v4 && v4 < v5 && v5 <= 0m && v6 >= 0m && v6 < v7 && v7 < v8;
	}

	private void PushValue(decimal[] buffer, ref int count, decimal value)
	{
		if (count < buffer.Length)
			count += 1;

		for (var i = count - 1; i > 0; i--)
		{
			buffer[i] = buffer[i - 1];
		}

		buffer[0] = value;
	}

	private bool TryGetHistoryValue(decimal[] buffer, int count, int index, out decimal value)
	{
		if (index < 0 || index >= count)
		{
			value = 0m;
			return false;
		}

		value = buffer[index];
		return true;
	}

	private decimal GetPipValue(decimal pips)
	// Convert pip-based settings into price units using the instrument step.
	{
		var step = Security?.PriceStep ?? 0.0001m;
		return pips * step * 10m;
	}

	private bool ShouldCloseForDay(ICandleMessage candle)
	{
		var time = candle.CloseTime;
		var endHour = time.DayOfWeek == DayOfWeek.Friday ? 21 : 23;
		return time.Hour >= endHour;
	}

	private void ClosePosition()
	{
		if (Position > 0)
		{
			SellMarket(Position);
		}
		else if (Position < 0)
		{
			BuyMarket(Math.Abs(Position));
		}

		ResetPositionState();
	}

	private void ResetPositionState()
	// Reset cached trading state once the position has been closed.
	{
		_stopPrice = null;
		_takePrice = null;
		_breakevenTrigger = null;
		_trailingDistance = null;
		_trailingStep = null;
		_isLongPosition = false;
		_entryPrice = 0m;
	}
}