Ver en GitHub

Estrategia de Tendencia Alligator

La estrategia reproduce el sistema clásico Alligator de Bill Williams del script original de MetaTrader (Alligator.mq5). Utiliza tres medias móviles suavizadas construidas sobre el precio mediano y desplazadas hacia adelante para visualizar la fase del mercado. Se abre una posición larga cuando la línea rápida Lips está por encima de Teeth, y Teeth está por encima de Jaw. Se abre una posición corta cuando la alineación está invertida. Solo puede estar activa una posición al mismo tiempo.

Una vez en una operación, la estrategia protege la posición con un stop-loss y take-profit expresados en pips. Cuando el mercado se mueve a favor de la operación una distancia de nivel cero configurable, el stop se mueve a punto de equilibrio. Un trailing stop sigue el máximo más alto (para largos) o el mínimo más bajo (para cortos) con un paso mínimo para evitar actualizaciones frecuentes del stop. Las posiciones se cierran cuando se alcanzan los niveles de stop-loss, trailing stop o take-profit.

La configuración predeterminada apunta a velas de 30 minutos y valores de pip estilo Forex, pero los parámetros se pueden optimizar para otros mercados. Dado que la versión MQL original usa el manejo de pips específico del bróker, la conversión depende del PriceStep del instrumento para traducir distancias en pips a precios absolutos.

Reglas de Trading

Entrada

  • Largo: Sin posición abierta y Lips > Teeth > Jaw en la última vela completada.
  • Corto: Sin posición abierta y Lips < Teeth < Jaw en la última vela completada.

Salida y Gestión de Riesgos

  • Stop Inicial: Colocado StopLossPips por debajo (largo) o por encima (corto) del precio de llenado.
  • Take Profit: Colocado a TakeProfitPips del precio de llenado.
  • Nivel Cero: Cuando el precio avanza ZeroLevelPips, el stop se mueve al precio de entrada.
  • Trailing Stop: Después de la activación del nivel cero, el stop sigue el extremo con TrailingStopPips, actualizándose solo cuando la mejora supera TrailingStepPips.
  • Las posiciones se aplanan inmediatamente cuando cualquier stop o el nivel de take-profit es tocado en los datos de la vela.

Parámetros

Parámetro Predeterminado Descripción
CandleType Marco temporal de 30 minutos Serie de velas utilizada para cálculos de indicadores y evaluación de señales.
JawLength 13 Período de media móvil suavizada para la línea de mandíbula azul.
TeethLength 8 Período de media móvil suavizada para la línea de dientes roja.
LipsLength 5 Período de media móvil suavizada para la línea de labios verde.
JawShift 8 Desplazamiento hacia adelante de la línea de mandíbula, expresado en barras.
TeethShift 5 Desplazamiento hacia adelante de la línea de dientes, expresado en barras.
LipsShift 3 Desplazamiento hacia adelante de la línea de labios, expresado en barras.
EnableLong true Permite o bloquea entradas largas.
EnableShort true Permite o bloquea entradas cortas.
StopLossPips 45 Distancia de stop-loss en pips desde el precio de llenado.
TakeProfitPips 145 Distancia de take-profit en pips desde el precio de llenado.
ZeroLevelPips 30 Distancia en pips requerida para mover el stop a punto de equilibrio.
TrailingStopPips 50 Distancia entre el extremo actual y el trailing stop.
TrailingStepPips 10 Mejora mínima en pips requerida antes de actualizar el trailing stop.

