Ver no GitHub

Estratégia de Confirmação de Tendência de MA Dupla

Visão geral

A Estratégia de Confirmação de Tendência de MA Dupla replica o expert original do MetaTrader que combina uma média móvel exponencial lenta (EMA) com uma média móvel ponderada linearmente rápida (LWMA). O sistema aguarda que ambas as médias móveis se alinhem na mesma direção e usa o fechamento do candle anterior como confirmação adicional antes de entrar em uma posição. A ideia é participar apenas de fortes oscilações de momentum quando o filtro de tendência lenta e o filtro de confirmação rápida simultaneamente se inclinam para cima ou para baixo.

A implementação do StockSharp processa apenas candles completamente terminados, rastreia a inclinação de cada média móvel durante as últimas três barras e gerencia automaticamente as ordens protetoras via mecanismo integrado StartProtection. A estratégia é agnóstica ao instrumento: pode operar em qualquer título e timeframe que forneçam candles e suportem o conceito de "pontos" pelo passo de preço do instrumento.

Indicadores

  • EMA lenta – Período padrão 57. Representa a direção de tendência dominante. A estratégia requer que a EMA aumente (ou diminua) por dois candles consecutivos antes de operar.
  • LWMA rápida – Período padrão 3. Atua como filtro de confirmação de momentum. Sua inclinação deve concordar com a EMA lenta, reforçando que o momentum apoia a tendência.

Parâmetros

Parâmetro Padrão Descrição
SlowMaLength 57 Período do filtro de tendência EMA lenta.
FastMaLength 3 Período do filtro de confirmação LWMA rápida.
StopLossPoints 100 Distância de stop protetor expressa em pontos do instrumento (multiplicado por Security.PriceStep).
TakeProfitPoints 100 Distância de take-profit expressa em pontos do instrumento (multiplicado por Security.PriceStep).
CandleType Timeframe de 15 minutos Tipo de dados de candle usado para todos os cálculos.

Todos os parâmetros são expostos como valores StrategyParam<T> para que possam ser modificados em tempo de execução ou otimizados através das ferramentas de otimização do StockSharp.

Regras de trading

Setup comprado

  1. EMA lenta está subindo: valor atual > valor anterior > valor há dois candles.
  2. LWMA rápida está subindo: valor atual > valor anterior > valor há dois candles.
  3. Fechamento do candle anterior está acima do valor anterior da EMA lenta.
  4. Valor atual da EMA lenta está acima do valor atual da LWMA rápida.
  5. Posição atual é plana ou vendida.
  6. Quando todas as condições são atendidas, a estratégia envia uma ordem de compra de mercado por Volume + |Position| para voltar a uma posição comprada.

Setup vendido

  1. EMA lenta está caindo: valor atual < valor anterior < valor há dois candles.
  2. LWMA rápida está caindo: valor atual < valor anterior < valor há dois candles.
  3. Fechamento do candle anterior está abaixo do valor anterior da EMA lenta.
  4. Valor atual da EMA lenta está abaixo do valor atual da LWMA rápida.
  5. Posição atual é plana ou comprada.
  6. Quando todas as condições são atendidas, a estratégia envia uma ordem de venda de mercado por Volume + |Position| para voltar a uma posição vendida.

Lógica protetora

  • StartProtection converte StopLossPoints e TakeProfitPoints em deslocamentos de preço absoluto multiplicando-os com Security.PriceStep. Ordens de stop-loss e take-profit são emitidas como saídas de mercado para que o motor possa fechar a posição mesmo se ordens limite não forem suportadas.
  • Quando o sinal oposto aparece, a estratégia reverte imediatamente a posição independentemente das ordens protetoras.

Detalhes de implementação

  • Apenas candles terminados são processados, emulando a verificação de nova barra da versão MQL original.
  • A estratégia mantém os últimos dois valores de média móvel e o preço de fechamento anterior em campos privados para evitar pesquisas no histórico do indicador.
  • IsFormedAndOnlineAndAllowTrading() garante que o trading ocorra apenas quando todos os fluxos de dados estejam ativos e o trading seja permitido.
  • Logs de direção de trade (LogInfo) fornecem transparência para depuração e monitoramento ao vivo.
  • A renderização de gráficos (se disponível) desenha os candles e ambas as médias móveis para validação visual rápida.

Notas de uso

  • Escolha Volume de acordo com o tamanho do lote do instrumento. A estratégia sempre envia ordens de mercado de tamanho Volume + |Position| para reverter eficientemente.
  • Ao executar em instrumentos sem um PriceStep definido, o código recorre a um valor de 1. Ajuste os parâmetros de acordo se o tamanho do tick diferir.
  • A otimização pode focar nos períodos de média móvel e distâncias protetoras para adaptar a estratégia a diferentes mercados.
  • Combine com filtros adicionais (volatilidade, horários de sessão, etc.) se necessário. A estrutura modular torna fácil a extensão.

