Ver en GitHub

ATR Estrategia de comerciante por pasos

Descripción general

La estrategia ATR Step Trader es una adaptación directa del asesor experto MetaTrader5 atrTrader.mq5. Combina un filtro de promedio móvil rápido/lento con reglas de desglose y pirámide basadas en el rango verdadero promedio (ATR). El puerto mantiene el flujo de trabajo basado en barras del EA original: solo se procesan velas completadas, el SMA rápido debe ubicarse por encima o debajo del SMA lento para un número fijo de barras, y cada decisión está anclada a ATR múltiplos para normalizar las distancias entre los mercados.

Indicadores y datos

  • Promedios móviles simples (SMA). Dos promedios móviles (FastPeriod y SlowPeriod) definen el filtro de tendencia principal. Ambos se aplican a la serie de velas de suscripción.
  • Rango verdadero promedio (ATR). Un indicador AverageTrueRange (AtrPeriod) convierte la volatilidad en distancias de precios. Cada cálculo de ruptura, complemento y parada utiliza múltiplos ATR.
  • Canales de precios más altos/más bajos. Los indicadores Highest y Lowest rastrean los máximos y mínimos extremos de las velas MomentumPeriod más recientes. Reemplazan las llamadas iHighest/iLowest del código MQL.
  • Período de tiempo. El tipo de vela predeterminado es una hora (TimeSpan.FromHours(1)), lo que refleja el comportamiento PERIOD_CURRENT del script original. Puede cambiar a cualquier otro período editando el parámetro CandleType.

Lógica de entrada

  1. Espera a que se acabe la vela. Las velas sin terminar se ignoran para permanecer sincronizadas con el protector MT5 OnTick + iTime.
  2. Actualiza los contadores de racha de media móvil. Una racha alcista aumenta cuando el SMA rápido se imprime por encima del SMA lento; una racha bajista aumenta cuando se imprime debajo. Las lecturas mixtas restablecieron la racha opuesta.
  3. Una vez que la racha alcista alcance MomentumPeriod, verifique si el precio de cierre todavía está por debajo del máximo reciente en al menos StepMultiplier * ATR. Si es así, compre en el mercado.
  4. Una vez que la racha bajista alcance MomentumPeriod, compruebe si el precio de cierre todavía está por encima del mínimo reciente en al menos StepMultiplier * ATR. Si es así, véndalo en el mercado.
  5. Cada nueva posición inicializa el estado direccional: la estrategia recuerda los precios completados más alto y más bajo por lado para que las pirámides posteriores tengan anclajes de referencia. La primera orden también incluye un stop del tamaño de la volatilidad (StepMultiplier * StopMultiplier * ATR).

Gestión de posiciones

  • Pirámide: Mientras el número de entradas activas esté por debajo de PyramidLimit, la estrategia agrega otra unidad cada vez que el precio se aleja +/- StepsMultiplier * ATR de la referencia extrema actual. Esto refleja la cuadrícula de escala de "Pasos" del EA y funciona tanto en direcciones favorables como desfavorables.
  • Paradas de protección: La parada inicial para una nueva orden se encuentra a StepMultiplier * StopMultiplier * ATR del precio de cumplimiento. Cuando la pirámide está llena, las paradas se ajustan a StepMultiplier * ATR detrás (para largos) o delante (para cortos) del último cierre, emulando la actualización final del EA cuando hay tres posiciones abiertas.
  • Salidas adversas: si el precio retrocede StepsMultiplier * ATR más allá del extremo rastreado, la estrategia sale inmediatamente de todas las posiciones de ese lado con una orden de mercado. Esto captura la lógica EA que descarta la pila completa cuando el precio supera el borde de la escalera más reciente.
  • Restablecimiento de estado: Después de una salida completa, los contadores de racha y las referencias de parada ATR se reinician de modo que se debe desarrollar una nueva secuencia de tendencia antes del reingreso.

Parámetros

grupo Nombre Descripción Predeterminado
Filtro de tendencias FastPeriod Longitud rápida SMA que mide la dirección a corto plazo. 70
Filtro de tendencias SlowPeriod Longitud lenta de SMA que mide la dirección a largo plazo. 180
Filtro de tendencias MomentumPeriod Número de velas terminadas consecutivas que deben confirmar la tendencia. 50
volatilidad AtrPeriod Ventana ATR utilizada para todos los cálculos de distancia. 100
Lógica de entrada StepMultiplier ATR múltiplo que bloquea las rupturas iniciales. 4
Lógica de entrada StepsMultiplier ATR múltiplo que separa las capas de la pirámide. 2
Gestión de riesgos StopMultiplier Multiplicador adicional aplicado a la parada inicial más allá de la distancia del paso base. 3
Tamaño de posición PyramidLimit Número máximo de entradas por sentido. 3
Comercio TradeVolume Volumen de estrategia presentado con cada orden de mercado. 1
generales CandleType Tipo de vela (período de tiempo) utilizado para la suscripción. TimeFrame(1h)

Notas practicas

  • La versión StockSharp utiliza la propiedad de estrategia Volume para el tamaño. Ajuste TradeVolume para que coincida con el tamaño del contrato de su instrumento antes de publicarlo.
  • Se supone que las órdenes de mercado se ejecutan inmediatamente, al igual que el uso de CTrade.Buy/Sell por parte de MT5. En mercados reducidos es posible que desee sustituir las órdenes de mercado por órdenes de límite o de parada.
  • Las referencias alta/baja replican las variables h_price y l_price de EA y se actualizan cada vez que se agrega o elimina una nueva capa. Son esenciales para determinar cuándo agregar o lavar la escalera.
  • Debido a que EA almacena stop loss por posición mientras que StockSharp los administra a nivel de estrategia, el puerto aplica la lógica de stop más estricta a toda la pila. Esto ofrece el mismo comportamiento (todas las posiciones salen juntas) con menos órdenes del corredor que administrar.
  • Pruebe siempre la estrategia en simulación. Las distancias ATR se adaptan a la volatilidad, pero en mercados con brechas o deslizamiento alto el riesgo realizado aún puede exceder la distancia de parada proyectada.
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;
	}
}