Ver en GitHub

Estrategia MARE5.1 con Media Móvil Desplazada

Descripción general

La Estrategia MARE5.1 con Media Móvil Desplazada es un port directo del asesor experto original de MetaTrader 5 "MARE5.1" a la API de alto nivel de StockSharp. El sistema monitorea velas de un minuto (configurables) y compara dos medias móviles simples (SMA) que comparten un desplazamiento hacia adelante configurable. La lógica busca patrones de cruce confirmados por relaciones históricas de SMA y la dirección de la última vela completada.

Lógica de Trading

  • La estrategia usa dos SMAs: una SMA rápida y una SMA lenta. Ambas están desplazadas hacia adelante el mismo número de barras, replicando el comportamiento del asesor experto original.
  • Se abre una posición corta cuando todo lo siguiente es verdadero:
    1. La SMA lenta está al menos un paso de precio por encima de la SMA rápida en la vela actual.
    2. Dos velas atrás, la SMA rápida estaba al menos un paso de precio por encima de la SMA lenta.
    3. Cinco velas atrás, la SMA rápida estaba al menos un paso de precio por encima de la SMA lenta.
    4. La vela completada más reciente (barra anterior) es bajista.
  • Se abre una posición larga cuando ocurre el patrón opuesto:
    1. La SMA rápida está al menos un paso de precio por encima de la SMA lenta en la vela actual.
    2. Dos velas atrás, la SMA lenta estaba al menos un paso de precio por encima de la SMA rápida.
    3. Cinco velas atrás, la SMA lenta estaba al menos un paso de precio por encima de la SMA rápida.
    4. La vela completada más reciente (barra anterior) es alcista.
  • Solo puede estar abierta una posición a la vez. El tamaño predeterminado de la orden proviene del parámetro TradeVolume.
  • El trading solo está permitido entre las horas de sesión configuradas (inclusive). Esta ventana replica el filtro basado en horas del asesor experto original.

Gestión de Riesgos

La estrategia replica las distancias fijas de toma de ganancias y stop-loss originales. Se definen en "pips" (puntos ajustados para instrumentos de tres y cinco dígitos) y se convierten en unidades de precio absolutas cuando la estrategia comienza. Las órdenes de protección se gestionan a través de StartProtection con salidas de órdenes de mercado.

Indicadores y Datos

  • SMA rápida – longitud definida por FastPeriod.
  • SMA lenta – longitud definida por SlowPeriod.
  • Fuente de datos – por defecto velas de un minuto, pero cualquier tipo de vela compatible con StockSharp puede seleccionarse a través del parámetro CandleType.

Parámetros

Nombre Valor predeterminado Descripción
TradeVolume 0.01 Volumen de orden utilizado para entradas.
TakeProfitPips 35 Distancia de toma de ganancias en pips ajustados. Establecer en cero para deshabilitar.
StopLossPips 55 Distancia de stop-loss en pips ajustados. Establecer en cero para deshabilitar.
FastPeriod 14 Período de la SMA rápida.
SlowPeriod 79 Período de la SMA lenta.
MovingAverageShift 4 Desplazamiento hacia adelante (en barras) aplicado a ambas SMAs.
SessionOpenHour 2 Inicio de la ventana de trading permitida (0–23, inclusive).
SessionCloseHour 3 Fin de la ventana de trading permitida (0–23, inclusive). Debe ser mayor que SessionOpenHour.
CandleType Velas de 1 minuto Tipo de datos de velas utilizado por la estrategia.

