Ver en GitHub

Estrategia BARS Alligator

La estrategia BARS Alligator es un port directo del asesor experto de MetaTrader con el mismo nombre. Se basa en el indicador Alligator de Bill Williams para detectar tendencias emergentes: cuando la línea verde de los labios (lips) cruza por encima de la línea azul de la mandíbula (jaw), el sistema lo trata como un rupturo alcista, mientras que un cruce hacia abajo señala impulso bajista. Las salidas dependen de que los labios crucen la línea roja de los dientes (teeth) para que las posiciones se cierren tan pronto como el impulso se desvanece. Las distancias de stop-loss de protección, take-profit y trailing stop se configuran en pips y se convierten automáticamente a unidades de precio según el paso de precio del instrumento y la precisión decimal.

Lógica de trading

  1. Construcción del indicador
    • Tres medias móviles con longitudes, desplazamientos y tipo configurables (simple, exponencial, suavizada o ponderada) forman el Alligator.
    • El precio aplicado puede ser el cierre, apertura, máximo, mínimo, mediano, típico o precio ponderado de cada vela.
    • Los desplazamientos se respetan almacenando un pequeño buffer rotativo para cada línea para que las cruces usen los mismos valores que aparecerían en un gráfico de MetaTrader.
  2. Condiciones de entrada
    • Largo: la línea lips en la barra anterior está por encima de la jaw y estaba por debajo dos barras atrás (cruce alcista hacia arriba).
    • Corto: la línea lips en la barra anterior está por debajo de la jaw y estaba por encima dos barras atrás (cruce bajista hacia abajo).
    • Las nuevas entradas solo se permiten si la posición actual es plana o ya está alineada con la dirección de la señal y el tamaño agregado de la posición permanece por debajo de MaxPositions × OrderVolume (o el equivalente dimensionado por riesgo).
  3. Condiciones de salida
    • Salida larga: la línea lips cruza por debajo de la línea teeth y la posición es rentable respecto al precio de entrada promedio.
    • Salida corta: la línea lips cruza por encima de la línea teeth y la posición es rentable.
    • Las salidas también ocurren cuando se superan los niveles estáticos de stop-loss o take-profit.
  4. Stop dinámico
    • Cuando está habilitado, un trailing stop reposiciona el stop de protección una vez que el precio se mueve más allá de TrailingStopPips + TrailingStepPips en la dirección de la operación. El stop entonces sigue al precio a una distancia de TrailingStopPips pips pero solo avanza si el precio hace un nuevo progreso de al menos TrailingStepPips pips.
  5. Gestión monetaria
    • Con MoneyMode = FixedVolume, las órdenes usan el tamaño de OrderVolume directamente.
    • Con MoneyMode = RiskPercent, la estrategia asigna volumen de manera que el porcentaje configurado MoneyValue del capital del portafolio se perdería si se golpeara el stop-loss. El riesgo por unidad equivale a la distancia del stop-loss expresada en unidades de precio. El resultado se redondea hacia abajo al VolumeStep más cercano (o a 1 cuando falta información del step).

Parámetros

Parámetro Tipo Por defecto Descripción
CandleType DataType TimeSpan.FromHours(1).TimeFrame() Marco temporal usado para cálculos del Alligator.
OrderVolume decimal 0.1 Volumen de operación fijo cuando MoneyMode es FixedVolume.
MoneyMode MoneyManagementMode FixedVolume Elige entre volumen fijo y dimensionamiento por porcentaje de riesgo.
MoneyValue decimal 1 Porcentaje de riesgo aplicado cuando MoneyMode es RiskPercent; ignorado en caso contrario.
MaxPositions int 1 Número máximo de entradas aditivas por dirección (expresado como múltiplos del volumen de orden calculado).
StopLossPips int 150 Distancia de stop-loss en pips. Cero desactiva el stop de protección.
TakeProfitPips int 150 Distancia de take-profit en pips. Cero desactiva el objetivo de beneficio.
TrailingStopPips int 5 Distancia del trailing stop en pips. Cero desactiva el trailing.
TrailingStepPips int 5 Distancia extra que debe recorrer el precio antes de que avance el trailing stop. Debe ser positivo cuando el trailing está habilitado.
JawPeriod int 13 Longitud de la media móvil jaw.
JawShift int 8 Desplazamiento hacia adelante (en barras) aplicado a la serie jaw.
TeethPeriod int 8 Longitud de la media móvil teeth.
TeethShift int 5 Desplazamiento hacia adelante aplicado a la serie teeth.
LipsPeriod int 5 Longitud de la media móvil lips.
LipsShift int 3 Desplazamiento hacia adelante aplicado a la serie lips.
MaType MovingAverageType Smoothed Algoritmo de media móvil usado para las tres líneas del Alligator.
AppliedPrice AppliedPriceType Median Precio de la vela suministrado a las medias móviles (cierre, apertura, máximo, mínimo, mediano, típico o ponderado).

