Ver en GitHub

Estrategia EMA (Edición barabashkakvn)

Convertida del asesor experto MetaTrader 5 "EMA (barabashkakvn's edition)". El sistema opera el cruce de dos medias móviles exponenciales calculadas sobre el precio mediana y utiliza niveles virtuales de take-profit/stop-loss expresados en pips. Las posiciones solo se abren después de un cruce confirmado y un pequeño retroceso hacia el extremo de la vela anterior.

Idea central

  1. Seguimiento de EMA de 5 y 10 períodos (precio mediana) en el marco temporal seleccionado.
  2. Cuando la EMA rápida cruza la EMA lenta, armar una señal pendiente en lugar de operar inmediatamente.
  3. Esperar a que el precio retroceda MoveBackPips desde el extremo de la vela anterior mientras el spread de EMA supera 2 * pipSize.
  4. Entrar en la dirección del cruce una vez que ocurra el retroceso.
  5. Gestionar la posición abierta con objetivos y stops virtuales medidos en pips desde el precio de entrada.

Este comportamiento refleja la implementación MQL original: el experto esperaba el indicador de cruce (check) y luego requería un spread de EMA más un retroceso de precio relativo a la vela anterior para activar la operación. Las reglas de salida también siguen el enfoque "virtual" cerrando posiciones cuando el bid/ask habría tocado las distancias especificadas.

Indicadores y datos

  • EMA de 5 períodos sobre precio mediana (high + low) / 2.
  • EMA de 10 períodos sobre precio mediana.
  • Máximo/mínimo de la vela terminada anterior para verificaciones de retroceso.
  • Todo el procesamiento usa velas terminadas de la suscripción CandleType configurada.

Parámetros

Parámetro Predeterminado Descripción
OrderVolume 0.1 Volumen de trading en lotes/contratos para cada entrada.
VirtualProfitPips 5 Distancia (en pips) entre el precio de entrada y el take-profit virtual.
MoveBackPips 3 Retroceso requerido después del cruce, medido desde el extremo de la vela anterior.
StopLossPips 20 Distancia (en pips) entre el precio de entrada y el stop-loss virtual.
PipSize 0.0001 Tamaño del pip expresado en unidades de precio. Sobreescribir cuando se operen instrumentos con una definición de pip diferente.
FastLength 5 Longitud de la EMA rápida.
SlowLength 10 Longitud de la EMA lenta.
CandleType TimeFrame(1m) Fuente de velas usada para los cálculos.

Todos los valores basados en pips se convierten a distancias de precio usando pipValue = PipSize. Si el parámetro se deja en cero o un número negativo la estrategia recurre a Security.PriceStep (cuando lo proporciona el board).

Lógica de trading

Condiciones de entrada

  • Armado de señal: almacenar una señal pendiente siempre que ocurra un cruce (FastEMA cruza por encima de SlowEMA o viceversa). Todavía no se coloca ninguna operación.
  • Entrada corta: requiere
    • Señal pendiente presente.
    • SlowEMA - FastEMA > 2 * pipSize.
    • Máximo de la vela actual ≥ mínimo de la vela anterior + MoveBackPips * pipSize (el precio retrocedió hacia arriba desde el mínimo anterior).
  • Entrada larga: requiere
    • Señal pendiente presente.
    • FastEMA - SlowEMA > 2 * pipSize.
    • Mínimo de la vela actual ≤ máximo de la vela anterior - MoveBackPips * pipSize (el precio retrocedió hacia abajo desde el máximo anterior).

Después de abrir una posición el indicador pendiente se resetea para evitar entradas duplicadas.

Condiciones de salida

Los objetivos virtuales emulan el comportamiento MQL comparando los extremos de la vela con las distancias preestablecidas:

  • Posición larga:
    • Cerrar si el máximo de la vela ≥ precio de entrada + VirtualProfitPips * pipSize.
    • Cerrar si el mínimo de la vela ≤ precio de entrada - StopLossPips * pipSize.
  • Posición corta:
    • Cerrar si el mínimo de la vela ≤ precio de entrada - VirtualProfitPips * pipSize.
    • Cerrar si el máximo de la vela ≥ precio de entrada + StopLossPips * pipSize.

Después de cualquier salida los niveles virtuales se resetean y la estrategia espera el próximo cruce.

Notas de implementación

  • Usa la suscripción de velas de alto nivel (SubscribeCandles) y dibuja EMAs más operaciones en el área de gráfico opcional.
  • El precio mediana se computa directamente desde el high/low de la vela para coincidir con PRICE_MEDIAN de MetaTrader.
  • El indicador de cruce (_hasCrossSignal) reproduce la variable check original, asegurando que las operaciones solo ocurran después de verificaciones de cruce y retroceso.
  • StartProtection() se llama en OnStarted para habilitar la monitorización de riesgo integrada aunque la estrategia gestione las salidas manualmente.
  • El código mantiene todos los comentarios en inglés, según lo solicitado, y depende únicamente de velas terminadas sin acceder directamente a los buffers de indicadores.

Consejos de uso

  • Ajustar PipSize cuando se opere con instrumentos con definiciones de pip no estándar (p.ej., pares JPY, índices, cotizaciones cripto).
  • Dado que las salidas dependen de los extremos de las velas, usar marcos temporales más cortos (1–5 minutos) mantiene el comportamiento más cercano al experto basado en ticks original.
  • La optimización puede explorar longitudes de EMA, distancias en pips y valores de retroceso usando los metadatos de parámetros proporcionados.
  • La estrategia opera una posición a la vez; cualquier posición externa en el mismo instrumento puede interferir con el seguimiento virtual de salidas.

