Ver en GitHub

Estrategia Momo Trades

Conversión del asesor experto original de MetaTrader "Momo_trades" que opera rupturas de momentum filtradas por una media móvil y la estructura del MACD.

Lógica de la estrategia

  • Trabaja con velas completadas del marco temporal configurado y procesa solo una posición neta a la vez.
  • Usa una media móvil simple con un desplazamiento de barra configurable para medir cuánto cerró el precio alejado del promedio. Las operaciones largas requieren que el cierre desplazado esté por encima de la SMA en más del umbral de desplazamiento de precio; los cortos requieren lo contrario.
  • Evalúa un patrón de momentum MACD en cascada que refleja las reglas MQL: varios valores pasados de la línea principal MACD deben aumentar a través de cero para largos o disminuir a través de cero para cortos. Esto evita operaciones mientras el momentum se debilita.
  • Abre una orden de mercado con el volumen de la estrategia una vez que tanto el filtro de distancia SMA como el patrón MACD se alinean para la misma dirección.

Gestión de riesgo

  • El stop-loss, take-profit, trailing stop, paso de trailing, break-even y los inputs de desplazamiento de precio se definen en pips y se convierten automáticamente a unidades de precio usando el paso del instrumento.
  • Cuando se proporcionan valores de take-profit y trailing, el stop solo se arrastra después de que el precio avance por la distancia de trailing más el paso de trailing, reproduciendo el comportamiento MQL.
  • Cuando no hay take-profit configurado pero sí una distancia de break-even, el stop se mueve al precio de entrada una vez que se alcanza el disparador de break-even.
  • Todos los niveles de stop y take se recalculan en cada vela completada y se cierran mediante órdenes de mercado cuando son cruzados por los extremos de la vela.

Gestión de sesión

  • La bandera CloseEndDay coincide con el asesor experto original y cierra cualquier posición activa a las 23:00 hora de la plataforma (21:00 los viernes). Después del corte, la estrategia omite nuevas entradas hasta el día siguiente.

Parámetros

  • SMA Period / MA Bar Shift – longitud de la media móvil y el índice de barra usado para obtener valores de SMA y precio.
  • MACD Fast / Slow / Signal / Bar Shift – configuración de MACD y el desplazamiento aplicado a los valores almacenados para las verificaciones de patrón.
  • Stop Loss / Take Profit / Trailing Stop / Trailing Step / Breakeven / Price Shift – distancias en pips que controlan la salida, el trailing y los filtros de SMA.
  • Close End Of Day – cierra posiciones después del fin de sesión configurado.
  • Candle Type – marco temporal usado para velas y 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;
	}
}