Ver en GitHub

Estrategia de Dos MA Cuatro Niveles

Esta estrategia replica el experto de MetaTrader "2MA_4Level" usando la API de alto nivel de StockSharp. Opera un único instrumento con dos medias móviles suavizadas (SMMA) calculadas sobre el precio mediano y observa cinco zonas relativas de cruce entre las curvas rápida y lenta. Las entradas solo se permiten cuando no hay posición abierta, y cada operación está protegida por offsets de stop-loss y take-profit en pips.

Lógica

  • Se calcula una SMMA rápida y una lenta sobre la serie de velas seleccionada (por defecto 50 y 130 períodos).
  • Se evalúan los valores anteriores y actuales de la SMMA en la vela completada para detectar un cruce.
  • El cruce se verifica contra cinco umbrales construidos a partir de la MA lenta:
    • la MA lenta sin offset,
    • MA lenta + pips de MostTopLevel,
    • MA lenta + pips de TopLevel,
    • MA lenta - pips de LowermostLevel,
    • MA lenta - pips de LowerLevel.
  • Cuando la MA rápida cruza por encima de cualquier umbral, se abre una posición larga (si está flat). Un cruce por debajo de cualquier umbral abre una posición corta.
  • Los niveles de stop-loss y take-profit se adjuntan a través de StartProtection usando el valor de pip del instrumento (Security.PriceStep).

La estrategia nunca piramida posiciones: una nueva operación solo puede abrirse después de que la anterior haya sido cerrada por stop o por objetivo.

Parámetros

Parámetro Predeterminado Descripción
FastPeriod 50 Longitud de la media móvil suavizada rápida. Debe ser menor que SlowPeriod.
SlowPeriod 130 Longitud de la media móvil suavizada lenta.
MostTopLevel 500 Offset superior (en pips) para la confirmación alcista/bajista más amplia. Debe ser mayor que TopLevel.
TopLevel 250 Offset superior (en pips) para la confirmación alcista/bajista secundaria.
LowerLevel 250 Offset inferior (en pips) para la confirmación bajista/alcista secundaria. Debe ser menor que LowermostLevel.
LowermostLevel 500 Offset inferior (en pips) para la confirmación bajista/alcista más amplia.
TakeProfitPips 55 Distancia desde la entrada al take-profit, expresada en pips.
StopLossPips 260 Distancia desde la entrada al stop-loss, expresada en pips.
CandleType Marco temporal de 15 minutos Serie de velas usada para los cálculos de SMMA y el procesamiento de señales.

Detalles de implementación

  • El precio mediano ((High + Low) / 2) alimenta ambas SMMA, coincidiendo con la configuración de MT5 que usa PRICE_MEDIAN.
  • La prueba de cruce compara la última vela completada con la anterior, eliminando cualquier dependencia de barras parcialmente formadas.
  • StartProtection conecta el stop-loss y take-profit una sola vez al inicio, por lo que cada orden hereda automáticamente los límites de riesgo configurados.
  • La estrategia se detiene durante OnStarted si se proporcionan combinaciones de parámetros no válidas (p. ej., FastPeriod >= SlowPeriod).

Notas de uso

  1. Adjunta la estrategia a un instrumento con un PriceStep definido; de lo contrario, la conversión de pips toma un valor de 1 por defecto.
  2. Adecuada para cuentas de cobertura en MT5; en StockSharp se comporta igual al garantizar solo una posición abierta a la vez.
  3. Los hooks de optimización (SetCanOptimize) están habilitados para ambos períodos de MA, lo que permite ejecutar barridos de parámetros directamente desde el optimizador de StockSharp.
  4. Dado que la estrategia depende exclusivamente de salidas por stop-loss y take-profit, asegúrate de que las distancias configuradas estén alineadas con la volatilidad del instrumento para evitar exposición prolongada.

Archivos

  • CS/TwoMaFourLevelStrategy.cs – Implementación en C# de la lógica de trading.
  • README_ru.md – Documentación en ruso.
  • README_zh.md – Documentación en chino.
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>
/// Two smoothed moving average crossover strategy with level offsets.
/// </summary>
public class TwoMaFourLevelStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _mostTopLevel;
	private readonly StrategyParam<int> _topLevel;
	private readonly StrategyParam<int> _lowerLevel;
	private readonly StrategyParam<int> _lowermostLevel;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private decimal? _prevFast;
	private decimal? _prevSlow;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MostTopLevel { get => _mostTopLevel.Value; set => _mostTopLevel.Value = value; }
	public int TopLevel { get => _topLevel.Value; set => _topLevel.Value = value; }
	public int LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
	public int LowermostLevel { get => _lowermostLevel.Value; set => _lowermostLevel.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TwoMaFourLevelStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Period of the fast smoothed MA", "Moving Averages")
			.SetOptimize(20, 150, 5);

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Period of the slow smoothed MA", "Moving Averages")
			.SetOptimize(60, 300, 5);

		_mostTopLevel = Param(nameof(MostTopLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Upper Level", "Highest positive offset in points", "Levels");

		_topLevel = Param(nameof(TopLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Upper Level", "Second positive offset in points", "Levels");

		_lowerLevel = Param(nameof(LowerLevel), 1)
			.SetGreaterThanZero()
			.SetDisplay("Lower Level", "Second negative offset in points", "Levels");

		_lowermostLevel = Param(nameof(LowermostLevel), 2)
			.SetGreaterThanZero()
			.SetDisplay("Extreme Lower Level", "Largest negative offset in points", "Levels");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		if (FastPeriod >= SlowPeriod)
		{
			this.LogError("FastPeriod must be less than SlowPeriod.");
			Stop();
			return;
		}

		if (MostTopLevel <= TopLevel)
		{
			this.LogError("MostTopLevel must be greater than TopLevel.");
			Stop();
			return;
		}

		if (LowerLevel >= LowermostLevel)
		{
			this.LogError("LowerLevel must be less than LowermostLevel.");
			Stop();
			return;
		}

		_fastMa = new SmoothedMovingAverage { Length = FastPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowPeriod };

		var pip = Security?.PriceStep ?? 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPips * pip, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPips * pip, UnitTypes.Absolute));

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(_fastMa, _slowMa, ProcessCandle).Start();

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

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

		if (_prevFast is null || _prevSlow is null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var pip = Security?.PriceStep ?? 0.05m;
		var signal = GetSignal(fast, slow, _prevFast.Value, _prevSlow.Value, pip);

		if (signal > 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal < 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}

	private int GetSignal(decimal fast, decimal slow, decimal prevFast, decimal prevSlow, decimal pip)
	{
		if (IsCrossUp(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossUp(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return 1;
		}

		if (IsCrossDown(prevFast, fast, prevSlow, slow, 0m) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, MostTopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, TopLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowermostLevel * pip) ||
			IsCrossDown(prevFast, fast, prevSlow, slow, -LowerLevel * pip))
		{
			return -1;
		}

		return 0;
	}

	private static bool IsCrossUp(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast <= prevSlowShifted && fast > slowShifted;
	}

	private static bool IsCrossDown(decimal prevFast, decimal fast, decimal prevSlow, decimal slow, decimal offset)
	{
		var prevSlowShifted = prevSlow + offset;
		var slowShifted = slow + offset;
		return prevFast >= prevSlowShifted && fast < slowShifted;
	}
}