Ver en GitHub

Estrategia Chaos Trader Lite

La estrategia Chaos Trader Lite replica las técnicas de entrada de los tres hombres sabios de Bill Williams utilizando el API de alto nivel de StockSharp. Analiza cada vela finalizada del marco temporal configurado (1 hora por defecto) y coloca órdenes stop cuando se cumple cualquiera de las siguientes condiciones:

  1. Primer Hombre Sabio – Barra divergente: detecta velas divergentes alcistas o bajistas y requiere una distancia mínima entre el precio y la línea de labios del Alligator.
  2. Segundo Hombre Sabio – Aceleración del Awesome Oscillator: espera cinco lecturas consecutivas del Awesome Oscillator que muestren momentum acelerado.
  3. Tercer Hombre Sabio – Ruptura de fractal: confirma un fractal dos velas atrás y verifica que el precio esté operando suficientemente lejos de la línea de dientes del Alligator antes de encolar una orden de ruptura.

Siempre que aparece una configuración larga, la estrategia cancela los sell stops existentes, cierra posiciones cortas, coloca un nuevo buy stop justo por encima del máximo anterior y registra un stop protector debajo de la vela. Lo opuesto ocurre para configuraciones cortas. Los stops protectores se monitorean en cada barra; si el precio cruza el nivel almacenado, la posición se cierra a mercado.

Indicadores y cálculos

  • Labios del Alligator: media móvil suavizada de 5 períodos del precio mediano desplazada tres velas hacia adelante. La estrategia mantiene una cola para que el valor alineado con la barra actual coincida con la implementación de MetaTrader.
  • Dientes del Alligator: media móvil suavizada de 8 períodos del precio mediano desplazada cinco velas hacia adelante. El valor desplazado impulsa el filtro del tercer hombre sabio.
  • Awesome Oscillator: el indicador integrado de StockSharp (SMA de 5 vs 34 del precio mediano) proporciona la serie de momentum utilizada por el segundo hombre sabio.
  • Fractales: el código inspecciona el máximo/mínimo de la vela que está dos barras detrás de la última barra. Un fractal válido requiere que esa vela sea más alta (o más baja) que las dos velas de cada lado.

Lógica de trading

  1. Suscribirse al tipo de vela solicitado y procesar solo las velas finalizadas.
  2. Actualizar los indicadores Alligator y Awesome Oscillator y almacenar valores desplazados.
  3. Evaluar las condiciones de los hombres sabios:
    • La barra divergente debe cerrar en la mitad superior (para alcistas) o inferior (para bajistas) de la vela y mostrar una distancia de los labios mayor que MagnitudePips * PriceStep.
    • La aceleración del AO requiere cinco valores: AO[1] > AO[2] > AO[3] > AO[4] y AO[4] < AO[5] para largos, reflejado para cortos.
    • La ruptura de fractal verifica que el precio cierre por encima (o debajo) del fractal confirmado y por encima (o debajo) de los dientes del Alligator más el umbral de magnitud.
  4. Cuando una configuración está activa, colocar una orden BuyStop o SellStop con volumen Volume en el máximo de la vela más un paso de precio (o mínimo menos un paso). Cancelar el stop opuesto y aplanar posiciones contrarias.
  5. Actualizar los niveles de stop-loss almacenados: los stops largos siguen hacia arriba, los stops cortos hacia abajo. Si una vela perfora el stop almacenado, la estrategia sale de la posición abierta a mercado.

Parámetros

  • MagnitudePips (predeterminado 10) – distancia mínima en pips entre la barra divergente y los labios del Alligator.
  • UseFirstWiseMan (predeterminado true) – habilitar o deshabilitar la entrada por barra divergente.
  • UseSecondWiseMan (predeterminado true) – habilitar o deshabilitar la entrada por aceleración del Awesome Oscillator.
  • UseThirdWiseMan (predeterminado true) – habilitar o deshabilitar la entrada por ruptura de fractal.
  • Volume (predeterminado 0.01) – tamaño de orden para entradas stop.
  • CandleType (predeterminado 1 hora) – tipo de datos procesado por la estrategia.

Notas

  • Las verificaciones de bid/ask del código MQL4 original se aproximan con el precio de cierre de la vela en StockSharp.
  • Las rutinas de validación de margen y volumen de MetaTrader se omiten porque StockSharp maneja la validación de órdenes internamente.
  • Las órdenes stop se cancelan cuando aparece la configuración opuesta para evitar órdenes pendientes conflictivas, coincidiendo con el comportamiento de CloseAll del EA.
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>
/// Chaos Trader Lite strategy implementing Bill Williams' three wise men entry concepts.
/// Uses market orders when divergent bars, Awesome Oscillator accelerations or confirmed fractals appear.
/// </summary>
public class ChaosTraderLiteStrategy : Strategy
{
	private readonly StrategyParam<int> _lipsShift;
	private readonly StrategyParam<int> _teethShift;