Faixas de otimização sugeridas

  • SlowMaLength: 20 – 120 com passo 5–10.
  • FastMaLength: 2 – 10 com passo 1.
  • StopLossPoints / TakeProfitPoints: 50 – 200 dependendo da volatilidade do instrumento.

Essas faixas refletem de perto as configurações originais do expert enquanto fornecem flexibilidade para outros instrumentos.

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving average trend confirmation strategy.
/// Uses a slow EMA and a fast LWMA to detect synchronized trends.
/// Enters long when both averages slope upward, price stays above the slow EMA, and the slow EMA is above the fast LWMA.
/// Enters short when both averages slope downward, price stays below the slow EMA, and the slow EMA is below the fast LWMA.
/// Built-in stop-loss and take-profit are defined in instrument points.
/// </summary>
public class DualMaTrendConfirmationStrategy : Strategy
{
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousClose;
	private decimal _slowPrevious;
	private decimal _slowPrevious2;
	private decimal _fastPrevious;
	private decimal _fastPrevious2;
	private int _historyCount;

	/// <summary>
	/// Slow EMA period length.
	/// </summary>
	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	/// <summary>
	/// Fast LWMA period length.
	/// </summary>
	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in instrument points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in instrument points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.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 the <see cref="DualMaTrendConfirmationStrategy"/> class.
	/// </summary>
	public DualMaTrendConfirmationStrategy()
	{
		_slowMaLength = Param(nameof(SlowMaLength), 57)
			.SetDisplay("Slow EMA Length", "Period for the slow EMA trend filter", "Moving Averages")
			.SetRange(10, 200)
			;

		_fastMaLength = Param(nameof(FastMaLength), 3)
			.SetDisplay("Fast LWMA Length", "Period for the fast LWMA confirmation filter", "Moving Averages")
			.SetRange(1, 50)
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 100m)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
			.SetDisplay("Take Profit (points)", "Take-profit distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for moving average calculations", "General");
	}

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

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

		// Clear stored history so the next candle starts with a clean state.
		_previousClose = 0m;
		_slowPrevious = 0m;
		_slowPrevious2 = 0m;
		_fastPrevious = 0m;
		_fastPrevious2 = 0m;
		_historyCount = 0;
	}

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

		var slowEma = new ExponentialMovingAverage
		{
			Length = SlowMaLength
		};

		var fastLwma = new WeightedMovingAverage
		{
			Length = FastMaLength
		};

		var subscription = SubscribeCandles(CandleType);

		var step = Security.PriceStep ?? 1m;

		// Enable automatic stop-loss and take-profit management based on point offsets.
		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
			useMarketOrders: true);

		subscription
			.Bind(slowEma, fastLwma, (candle, slowValue, fastValue) => ProcessCandle(candle, slowValue, fastValue, slowEma, fastLwma))
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal slowValue, decimal fastValue, ExponentialMovingAverage slowEma, WeightedMovingAverage fastLwma)
	{
		// Work only with fully formed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure both indicators produced reliable values before trading logic.
		if (!slowEma.IsFormed || !fastLwma.IsFormed)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		// Accumulate at least two previous candles for slope calculations.
		if (_historyCount < 2)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		var slowRising = slowValue > _slowPrevious && _slowPrevious > _slowPrevious2;
		var fastRising = fastValue > _fastPrevious && _fastPrevious > _fastPrevious2;
		var slowFalling = slowValue < _slowPrevious && _slowPrevious < _slowPrevious2;
		var fastFalling = fastValue < _fastPrevious && _fastPrevious < _fastPrevious2;
		var priceAboveSlow = _previousClose > _slowPrevious;
		var priceBelowSlow = _previousClose < _slowPrevious;
		var slowAboveFast = slowValue > fastValue;
		var slowBelowFast = slowValue < fastValue;

		if (slowRising && fastRising && priceAboveSlow && slowAboveFast && Position <= 0)
		{
			BuyMarket();
		}
		else if (slowFalling && fastFalling && priceBelowSlow && slowBelowFast && Position >= 0)
		{
			SellMarket();
		}

		UpdateHistory(slowValue, fastValue, candle.ClosePrice);
	}

	private void UpdateHistory(decimal slowValue, decimal fastValue, decimal closePrice)
	{
		// Shift previous values so the last two candles are always available.
		_slowPrevious2 = _slowPrevious;
		_slowPrevious = slowValue;
		_fastPrevious2 = _fastPrevious;
		_fastPrevious = fastValue;
		_previousClose = closePrice;

		if (_historyCount < 2)
			_historyCount++;
	}
}