Notas

  • El indicador Alligator se calcula sobre el precio mediano (High + Low) / 2 para coincidir con la implementación de MetaTrader.
  • Los valores de línea desplazados se emulan con búferes internos para que las comparaciones usen los mismos datos desplazados que el script original.
  • La estrategia asume que una operación se ejecuta antes de que se procese una nueva señal en la misma barra, reflejando la ejecución barra a barra del EA fuente.
  • Optimice las distancias en pips para que coincidan con el tamaño del tick y la volatilidad del instrumento operado.
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>
/// Classic Bill Williams Alligator strategy with stop management rules.
/// </summary>
/// <remarks>
/// The strategy opens a long position when the Lips, Teeth, and Jaw lines are aligned upward
/// and opens a short position when they are aligned downward. Once in a trade the algorithm
/// applies the zero level rule to move the stop to break-even, updates a trailing stop with a
/// configurable step, and closes the position at the stop or take-profit levels.
/// </remarks>
public class AlligatorTrendStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _jawLength;
	private readonly StrategyParam<int> _teethLength;
	private readonly StrategyParam<int> _lipsLength;
	private readonly StrategyParam<int> _jawShift;
	private readonly StrategyParam<int> _teethShift;
	private readonly StrategyParam<int> _lipsShift;
	private readonly StrategyParam<bool> _enableLong;
	private readonly StrategyParam<bool> _enableShort;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _zeroLevelPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;

	private readonly List<decimal> _jawBuffer = new();
	private readonly List<decimal> _teethBuffer = new();
	private readonly List<decimal> _lipsBuffer = new();

	private decimal _entryPrice;

	private decimal? _longStop;
	private decimal? _longTake;
	private bool _longBreakevenActivated;
	private decimal _longBestPrice;

	private decimal? _shortStop;
	private decimal? _shortTake;
	private bool _shortBreakevenActivated;
	private decimal _shortBestPrice;

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

	/// <summary>
	/// Jaw length.
	/// </summary>
	public int JawLength
	{
		get => _jawLength.Value;
		set => _jawLength.Value = value;
	}

	/// <summary>
	/// Teeth length.
	/// </summary>
	public int TeethLength
	{
		get => _teethLength.Value;
		set => _teethLength.Value = value;
	}

	/// <summary>
	/// Lips length.
	/// </summary>
	public int LipsLength
	{
		get => _lipsLength.Value;
		set => _lipsLength.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the jaw line.
	/// </summary>
	public int JawShift
	{
		get => _jawShift.Value;
		set => _jawShift.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the teeth line.
	/// </summary>
	public int TeethShift
	{
		get => _teethShift.Value;
		set => _teethShift.Value = value;
	}

	/// <summary>
	/// Forward shift applied to the lips line.
	/// </summary>
	public int LipsShift
	{
		get => _lipsShift.Value;
		set => _lipsShift.Value = value;
	}

	/// <summary>
	/// Enable long trades.
	/// </summary>
	public bool EnableLong
	{
		get => _enableLong.Value;
		set => _enableLong.Value = value;
	}

	/// <summary>
	/// Enable short trades.
	/// </summary>
	public bool EnableShort
	{
		get => _enableShort.Value;
		set => _enableShort.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Distance to move the stop to break-even.
	/// </summary>
	public decimal ZeroLevelPips
	{
		get => _zeroLevelPips.Value;
		set => _zeroLevelPips.Value = value;
	}

	/// <summary>
	/// Distance between price extreme and trailing stop.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimum increment for trailing stop updates.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="AlligatorTrendStrategy"/> class.
	/// </summary>
	public AlligatorTrendStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for calculations", "General");

		_jawLength = Param(nameof(JawLength), 13)
			.SetGreaterThanZero()
			.SetDisplay("Jaw Length", "Smoothed moving average period for the jaw", "Alligator")
			;

		_teethLength = Param(nameof(TeethLength), 8)
			.SetGreaterThanZero()
			.SetDisplay("Teeth Length", "Smoothed moving average period for the teeth", "Alligator")
			;

		_lipsLength = Param(nameof(LipsLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lips Length", "Smoothed moving average period for the lips", "Alligator")
			;

		_jawShift = Param(nameof(JawShift), 8)
			.SetDisplay("Jaw Shift", "Forward shift applied to the jaw line", "Alligator")
			;

		_teethShift = Param(nameof(TeethShift), 5)
			.SetDisplay("Teeth Shift", "Forward shift applied to the teeth line", "Alligator")
			;

		_lipsShift = Param(nameof(LipsShift), 3)
			.SetDisplay("Lips Shift", "Forward shift applied to the lips line", "Alligator")
			;

		_enableLong = Param(nameof(EnableLong), true)
			.SetDisplay("Enable Long", "Allow long entries", "Trading");

		_enableShort = Param(nameof(EnableShort), true)
			.SetDisplay("Enable Short", "Allow short entries", "Trading");

		_stopLossPips = Param(nameof(StopLossPips), 500m)
			.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
			;

		_takeProfitPips = Param(nameof(TakeProfitPips), 2000m)
			.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk")
			;

		_zeroLevelPips = Param(nameof(ZeroLevelPips), 300m)
			.SetDisplay("Zero Level", "Distance to move stop to break-even", "Risk")
			;

		_trailingStopPips = Param(nameof(TrailingStopPips), 500m)
			.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk")
			;

		_trailingStepPips = Param(nameof(TrailingStepPips), 100m)
			.SetDisplay("Trailing Step", "Minimum trailing stop increment in pips", "Risk")
			;
	}

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

		_jawBuffer.Clear();
		_teethBuffer.Clear();
		_lipsBuffer.Clear();
		_entryPrice = 0m;
		ResetLong();
		ResetShort();
	}

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

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

		var jaw = new SmoothedMovingAverage { Length = JawLength };
		var teeth = new SmoothedMovingAverage { Length = TeethLength };
		var lips = new SmoothedMovingAverage { Length = LipsLength };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, jaw);
			DrawIndicator(area, teeth);
			DrawIndicator(area, lips);
			DrawOwnTrades(area);
		}

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

			var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

			var inputValue = new DecimalIndicatorValue(jaw, medianPrice, candle.CloseTime) { IsFinal = true };
			var jawValue = jaw.Process(inputValue);
			var teethValue = teeth.Process(new DecimalIndicatorValue(teeth, medianPrice, candle.CloseTime) { IsFinal = true });
			var lipsValue = lips.Process(new DecimalIndicatorValue(lips, medianPrice, candle.CloseTime) { IsFinal = true });

			if (!jawValue.IsFormed || !teethValue.IsFormed || !lipsValue.IsFormed)
				return;

			var jawShifted = GetShiftedValue(_jawBuffer, jawValue.GetValue<decimal>(), JawShift);
			var teethShifted = GetShiftedValue(_teethBuffer, teethValue.GetValue<decimal>(), TeethShift);
			var lipsShifted = GetShiftedValue(_lipsBuffer, lipsValue.GetValue<decimal>(), LipsShift);

			if (!jawShifted.HasValue || !teethShifted.HasValue || !lipsShifted.HasValue)
				return;

			if (!jaw.IsFormed)
				return;

			if (ManagePosition(candle))
				return;

			var bullishAlignment = lipsShifted.Value > teethShifted.Value && teethShifted.Value > jawShifted.Value;
			var bearishAlignment = lipsShifted.Value < teethShifted.Value && teethShifted.Value < jawShifted.Value;

			if (Position == 0)
			{
				if (bullishAlignment && EnableLong)
				{
					BuyMarket();
				}
				else if (bearishAlignment && EnableShort)
				{
					SellMarket();
				}
			}
		}
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		var tradeMsg = trade.Trade;
		if (tradeMsg is null)
			return;
		var price = tradeMsg.Price;
		var direction = trade.Order.Side;

		var distanceToStop = StopLossPips > 0m ? GetPriceByPips(StopLossPips) : (decimal?)null;
		var distanceToTake = TakeProfitPips > 0m ? GetPriceByPips(TakeProfitPips) : (decimal?)null;

		if (direction == Sides.Buy)
		{
			if (Position > 0)
			{
				_entryPrice = price;
				_longStop = distanceToStop.HasValue ? price - distanceToStop.Value : null;
				_longTake = distanceToTake.HasValue ? price + distanceToTake.Value : null;
				_longBreakevenActivated = false;
				_longBestPrice = price;
			}
			else if (Position == 0)
			{
				ResetShort();
			}
		}
		else if (direction == Sides.Sell)
		{
			if (Position < 0)
			{
				_entryPrice = price;
				_shortStop = distanceToStop.HasValue ? price + distanceToStop.Value : null;
				_shortTake = distanceToTake.HasValue ? price - distanceToTake.Value : null;
				_shortBreakevenActivated = false;
				_shortBestPrice = price;
			}
			else if (Position == 0)
			{
				ResetLong();
			}
		}

		if (Position == 0)
		{
			ResetLong();
			ResetShort();
		}
	}

	private bool ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var entryPrice = _entryPrice;
			if (entryPrice == 0m)
				return false;

			_longBestPrice = Math.Max(_longBestPrice, candle.HighPrice);

			if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
			{
				SellMarket();
				ResetLong();
				return true;
			}

			if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
			{
				SellMarket();
				ResetLong();
				return true;
			}

			if (ZeroLevelPips > 0m && !_longBreakevenActivated && _longStop.HasValue && entryPrice > _longStop.Value)
			{
				var zeroDistance = GetPriceByPips(ZeroLevelPips);
				if (_longBestPrice - entryPrice >= zeroDistance)
				{
					_longStop = entryPrice;
					_longBreakevenActivated = true;
				}
			}

			if (TrailingStopPips > 0m)
			{
				var trailingDistance = GetPriceByPips(TrailingStopPips);
				var step = GetPriceByPips(TrailingStepPips);
				var candidate = _longBestPrice - trailingDistance;

				if (!_longStop.HasValue || candidate - _longStop.Value >= step)
					_longStop = candidate;
			}
		}
		else if (Position < 0)
		{
			var entryPrice = _entryPrice;
			if (entryPrice == 0m)
				return false;

			_shortBestPrice = _shortBestPrice == 0m ? candle.LowPrice : Math.Min(_shortBestPrice, candle.LowPrice);

			if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
			{
				BuyMarket();
				ResetShort();
				return true;
			}

			if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
			{
				BuyMarket();
				ResetShort();
				return true;
			}

			if (ZeroLevelPips > 0m && !_shortBreakevenActivated && _shortStop.HasValue && entryPrice < _shortStop.Value)
			{
				var zeroDistance = GetPriceByPips(ZeroLevelPips);
				if (entryPrice - candle.LowPrice >= zeroDistance)
				{
					_shortStop = entryPrice;
					_shortBreakevenActivated = true;
				}
			}

			if (TrailingStopPips > 0m)
			{
				var trailingDistance = GetPriceByPips(TrailingStopPips);
				var step = GetPriceByPips(TrailingStepPips);
				var candidate = _shortBestPrice + trailingDistance;

				if (!_shortStop.HasValue || _shortStop.Value - candidate >= step)
					_shortStop = candidate;
			}
		}
		else
		{
			ResetLong();
			ResetShort();
		}

		return false;
	}

	private static decimal? GetShiftedValue(List<decimal> buffer, decimal value, int shift)
	{
		if (shift <= 0)
			return value;

		buffer.Add(value);

		if (buffer.Count <= shift)
			return null;

		var result = buffer[0];
		try { buffer.RemoveAt(0); } catch { }
		return result;
	}

	private decimal GetPriceByPips(decimal pips)
	{
		if (pips <= 0m)
			return 0m;

		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			step = 1m;

		return pips * step;
	}

	private void ResetLong()
	{
		_longStop = null;
		_longTake = null;
		_longBreakevenActivated = false;
		_longBestPrice = 0m;
	}

	private void ResetShort()
	{
		_shortStop = null;
		_shortTake = null;
		_shortBreakevenActivated = false;
		_shortBestPrice = 0m;
	}
}