Conversión de pips

La estrategia multiplica la configuración de pips por el PriceStep del instrumento. Cuando el instrumento usa 3 o 5 decimales, el valor se ajusta por ×10 para imitar la definición de pip de MetaTrader para cotizaciones fraccionarias. Si no hay ningún paso de precio disponible, se asume un valor de 1.

Notas de implementación

  • MaxPositions actúa sobre el tamaño de posición agregada porque StockSharp opera en modo netting. Las entradas adicionales aumentan el precio promedio en lugar de crear tickets de posición separados.
  • El stop-loss y el take-profit se rastrean internamente y se ejecutan con órdenes de mercado en la primera vela que viola los umbrales, coincidiendo con el comportamiento del experto MQL original.
  • El dimensionamiento basado en riesgo requiere una distancia de stop-loss no nula; de lo contrario, el sistema recurre al OrderVolume fijo.
  • Todos los valores de los indicadores se actualizan solo en velas terminadas (CandleStates.Finished) para evitar señales prematuras.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Candles;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Bill Williams Alligator strategy: trades on lips/jaw crossover and exits on lips/teeth crossover.
/// </summary>
public class BarsAlligatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;

	private readonly SmoothedMovingAverage _jaw = new() { Length = 13 };
	private readonly SmoothedMovingAverage _teeth = new() { Length = 8 };
	private readonly SmoothedMovingAverage _lips = new() { Length = 5 };

	private decimal _previousJaw;
	private decimal _previousTeeth;
	private decimal _previousLips;
	private bool _hasPrevious;
	private decimal? _entryPrice;
	private int _cooldownLeft;

	public BarsAlligatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_cooldownBars = Param(nameof(CooldownBars), 6).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between completed trades", "Trading");
		_stopLossPercent = Param(nameof(StopLossPercent), 3m).SetDisplay("Stop Loss %", "Stop distance as percentage of entry price", "Risk");
		_takeProfitPercent = Param(nameof(TakeProfitPercent), 3m).SetDisplay("Take Profit %", "Take-profit distance as percentage of entry price", "Risk");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
	public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

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

		_previousJaw = 0m;
		_previousTeeth = 0m;
		_previousLips = 0m;
		_hasPrevious = false;
		_entryPrice = null;
		_cooldownLeft = 0;
	}

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

		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_jaw, _teeth, _lips, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal jaw, decimal teeth, decimal lips)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_cooldownLeft > 0)
			_cooldownLeft--;

		if (Position != 0 && _entryPrice is null)
			_entryPrice = candle.ClosePrice;

		if (TryExitByRisk(candle))
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!_hasPrevious)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Exit conditions: lips crosses teeth against position
		var closeLong = lips < teeth && _previousLips >= _previousTeeth && Position > 0;
		var closeShort = lips > teeth && _previousLips <= _previousTeeth && Position < 0;

		if (closeLong)
		{
			SellMarket(Position);
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (closeShort)
		{
			BuyMarket(Math.Abs(Position));
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading() || _cooldownLeft > 0)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Entry: lips crosses jaw with proper Alligator ordering
		var buySignal = lips > jaw && _previousLips <= _previousJaw && lips > teeth;
		var sellSignal = lips < jaw && _previousLips >= _previousJaw && lips < teeth;

		if (buySignal && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}
		else if (sellSignal && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}

		UpdatePrevious(jaw, teeth, lips);
	}

	private bool TryExitByRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entryPrice || Position == 0 || entryPrice == 0)
			return false;

		var stopDistance = entryPrice * StopLossPercent / 100m;
		var takeDistance = entryPrice * TakeProfitPercent / 100m;

		if (Position > 0)
		{
			if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
				(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);

			if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
				(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
			{
				BuyMarket(volume);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}

		return false;
	}

	private void UpdatePrevious(decimal jaw, decimal teeth, decimal lips)
	{
		_previousJaw = jaw;
		_previousTeeth = teeth;
		_previousLips = lips;
		_hasPrevious = true;
	}
}