Ver no GitHub

ATR Estratégia do Step Trader

Visão geral

A estratégia ATR Step Trader é uma porta direta do consultor especialista MetaTrader5 atrTrader.mq5. Ele combina um filtro de média móvel rápida/lenta com regras de quebra e pirâmide baseadas em Average True Range (ATR). A porta mantém o fluxo de trabalho baseado em barras do EA original: apenas velas concluídas são processadas, o SMA rápido deve ficar acima ou abaixo do SMA lento para um número fixo de barras, e cada decisão é ancorada em múltiplos de ATR para normalizar as distâncias entre os mercados.

Indicadores e dados

  • Médias móveis simples (SMA). Duas médias móveis (FastPeriod e SlowPeriod) definem o filtro de tendência principal. Ambos são aplicados à série de velas de assinatura.
  • Average True Range (ATR). Um indicador AverageTrueRange (AtrPeriod) converte volatilidade em distâncias de preço. Cada cálculo de breakout, add-on e stop usa ATR múltiplos.
  • Canais de preços mais altos/mais baixos. Os indicadores Highest e Lowest rastreiam os máximos e mínimos extremos das velas MomentumPeriod mais recentes. Eles substituem as chamadas iHighest/iLowest do código MQL.
  • Período. O tipo de vela padrão é uma hora (TimeSpan.FromHours(1)), refletindo o comportamento PERIOD_CURRENT do script original. Você pode mudar para qualquer outro período editando o parâmetro CandleType.

Lógica de entrada

  1. Espere a vela terminar. Velas inacabadas são ignoradas para permanecerem sincronizadas com o protetor MT5 OnTick + iTime.
  2. Atualize os contadores de sequência de média móvel. Uma seqüência de alta aumenta quando o SMA rápido é impresso acima do SMA lento; uma faixa de baixa aumenta quando é impressa abaixo. Leituras mistas redefinem a faixa oposta.
  3. Quando a seqüência de alta atingir MomentumPeriod, verifique se o preço de fechamento ainda está abaixo da alta recente em pelo menos StepMultiplier * ATR. Se sim, compre no mercado.
  4. Quando a seqüência de baixa atingir MomentumPeriod, verifique se o preço de fechamento ainda está acima da mínima recente em pelo menos StepMultiplier * ATR. Se sim, venda no mercado.
  5. Cada nova posição inicializa o estado direcional: a estratégia lembra os preços preenchidos mais altos e mais baixos de cada lado para que as pirâmides posteriores tenham âncoras de referência. A primeira ordem também atribui um stop do tamanho da volatilidade (StepMultiplier * StopMultiplier * ATR).

Gestão de posição

  • Pirâmide: Embora o número de entradas ativas esteja abaixo de PyramidLimit, a estratégia adiciona outra unidade sempre que o preço se afasta +/- StepsMultiplier * ATR da referência extrema atual. Isso reflete a grade de escala “Etapas” do EA e funciona em direções favoráveis ​​e desfavoráveis.
  • Paradas de proteção: A parada inicial para uma nova ordem fica a StepMultiplier * StopMultiplier * ATR de distância do preço de preenchimento. Quando a pirâmide está cheia, os stops são reduzidos para StepMultiplier * ATR atrás (para posições compradas) ou à frente (para posições vendidas) do último fechamento, emulando a atualização final de EA quando três posições estão abertas.
  • Saídas adversas: Se o preço recuar StepsMultiplier * ATR além do extremo rastreado, a estratégia sai imediatamente de todas as posições desse lado com uma ordem de mercado. Isso captura a lógica EA que descarta toda a pilha quando o preço ultrapassa a borda da escada mais recente.
  • Redefinição de estado: Após uma saída completa, os contadores de sequência e as referências de parada ATR são redefinidos para que uma nova sequência de tendência seja desenvolvida antes da reentrada.

Parâmetros