	private readonly StrategyParam<int> _magnitudePips;
	private readonly StrategyParam<bool> _useFirstWiseMan;
	private readonly StrategyParam<bool> _useSecondWiseMan;
	private readonly StrategyParam<bool> _useThirdWiseMan;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _lipsSmma = null!;
	private SimpleMovingAverage _teethSmma = null!;
	private AwesomeOscillator _awesomeOscillator = null!;

	private readonly List<decimal> _lipsShiftQueue = new();
	private readonly List<decimal> _teethShiftQueue = new();

	private CandleInfo? _bar0;
	private CandleInfo? _bar1;
	private CandleInfo? _bar2;
	private CandleInfo? _bar3;
	private CandleInfo? _bar4;

	private decimal? _lips0;
	private decimal? _teeth0;
	private decimal? _teeth1;

	private decimal? _ao0;
	private decimal? _ao1;
	private decimal? _ao2;
	private decimal? _ao3;
	private decimal? _ao4;
	private decimal? _ao5;

	private decimal? _longStopLoss;
	private decimal? _shortStopLoss;

	// Pending entry prices for stop-like behavior
	private decimal? _pendingBuyPrice;
	private decimal? _pendingSellPrice;
	private decimal? _pendingBuyStop;
	private decimal? _pendingSellStop;

	/// <summary>
	/// Magnitude threshold in pips between price and Alligator lips.
	/// </summary>
	public int MagnitudePips
	{
		get => _magnitudePips.Value;
		set => _magnitudePips.Value = value;
	}

	/// <summary>
	/// Number of candles used to shift the Alligator lips line.
	/// </summary>
	public int LipsShift
	{
		get => _lipsShift.Value;
		set => _lipsShift.Value = value;
	}

	/// <summary>
	/// Number of candles used to shift the Alligator teeth line.
	/// </summary>
	public int TeethShift
	{
		get => _teethShift.Value;
		set => _teethShift.Value = value;
	}

	/// <summary>
	/// Enable the first wise man divergent bar setup.
	/// </summary>
	public bool UseFirstWiseMan
	{
		get => _useFirstWiseMan.Value;
		set => _useFirstWiseMan.Value = value;
	}

	/// <summary>
	/// Enable the second wise man Awesome Oscillator acceleration setup.
	/// </summary>
	public bool UseSecondWiseMan
	{
		get => _useSecondWiseMan.Value;
		set => _useSecondWiseMan.Value = value;
	}

