Ver no GitHub

Estratégia EMA (Edição barabashkakvn)

Convertida do consultor especialista MetaTrader 5 "EMA (barabashkakvn's edition)". O sistema opera o cruzamento de duas médias móveis exponenciais calculadas sobre o preço mediana e usa níveis virtuais de take-profit/stop-loss expressos em pips. As posições só são abertas após um cruzamento confirmado e um pequeno retrocesso em direção ao extremo da vela anterior.

Ideia central

  1. Rastrear EMAs de 5 e 10 períodos (preço mediana) no período selecionado.
  2. Quando a EMA rápida cruza a EMA lenta, armar um sinal pendente em vez de operar imediatamente.
  3. Aguardar o preço retrair MoveBackPips a partir do extremo da vela anterior enquanto o spread da EMA excede 2 * pipSize.
  4. Entrar na direção do cruzamento assim que o retrocesso ocorrer.
  5. Gerenciar a posição aberta com alvos e stops virtuais medidos em pips a partir do preço de entrada.

Este comportamento reflete a implementação MQL original: o especialista aguardava a sinalização de cruzamento (check) e então exigia um spread de EMA mais um retrocesso de preço relativo à vela anterior para acionar a operação. As regras de saída também seguem a abordagem "virtual" fechando posições quando o bid/ask teria tocado as distâncias especificadas.

Indicadores e dados

  • EMA de 5 períodos sobre o preço mediana (high + low) / 2.
  • EMA de 10 períodos sobre o preço mediana.
  • Máximo/mínimo da vela terminada anterior para verificações de retrocesso.
  • Todo o processamento usa velas terminadas da subscrição CandleType configurada.

Parâmetros

Parâmetro Padrão Descrição
OrderVolume 0.1 Volume de negociação em lotes/contratos para cada entrada.
VirtualProfitPips 5 Distância (em pips) entre o preço de entrada e o take-profit virtual.
MoveBackPips 3 Retrocesso necessário após o cruzamento, medido a partir do extremo da vela anterior.
StopLossPips 20 Distância (em pips) entre o preço de entrada e o stop loss virtual.
PipSize 0.0001 Tamanho do pip expresso em unidades de preço. Substituir ao negociar símbolos com uma definição de pip diferente.
FastLength 5 Comprimento da EMA rápida.
SlowLength 10 Comprimento da EMA lenta.
CandleType TimeFrame(1m) Fonte de velas usada para cálculos.

Todos os valores baseados em pips são convertidos para distâncias de preço usando pipValue = PipSize. Se o parâmetro for deixado em zero ou número negativo, a estratégia recorre ao Security.PriceStep (quando fornecido pelo board).

Lógica de negociação

Condições de entrada

  • Armamento do sinal: armazenar um sinal pendente sempre que um cruzamento ocorrer (FastEMA cruza acima de SlowEMA ou vice-versa). Nenhuma operação é colocada ainda.
  • Entrada vendida: requer
    • Sinal pendente presente.
    • SlowEMA - FastEMA > 2 * pipSize.
    • Máxima da vela atual ≥ mínima da vela anterior + MoveBackPips * pipSize (preço retrocedeu para cima a partir da mínima anterior).
  • Entrada comprada: requer
    • Sinal pendente presente.
    • FastEMA - SlowEMA > 2 * pipSize.
    • Mínima da vela atual ≤ máxima da vela anterior - MoveBackPips * pipSize (preço retrocedeu para baixo a partir da máxima anterior).

Após abrir uma posição a bandeira pendente é redefinida para evitar entradas duplicadas.

Condições de saída

Os alvos virtuais emulam o comportamento MQL comparando os extremos da vela com as distâncias predefinidas:

  • Posição comprada:
    • Fechar se a máxima da vela ≥ preço de entrada + VirtualProfitPips * pipSize.
    • Fechar se a mínima da vela ≤ preço de entrada - StopLossPips * pipSize.
  • Posição vendida:
    • Fechar se a mínima da vela ≤ preço de entrada - VirtualProfitPips * pipSize.
    • Fechar se a máxima da vela ≥ preço de entrada + StopLossPips * pipSize.

Após qualquer saída os níveis virtuais são redefinidos e a estratégia aguarda o próximo cruzamento.

Notas de implementação

  • Usa a subscrição de velas de alto nível (SubscribeCandles) e desenha EMAs mais operações na área de gráfico opcional.
  • O preço mediana é calculado diretamente a partir do high/low da vela para corresponder a PRICE_MEDIAN do MetaTrader.
  • A sinalização de cruzamento (_hasCrossSignal) reproduz a variável check original, garantindo que as operações só ocorram após verificações de cruzamento e retrocesso.
  • StartProtection() é chamado em OnStarted para habilitar o monitoramento de risco integrado mesmo que a estratégia gerencie as saídas manualmente.
  • O código mantém todos os comentários em inglês, conforme solicitado, e depende apenas de velas terminadas sem acessar diretamente os buffers de indicadores.

Dicas de uso

  • Ajustar PipSize ao operar instrumentos com definições de pip não-padrão (p.ex., pares JPY, índices, cotações cripto).
  • Como as saídas dependem dos extremos das velas, usar períodos mais curtos (1–5 minutos) mantém o comportamento mais próximo do especialista original baseado em ticks.
  • A otimização pode explorar comprimentos de EMA, distâncias em pips e valores de retrocesso usando os metadados de parâmetros fornecidos.
  • A estratégia opera uma posição de cada vez; qualquer posição externa no mesmo instrumento pode interferir no rastreamento virtual de saídas.

Riscos

  • A simulação baseada em velas pode perder toques intrabar dos níveis virtuais; considerar dados de maior resolução se a precisão for crítica.
  • As saídas virtuais não colocam ordens protetoras reais, portanto desconexões ou slippage podem levar a perdas maiores do que o esperado no trading ao vivo.
  • Como em qualquer sistema de cruzamento, o desempenho degrada em mercados laterais; combinar com filtros se necessário.
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;
	}
}