Grupo Nome Descrição Padrão
Filtro de tendências FastPeriod Comprimento SMA rápido que mede a direção de curto prazo. 70
Filtro de tendências SlowPeriod Comprimento SMA lento que mede a direção de longo prazo. 180
Filtro de tendências MomentumPeriod Número de velas concluídas consecutivas que devem confirmar a tendência. 50
Volatilidade AtrPeriod Janela ATR usada para todos os cálculos de distância. 100
Lógica de entrada StepMultiplier ATR múltiplo que bloqueia os rompimentos iniciais. 4
Lógica de entrada StepsMultiplier ATR múltiplo que separa as camadas da pirâmide. 2
Gestão de Risco StopMultiplier Multiplicador extra aplicado à parada inicial além da distância do passo base. 3
Dimensionamento de posição PyramidLimit Número máximo de entradas por direção. 3
Negociação TradeVolume Volume de estratégia enviado com cada ordem de mercado. 1
Geral CandleType Tipo de vela (prazo) utilizado para a assinatura. TimeFrame(1h)

Notas práticas

  • A versão StockSharp usa a propriedade de estratégia Volume para dimensionamento. Ajuste TradeVolume para corresponder ao tamanho do contrato do seu instrumento antes de entrar no ar.
  • Presume-se que as ordens de mercado sejam atendidas imediatamente, assim como o uso de CTrade.Buy/Sell pelo MT5. Em mercados fracos, você pode querer substituir as ordens de mercado por ordens de limite ou stop.
  • As referências alto/baixo replicam as variáveis h_price e l_price do EA e são atualizadas sempre que uma nova camada é adicionada ou removida. Eles são essenciais para determinar quando adicionar ou liberar a escada.
  • Como o EA armazena stop loss por posição enquanto StockSharp os gerencia no nível da estratégia, a porta aplica a lógica de stop mais rígida a toda a pilha. Isso proporciona o mesmo comportamento (todas as posições saem juntas) com menos ordens da corretora para gerenciar.
  • Sempre teste a estratégia em simulação. As distâncias ATR adaptam-se à volatilidade, mas em mercados com gaps ou altas derrapagens o risco realizado ainda pode exceder a distância de parada projetada.
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>
/// Multi-step ATR trend strategy converted from the "atrTrader" MQL5 expert advisor.
/// Filters trends with a dual moving-average stack, opens breakouts, and pyramids positions using ATR distances.
/// </summary>
public class AtrStepTraderStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _pyramidLimit;
	private readonly StrategyParam<decimal> _stepMultiplier;
	private readonly StrategyParam<decimal> _stepsMultiplier;
	private readonly StrategyParam<decimal> _stopMultiplier;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private int _bullishStreak;
	private int _bearishStreak;
	private decimal? _previousSlow;
	private decimal? _longEntryHigh;
	private decimal? _longEntryLow;
	private decimal? _shortEntryHigh;
	private decimal? _shortEntryLow;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;

	/// <summary>
	/// Fast moving average length.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow moving average length.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Number of consecutive bars that must confirm the trend direction.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	/// <summary>
	/// Maximum number of stacked entries per direction.
	/// </summary>
	public int PyramidLimit
	{
		get => _pyramidLimit.Value;
		set => _pyramidLimit.Value = value;
	}

	/// <summary>
	/// ATR multiple used for breakout gating.
	/// </summary>
	public decimal StepMultiplier
	{
		get => _stepMultiplier.Value;
		set => _stepMultiplier.Value = value;
	}

	/// <summary>
	/// ATR multiple used for pyramiding distance checks.
	/// </summary>
	public decimal StepsMultiplier
	{
		get => _stepsMultiplier.Value;
		set => _stepsMultiplier.Value = value;
	}

	/// <summary>
	/// Additional multiplier that widens the protective stop distance.
	/// </summary>
	public decimal StopMultiplier
	{
		get => _stopMultiplier.Value;
		set => _stopMultiplier.Value = value;
	}

	/// <summary>
	/// Base order volume for market entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="AtrStepTraderStrategy"/>.
	/// </summary>
	public AtrStepTraderStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 70)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "Length of the fast moving average", "Trend Filter")
			
			.SetOptimize(50, 100, 10);

		_slowPeriod = Param(nameof(SlowPeriod), 180)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Length of the slow moving average", "Trend Filter")
			
			.SetOptimize(120, 240, 20);

		_atrPeriod = Param(nameof(AtrPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR window used for distance calculations", "Volatility")
			
			.SetOptimize(50, 150, 10);

		_momentumPeriod = Param(nameof(MomentumPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Bars", "Number of consecutive bars required for trend confirmation", "Trend Filter")
			
			.SetOptimize(30, 80, 5);

		_pyramidLimit = Param(nameof(PyramidLimit), 3)
			.SetGreaterThanZero()
			.SetDisplay("Pyramid Limit", "Maximum number of entries per direction", "Position Sizing")
			
			.SetOptimize(2, 4, 1);

		_stepMultiplier = Param(nameof(StepMultiplier), 4m)
			.SetGreaterThanZero()
			.SetDisplay("Step Multiplier", "ATR multiple for breakout validation", "Entry Logic")
			
			.SetOptimize(2m, 6m, 1m);

		_stepsMultiplier = Param(nameof(StepsMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Steps Multiplier", "ATR multiple for add-on spacing", "Entry Logic")
			
			.SetOptimize(1m, 3m, 0.5m);

		_stopMultiplier = Param(nameof(StopMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Multiplier", "Extra multiplier applied on top of the step distance", "Risk Management")
			
			.SetOptimize(2m, 4m, 0.5m);

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Base order size for market entries", "Trading")
			
			.SetOptimize(0.5m, 2m, 0.5m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for processing", "General");
	}

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

		_bullishStreak = 0;
		_bearishStreak = 0;
		_previousSlow = null;
		_longEntryHigh = null;
		_longEntryLow = null;
		_shortEntryHigh = null;
		_shortEntryLow = null;
		_longStopPrice = null;
		_shortStopPrice = null;
	}

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

		Volume = TradeVolume;

		var fastMa = new SimpleMovingAverage { Length = FastPeriod };
		var slowMa = new SimpleMovingAverage { Length = SlowPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var highest = new Highest { Length = MomentumPeriod };
		var lowest = new Lowest { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastMa, slowMa, atr, highest, lowest, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastMa);
			DrawIndicator(area, slowMa);
			DrawIndicator(area, atr);
			DrawIndicator(area, highest);
			DrawIndicator(area, lowest);
			DrawOwnTrades(area);
		}
	}

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

		if (atrValue <= 0m)
			return;

		UpdateMomentumCounters(fastValue, slowValue);

		var price = candle.ClosePrice;
		var previousSlow = _previousSlow;
		_previousSlow = slowValue;

		var volume = Volume;
		if (volume <= 0m)
			volume = 1m;

		var netPosition = Position;
		var longCount = netPosition > 0m ? (int)Math.Round(netPosition / volume, MidpointRounding.AwayFromZero) : 0;
		var shortCount = netPosition < 0m ? (int)Math.Round(-netPosition / volume, MidpointRounding.AwayFromZero) : 0;

		if (longCount == 0 && shortCount == 0)
		{
			if (previousSlow.HasValue && slowValue > 0m)
			{
				var bullishReady = _bullishStreak >= MomentumPeriod && price > previousSlow.Value;
				if (bullishReady)
				{
					BuyMarket(Volume);
					longCount = 1;
					_longEntryHigh = price;
					_longEntryLow = price;
					_longStopPrice = price - StepMultiplier * StopMultiplier * atrValue;
				}
			}

			if (longCount == 0 && previousSlow.HasValue && slowValue > 0m)
			{
				var bearishReady = _bearishStreak >= MomentumPeriod && price < previousSlow.Value;
				if (bearishReady)
				{
					SellMarket(Volume);
					shortCount = 1;
					_shortEntryHigh = price;
					_shortEntryLow = price;
					_shortStopPrice = price + StepMultiplier * StopMultiplier * atrValue;
				}
			}
		}
		else if (longCount > 0 && shortCount == 0)
		{
			ManageLongPosition(ref longCount, price, atrValue);
		}
		else if (shortCount > 0 && longCount == 0)
		{
			ManageShortPosition(ref shortCount, price, atrValue);
		}
	}

	private void UpdateMomentumCounters(decimal fastValue, decimal slowValue)
	{
		if (fastValue > slowValue)
		{
			_bullishStreak++;
			_bearishStreak = 0;
		}
		else if (fastValue < slowValue)
		{
			_bearishStreak++;
			_bullishStreak = 0;
		}
		else
		{
			_bullishStreak++;
			_bearishStreak++;
		}
	}

	private void ManageLongPosition(ref int longCount, decimal price, decimal atrValue)
	{
		if (_longEntryHigh is not decimal high || _longEntryLow is not decimal low)
			return;

		var stepsDistance = StepsMultiplier * atrValue;
		var stepDistance = StepMultiplier * atrValue;

		if (_longStopPrice.HasValue && price <= _longStopPrice.Value)
		{
			SellMarket(Position);
			longCount = 0;
			ResetLongState();
			return;
		}

		if (longCount < PyramidLimit)
		{
			if (price >= high + stepsDistance || price <= low - stepsDistance)
			{
				BuyMarket(Volume);
				longCount++;
				_longEntryHigh = Math.Max(high, price);
				_longEntryLow = Math.Min(low, price);
				UpdateLongStopAfterEntry(price, atrValue);
				return;
			}
		}

		if (price <= low - stepsDistance)
		{
			SellMarket(Position);
			longCount = 0;
			ResetLongState();
			return;
		}

		if (longCount >= PyramidLimit)
		{
			var tightened = price - stepDistance;
			if (!_longStopPrice.HasValue || tightened > _longStopPrice.Value)
				_longStopPrice = tightened;
		}
	}

	private void ManageShortPosition(ref int shortCount, decimal price, decimal atrValue)
	{
		if (_shortEntryHigh is not decimal high || _shortEntryLow is not decimal low)
			return;

		var stepsDistance = StepsMultiplier * atrValue;
		var stepDistance = StepMultiplier * atrValue;

		if (_shortStopPrice.HasValue && price >= _shortStopPrice.Value)
		{
			BuyMarket(Math.Abs(Position));
			shortCount = 0;
			ResetShortState();
			return;
		}

		if (shortCount < PyramidLimit)
		{
			if (price <= low - stepsDistance || price >= high + stepsDistance)
			{
				SellMarket(Volume);
				shortCount++;
				_shortEntryHigh = Math.Max(high, price);
				_shortEntryLow = Math.Min(low, price);
				UpdateShortStopAfterEntry(price, atrValue);
				return;
			}
		}

		if (price >= high + stepsDistance)
		{
			BuyMarket(Math.Abs(Position));
			shortCount = 0;
			ResetShortState();
			return;
		}

		if (shortCount >= PyramidLimit)
		{
			var tightened = price + stepDistance;
			if (!_shortStopPrice.HasValue || tightened < _shortStopPrice.Value)
				_shortStopPrice = tightened;
		}
	}

	private void UpdateLongStopAfterEntry(decimal entryPrice, decimal atrValue)
	{
		var stop = entryPrice - StepMultiplier * StopMultiplier * atrValue;
		if (!_longStopPrice.HasValue || stop > _longStopPrice.Value)
			_longStopPrice = stop;
	}

	private void UpdateShortStopAfterEntry(decimal entryPrice, decimal atrValue)
	{
		var stop = entryPrice + StepMultiplier * StopMultiplier * atrValue;
		if (!_shortStopPrice.HasValue || stop < _shortStopPrice.Value)
			_shortStopPrice = stop;
	}

	private void ResetLongState()
	{
		_longEntryHigh = null;
		_longEntryLow = null;
		_longStopPrice = null;
		_bullishStreak = 0;
	}

	private void ResetShortState()
	{
		_shortEntryHigh = null;
		_shortEntryLow = null;
		_shortStopPrice = null;
		_bearishStreak = 0;
	}
}