Riesgos

  • La simulación basada en velas puede perder toques intrababarra de los niveles virtuales; considerar datos de mayor resolución si la precisión es crítica.
  • Las salidas virtuales no colocan órdenes protectoras reales, por lo que las desconexiones o el slippage pueden llevar a pérdidas mayores de lo esperado en trading en vivo.
  • Como con cualquier sistema de cruce, el rendimiento se degrada en mercados laterales; combinar con filtros si es necesario.
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _virtualProfitPips;
	private readonly StrategyParam<int> _moveBackPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _pipSize;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private bool _hasCrossSignal;
	private decimal? _prevFast;
	private decimal? _prevSlow;
	private decimal? _prevHigh;
	private decimal? _prevLow;
	private decimal? _entryPrice;
	private decimal? _virtualTarget;
	private decimal? _virtualStop;

	/// <summary>
	/// Order volume in lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Virtual take profit distance in pips.
	/// </summary>
	public int VirtualProfitPips
	{
		get => _virtualProfitPips.Value;
		set => _virtualProfitPips.Value = value;
	}

	/// <summary>
	/// Retracement distance after a crossover in pips.
	/// </summary>
	public int MoveBackPips
	{
		get => _moveBackPips.Value;
		set => _moveBackPips.Value = value;
	}

	/// <summary>
	/// Virtual stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Pip size in price units.
	/// </summary>
	public decimal PipSize
	{
		get => _pipSize.Value;
		set => _pipSize.Value = value;
	}

	/// <summary>
	/// Fast EMA length applied to median price.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length applied to median price.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
	/// </summary>
	public EmaBarabashkakvnEditionStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "Trading")
			
			.SetOptimize(0.05m, 1m, 0.05m);

		_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
			.SetGreaterThanZero()
			.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
			
			.SetOptimize(2, 20, 1);

		_moveBackPips = Param(nameof(MoveBackPips), 3)
			.SetGreaterThanZero()
			.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
			
			.SetOptimize(1, 10, 1);

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
			
			.SetOptimize(10, 60, 2);

		_pipSize = Param(nameof(PipSize), 0.0001m)
			.SetGreaterThanZero()
			.SetDisplay("Pip Size", "Instrument pip size in price units", "General");

		_fastLength = Param(nameof(FastLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
			
			.SetOptimize(3, 15, 1);

		_slowLength = Param(nameof(SlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
			
			.SetOptimize(8, 40, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");
	}

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

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

		_fastEma = default;
		_slowEma = default;
		_hasCrossSignal = false;
		_prevFast = default;
		_prevSlow = default;
		_prevHigh = default;
		_prevLow = default;
		_entryPrice = default;
		_virtualTarget = default;
		_virtualStop = default;
	}

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

		_fastEma = new EMA { Length = FastLength };
		_slowEma = new EMA { Length = SlowLength };

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

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

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

		// Calculate median price as in the original expert (PRICE_MEDIAN).
		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		// Update EMA values using the median price.
		var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
		var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });

		if (!_fastEma.IsFormed || !_slowEma.IsFormed)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_prevFast = fastValue.ToDecimal();
			_prevSlow = slowValue.ToDecimal();
			return;
		}

		var fast = fastValue.ToDecimal();
		var slow = slowValue.ToDecimal();

		if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
		{
			var bullishCross = prevFast <= prevSlow && fast > slow;
			var bearishCross = prevFast >= prevSlow && fast < slow;

			if (bullishCross || bearishCross)
				_hasCrossSignal = true;
		}

		_prevFast = fast;
		_prevSlow = slow;

		var pipValue = PipSize;
		if (pipValue <= 0m)
			pipValue = Security?.PriceStep ?? 0.0001m;

		var moveBackPrice = MoveBackPips * pipValue;
		var profitDistance = VirtualProfitPips * pipValue;
		var stopDistance = StopLossPips * pipValue;

		if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
		{
			var bearishSpread = slow - fast;
			var bullishSpread = fast - slow;

			var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
			var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;

			if (bearishReady)
			{
				// Enter short after bearish cross and retracement above the previous low.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice - profitDistance;
				_virtualStop = _entryPrice + stopDistance;
				SellMarket();
				_hasCrossSignal = false;
			}
			else if (bullishReady)
			{
				// Enter long after bullish cross and retracement below the previous high.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice + profitDistance;
				_virtualStop = _entryPrice - stopDistance;
				BuyMarket();
				_hasCrossSignal = false;
			}
		}
		else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
		{
			if (Position > 0)
			{
				// Long position: use high for profit target and low for stop.
				var hitTarget = candle.HighPrice >= target;
				var hitStop = candle.LowPrice <= stop;

				if (hitTarget || hitStop)
				{
					SellMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
			else if (Position < 0)
			{
				// Short position: use low for profit target and high for stop.
				var hitTarget = candle.LowPrice <= target;
				var hitStop = candle.HighPrice >= stop;

				if (hitTarget || hitStop)
				{
					BuyMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
		}

		if (Position == 0)
		{
			_entryPrice = null;
			_virtualTarget = null;
			_virtualStop = null;
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}