Notas

  • Las señales se evalúan en velas completadas. Los valores históricos de SMA se almacenan internamente para replicar las comparaciones basadas en índice del código MQL original.
  • El valor del paso de precio del instrumento activo se usa al comparar diferencias de SMA para asegurar que la distancia requerida sea al menos un tick.
  • Los niveles de stop-loss y toma de ganancias dependen del paso de precio del instrumento. Para instrumentos de tres y cinco decimales, el tamaño de pip se expande automáticamente diez veces, coincidiendo con el comportamiento de MetaTrader.
  • No se implementa ningún escalado automático de posiciones. La estrategia espera a que todas las posiciones abiertas se cierren antes de buscar la siguiente señal de entrada.
  • Este repositorio contiene solo la implementación en C#; no hay port en Python para esta estrategia.
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>
/// MARE5.1 strategy that trades shifted SMA crossovers with time filtering.
/// </summary>
public class Mare51Strategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _movingAverageShift;
	private readonly StrategyParam<int> _sessionOpenHour;
	private readonly StrategyParam<int> _sessionCloseHour;
	private readonly StrategyParam<DataType> _candleType;

	private SMA _fastSma;
	private SMA _slowSma;
	private decimal?[] _fastBuffer;
	private decimal?[] _slowBuffer;
	private ICandleMessage _previousCandle;
	private decimal _pipSize;

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

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

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int MovingAverageShift
	{
		get => _movingAverageShift.Value;
		set => _movingAverageShift.Value = value;
	}

	public int SessionOpenHour
	{
		get => _sessionOpenHour.Value;
		set => _sessionOpenHour.Value = value;
	}

	public int SessionCloseHour
	{
		get => _sessionCloseHour.Value;
		set => _sessionCloseHour.Value = value;
	}

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

	public Mare51Strategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.01m)
			.SetDisplay("Volume", "Default order volume", "Trading")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 35m)
			.SetDisplay("Take Profit (pips)", "Take profit distance in adjusted pips", "Risk")
			.SetNotNegative();

		_stopLossPips = Param(nameof(StopLossPips), 55m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in adjusted pips", "Risk")
			.SetNotNegative();

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
			.SetGreaterThanZero();

		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
			.SetGreaterThanZero();

		_movingAverageShift = Param(nameof(MovingAverageShift), 1)
			.SetDisplay("MA Shift", "Forward shift applied to both SMAs", "Indicators")
			.SetNotNegative();

		_sessionOpenHour = Param(nameof(SessionOpenHour), 0)
			.SetDisplay("Session Open Hour", "Inclusive start hour for trading", "Session")
			.SetRange(0, 23);

		_sessionCloseHour = Param(nameof(SessionCloseHour), 23)
			.SetDisplay("Session Close Hour", "Inclusive end hour for trading", "Session")
			.SetRange(0, 23);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle data type", "Data");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_fastSma = null;
		_slowSma = null;
		_fastBuffer = null;
		_slowBuffer = null;
		_previousCandle = null;
		_pipSize = 0m;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		if (SessionOpenHour >= SessionCloseHour)
			throw new InvalidOperationException("SessionOpenHour must be less than SessionCloseHour.");

		Volume = TradeVolume;

		_fastSma = new SMA { Length = FastPeriod };
		_slowSma = new SMA { Length = SlowPeriod };

		_fastBuffer = new decimal?[MovingAverageShift + 6];
		_slowBuffer = new decimal?[MovingAverageShift + 6];

		_pipSize = CalculatePipSize();
		var takeProfitUnit = TakeProfitPips > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);
		var stopLossUnit = StopLossPips > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);

		StartProtection(stopLossUnit, takeProfitUnit);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastSma, _slowSma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastSma);
			DrawIndicator(area, _slowSma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_fastBuffer == null || _slowBuffer == null)
			return;

		// Shift raw SMA values so we can later access shifted indexes.
		for (var i = _fastBuffer.Length - 1; i > 0; i--)
		{
			_fastBuffer[i] = _fastBuffer[i - 1];
			_slowBuffer[i] = _slowBuffer[i - 1];
		}

		_fastBuffer[0] = fastValue;
		_slowBuffer[0] = slowValue;

		var previousCandle = _previousCandle;
		_previousCandle = candle;

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (previousCandle == null)
			return;

		if (_fastSma == null || _slowSma == null)
			return;

		if (!_fastSma.IsFormed || !_slowSma.IsFormed)
			return;

		var fast0 = GetShiftedValue(_fastBuffer, 0);
		var fast2 = GetShiftedValue(_fastBuffer, 2);
		var fast5 = GetShiftedValue(_fastBuffer, 5);
		var slow0 = GetShiftedValue(_slowBuffer, 0);
		var slow2 = GetShiftedValue(_slowBuffer, 2);
		var slow5 = GetShiftedValue(_slowBuffer, 5);

		if (fast0 is not decimal f0 || fast2 is not decimal f2 || fast5 is not decimal f5 ||
			slow0 is not decimal s0 || slow2 is not decimal s2 || slow5 is not decimal s5)
		{
			return;
		}

		if (!IsWithinSession(candle.OpenTime))
			return;

		var bearishPrevious = previousCandle.ClosePrice < previousCandle.OpenPrice;
		var bullishPrevious = previousCandle.ClosePrice > previousCandle.OpenPrice;

		var sellSignal = f5 >= s5 && f2 < s2 && f0 < s0 && bearishPrevious;
		var buySignal = f5 <= s5 && f2 > s2 && f0 > s0 && bullishPrevious;

		if (Position != 0)
			return;

		if (sellSignal)
		{
			// Enter short when slow SMA overtakes the fast SMA and previous bars confirm the reversal.
			SellMarket();
		}
		else if (buySignal)
		{
			// Enter long when fast SMA overtakes the slow SMA and previous bars confirm the reversal.
			BuyMarket();
		}
	}

	private decimal? GetShiftedValue(decimal?[] buffer, int index)
	{
		var targetIndex = index + MovingAverageShift;
		if (buffer == null)
			return null;
		if (targetIndex < 0 || targetIndex >= buffer.Length)
			return null;
		return buffer[targetIndex];
	}

	private bool IsWithinSession(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= SessionOpenHour && hour <= SessionCloseHour;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 1m;

		var scale = GetDecimalScale(step);
		return (scale == 3 || scale == 5) ? step * 10m : step;
	}

	private static int GetDecimalScale(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}