	/// <summary>
	/// Enable the third wise man fractal breakout setup.
	/// </summary>
	public bool UseThirdWiseMan
	{
		get => _useThirdWiseMan.Value;
		set => _useThirdWiseMan.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="ChaosTraderLiteStrategy"/>.
	/// </summary>
	public ChaosTraderLiteStrategy()
	{
		_magnitudePips = Param(nameof(MagnitudePips), 10)
			.SetGreaterThanZero()
			.SetDisplay("Magnitude", "Distance from lips in pips", "General");

		_lipsShift = Param(nameof(LipsShift), 3)
			.SetNotNegative()
			.SetDisplay("Lips Shift", "Shift applied to Alligator lips", "Alligator");

		_teethShift = Param(nameof(TeethShift), 5)
			.SetNotNegative()
			.SetDisplay("Teeth Shift", "Shift applied to Alligator teeth", "Alligator");

		_useFirstWiseMan = Param(nameof(UseFirstWiseMan), true)
			.SetDisplay("First Wise Man", "Enable divergent bar setup", "General");

		_useSecondWiseMan = Param(nameof(UseSecondWiseMan), true)
			.SetDisplay("Second Wise Man", "Enable Awesome Oscillator setup", "General");

		_useThirdWiseMan = Param(nameof(UseThirdWiseMan), true)
			.SetDisplay("Third Wise Man", "Enable fractal breakout setup", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

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

		_bar0 = _bar1 = _bar2 = _bar3 = _bar4 = null;

		_lipsShiftQueue.Clear();
		_teethShiftQueue.Clear();

		_lips0 = null;
		_teeth0 = null;
		_teeth1 = null;

		_ao0 = null;
		_ao1 = null;
		_ao2 = null;
		_ao3 = null;
		_ao4 = null;
		_ao5 = null;

		_longStopLoss = null;
		_shortStopLoss = null;

		_pendingBuyPrice = null;
		_pendingSellPrice = null;
		_pendingBuyStop = null;
		_pendingSellStop = null;
	}

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

		_lipsSmma = new SimpleMovingAverage { Length = 5 };
		_teethSmma = new SimpleMovingAverage { Length = 8 };
		_awesomeOscillator = new AwesomeOscillator { ShortMa = { Length = 5 }, LongMa = { Length = 34 } };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _lipsSmma);
			DrawIndicator(area, _teethSmma);
			DrawIndicator(area, _awesomeOscillator);
			DrawOwnTrades(area);
		}
	}

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

		// Check pending stop-like entries first
		CheckPendingEntries(candle);

		UpdateBarHistory(candle);

		var median = (candle.HighPrice + candle.LowPrice) / 2m;
		var candleInput = new CandleIndicatorValue(_lipsSmma, candle);

		var lipsValue = _lipsSmma.Process(new DecimalIndicatorValue(_lipsSmma, median, candle.ServerTime) { IsFinal = true });
		var teethValue = _teethSmma.Process(new DecimalIndicatorValue(_teethSmma, median, candle.ServerTime) { IsFinal = true });
		var awesomeValue = _awesomeOscillator.Process(new CandleIndicatorValue(_awesomeOscillator, candle));

		if (lipsValue.IsFinal)
		{
			var lips = lipsValue.ToDecimal();
			_lipsShiftQueue.Add(lips);
			if (_lipsShiftQueue.Count > LipsShift)
			{
				_lips0 = _lipsShiftQueue[0];
				try { _lipsShiftQueue.RemoveAt(0); } catch { }
			}
		}

		if (teethValue.IsFinal)
		{
			var teeth = teethValue.ToDecimal();
			_teethShiftQueue.Add(teeth);
			if (_teethShiftQueue.Count > TeethShift)
			{
				_teeth1 = _teeth0;
				_teeth0 = _teethShiftQueue[0];
				try { _teethShiftQueue.RemoveAt(0); } catch { }
			}
		}

		if (awesomeValue.IsFinal)
		{
			var ao = awesomeValue.ToDecimal();
			_ao5 = _ao4;
			_ao4 = _ao3;
			_ao3 = _ao2;
			_ao2 = _ao1;
			_ao1 = _ao0;
			_ao0 = ao;
		}

		var upFractal = GetUpFractal();
		var downFractal = GetDownFractal();

		if (_lipsSmma.IsFormed && _teethSmma.IsFormed)
			EvaluateSignals(candle, upFractal, downFractal);

		UpdateProtection(candle);
	}

	private void CheckPendingEntries(ICandleMessage candle)
	{
		// Simulate buy stop: if candle high reaches or exceeds pending buy price, enter long
		if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice)
		{
			if (Position <= 0)
			{
				if (Position < 0)
				{
					BuyMarket();
					_shortStopLoss = null;
				}
				if (Volume > 0)
				{
					BuyMarket();
					_longStopLoss = _pendingBuyStop;
				}
			}
			_pendingBuyPrice = null;
			_pendingBuyStop = null;
		}

		// Simulate sell stop: if candle low reaches or goes below pending sell price, enter short
		if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice)
		{
			if (Position >= 0)
			{
				if (Position > 0)
				{
					SellMarket();
					_longStopLoss = null;
				}
				if (Volume > 0)
				{
					SellMarket();
					_shortStopLoss = _pendingSellStop;
				}
			}
			_pendingSellPrice = null;
			_pendingSellStop = null;
		}
	}

	private void EvaluateSignals(ICandleMessage candle, decimal? upFractal, decimal? downFractal)
	{
		if (_bar0 is not CandleInfo current || _bar1 is not CandleInfo previous)
			return;

		var point = Security?.PriceStep ?? 1m;
		var magnitudeThreshold = MagnitudePips * point;

		if (UseFirstWiseMan && _lips0 is decimal lips)
		{
			if (IsBullishDivergent(current, previous))
			{
				var distance = lips - current.High;
				if (distance > magnitudeThreshold)
					PlaceBuySetup(current, point);
			}

			if (IsBearishDivergent(current, previous))
			{
				var distance = current.Low - lips;
				if (distance > magnitudeThreshold)
					PlaceSellSetup(current, point);
			}
		}

		if (UseSecondWiseMan && _ao1.HasValue && _ao2.HasValue && _ao3.HasValue && _ao4.HasValue && _ao5.HasValue)
		{
			var currentAo = _ao1.Value;
			var bar2Ao = _ao2.Value;
			var bar3Ao = _ao3.Value;
			var bar4Ao = _ao4.Value;
			var bar5Ao = _ao5.Value;

			var bullishAcceleration = currentAo > bar2Ao && bar2Ao > bar3Ao && bar3Ao > bar4Ao && bar4Ao < bar5Ao;
			if (bullishAcceleration)
				PlaceBuySetup(current, point);

			var bearishAcceleration = currentAo < bar2Ao && bar2Ao < bar3Ao && bar3Ao < bar4Ao && bar4Ao > bar5Ao;
			if (bearishAcceleration)
				PlaceSellSetup(current, point);
		}

		if (UseThirdWiseMan && _teeth0.HasValue)
		{
			var teeth = _teeth0.Value;
			var offset = MagnitudePips * point;

			if (upFractal.HasValue && candle.ClosePrice > teeth + offset)
				PlaceBuySetup(current, point);

			if (downFractal.HasValue && candle.ClosePrice < teeth - offset)
				PlaceSellSetup(current, point);
		}
	}

	private void UpdateProtection(ICandleMessage candle)
	{
		if (Position > 0 && _longStopLoss is decimal longStop)
		{
			if (candle.LowPrice <= longStop)
			{
				SellMarket();
				_longStopLoss = null;
			}
		}
		else if (Position < 0 && _shortStopLoss is decimal shortStop)
		{
			if (candle.HighPrice >= shortStop)
			{
				BuyMarket();
				_shortStopLoss = null;
			}
		}
	}

	private void PlaceBuySetup(CandleInfo bar, decimal point)
	{
		if (Volume <= 0)
			return;

		var entryPrice = bar.High + point;
		if (entryPrice <= 0m)
			return;

		var stopPrice = bar.Low - point;

		// Cancel pending sell
		_pendingSellPrice = null;
		_pendingSellStop = null;

		// Set pending buy entry (simulates buy stop order)
		_pendingBuyPrice = entryPrice;
		_pendingBuyStop = stopPrice;
	}

	private void PlaceSellSetup(CandleInfo bar, decimal point)
	{
		if (Volume <= 0)
			return;

		var entryPrice = bar.Low - point;
		if (entryPrice <= 0m)
			return;

		var stopPrice = bar.High + point;

		// Cancel pending buy
		_pendingBuyPrice = null;
		_pendingBuyStop = null;

		// Set pending sell entry (simulates sell stop order)
		_pendingSellPrice = entryPrice;
		_pendingSellStop = stopPrice;
	}

	private static bool IsBullishDivergent(CandleInfo current, CandleInfo previous)
	{
		var median = (current.High + current.Low) / 2m;
		return current.Low < previous.Low && current.Close > median;
	}

	private static bool IsBearishDivergent(CandleInfo current, CandleInfo previous)
	{
		var median = (current.High + current.Low) / 2m;
		return current.High > previous.High && current.Close < median;
	}

	private decimal? GetUpFractal()
	{
		if (_bar0 is not CandleInfo bar0 || _bar1 is not CandleInfo bar1 || _bar2 is not CandleInfo bar2 ||
			_bar3 is not CandleInfo bar3 || _bar4 is not CandleInfo bar4)
			return null;

		return bar2.High > bar3.High && bar2.High > bar4.High && bar2.High > bar1.High && bar2.High > bar0.High
			? bar2.High
			: null;
	}

	private decimal? GetDownFractal()
	{
		if (_bar0 is not CandleInfo bar0 || _bar1 is not CandleInfo bar1 || _bar2 is not CandleInfo bar2 ||
			_bar3 is not CandleInfo bar3 || _bar4 is not CandleInfo bar4)
			return null;

		return bar2.Low < bar3.Low && bar2.Low < bar4.Low && bar2.Low < bar1.Low && bar2.Low < bar0.Low
			? bar2.Low
			: null;
	}

	private void UpdateBarHistory(ICandleMessage candle)
	{
		_bar4 = _bar3;
		_bar3 = _bar2;
		_bar2 = _bar1;
		_bar1 = _bar0;

		_bar0 = new CandleInfo
		{
			Open = candle.OpenPrice,
			High = candle.HighPrice,
			Low = candle.LowPrice,
			Close = candle.ClosePrice
		};
	}

	private struct CandleInfo
	{
		public decimal Open { get; init; }
		public decimal High { get; init; }
		public decimal Low { get; init; }
		public decimal Close { get; init; }
	}
}