Ver en GitHub

Estrategia de Alineación Doble ZigZag

Esta estrategia es un port de StockSharp del experto MQL5 «Double ZigZag». Recrea la lógica de confirmación dual de ZigZag combinando dos detectores de swings con diferentes ventanas de retroceso. Una operación se activa únicamente cuando ambos detectores coinciden en tres pivotes consecutivos y el swing más reciente muestra suficiente fuerza comparado con los anteriores.

Concepto

  • El detector de swings rápido aproxima la configuración original ZigZag(13, 5, 3) usando una ventana deslizante de máximos/mínimos.
  • El detector de swings lento utiliza una ventana más larga (por defecto x8) para confirmar puntos de giro principales.
  • Cuando ambos detectores cambian de dirección en la misma vela, se registra un pivote de «alineación» junto con el número de swings rápidos ocurridos desde la alineación anterior. Estos contadores son análogos directos de los contadores up y dw del EA original.

Configuración Largo

  1. La última alineación es un máximo de swing, la alineación anterior es un mínimo de swing, y la que precede a esta también es un máximo de swing.
  2. El número de swings rápidos acumulados desde la última alineación es mayor que el conteo del segmento anterior multiplicado por StrengthMultiplier (por defecto 2.1). Esto emula la condición original up > dw * k.
  3. El máximo de swing más reciente rompe por encima del mínimo de swing intermedio de forma más agresiva que el máximo más antiguo, es decir (previousHigh - swingLow) * BreakoutMultiplier < (newestHigh - swingLow) con el mismo multiplicador por defecto de 2.1.
  4. Cuando todos los criterios se cumplen, la estrategia compra un volumen igual al Volume configurado más cualquier posición corta pendiente para que la posición neta sea larga.

Configuración Corto

  1. La última alineación es un mínimo de swing, la alineación anterior es un máximo de swing, y la que precede a esta es otro mínimo de swing.
  2. El conteo del segmento más reciente es menor que el conteo anterior dividido por StrengthMultiplier (la comprobación traducida up * k < dw).
  3. El mínimo de swing actual rompe por debajo del máximo de swing intermedio de forma más agresiva que el mínimo más antiguo usando BreakoutMultiplier.
  4. La estrategia vende suficiente volumen para cerrar cualquier posición larga existente y establecer una posición corta neta.

Gestión de Posiciones

  • Las señales son mutuamente excluyentes; un nuevo largo cierra automáticamente cualquier corto y viceversa.
  • No hay órdenes de stop-loss ni take-profit. Las posiciones se mantienen hasta que aparece una señal de alineación opuesta.
  • La estrategia se ejecuta sobre el tipo de vela especificado por CandleType (por defecto marco temporal de 1 minuto).

Valores Predeterminados

  • FastLength = 13
  • SlowLength = 104
  • StrengthMultiplier = 2.1
  • BreakoutMultiplier = 2.1
  • CandleType = marco temporal TimeSpan.FromMinutes(1)

Etiquetas

  • Categoría: Seguimiento de tendencia / Reconocimiento de patrones
  • Dirección: Largo/Corto
  • Indicadores: ZigZag (aproximado), Highest/Lowest
  • Stops: Ninguno
  • Marco temporal: Intradía por defecto
  • Complejidad: Intermedio (requiere seguimiento sincronizado de swings)
  • Estacionalidad: No
  • Redes neuronales: No
  • Divergencia: No
  • Nivel de riesgo: Medio debido a exposición continua sin stops
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Double ZigZag alignment strategy. Uses fast and slow swing detectors
/// based on highest/lowest price lookbacks to find aligned pivot points and trade breakouts.
/// </summary>
public class DoubleZigZagStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	private int _fastDirection;
	private int _slowDirection;
	private decimal _lastFastPivotHigh;
	private decimal _lastFastPivotLow;

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DoubleZigZagStrategy()
	{
		_fastLength = Param(nameof(FastLength), 8)
			.SetDisplay("Fast Length", "Lookback for the fast swing detector", "Indicators");
		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow Length", "Lookback for the slow confirmation swing", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to analyze", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_fastDirection = 0;
		_slowDirection = 0;
		_lastFastPivotHigh = 0;
		_lastFastPivotLow = 0;
	}

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

		_highs.Clear();
		_lows.Clear();
		_fastDirection = 0;
		_slowDirection = 0;
		_lastFastPivotHigh = 0;
		_lastFastPivotLow = 0;

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

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

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		var maxBuf = Math.Max(FastLength, SlowLength) + 1;
		if (_highs.Count > maxBuf)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}

		if (_highs.Count < SlowLength)
			return;

		// Calculate fast and slow highest/lowest
		var fastHighest = GetMax(_highs, FastLength);
		var fastLowest = GetMin(_lows, FastLength);
		var slowHighest = GetMax(_highs, SlowLength);
		var slowLowest = GetMin(_lows, SlowLength);

		var prevFastDir = _fastDirection;
		var prevSlowDir = _slowDirection;

		// Detect fast swing pivot
		if (_fastDirection <= 0 && candle.HighPrice >= fastHighest)
		{
			_fastDirection = 1;
			_lastFastPivotHigh = candle.HighPrice;
		}
		else if (_fastDirection >= 0 && candle.LowPrice <= fastLowest)
		{
			_fastDirection = -1;
			_lastFastPivotLow = candle.LowPrice;
		}

		// Detect slow swing pivot
		if (_slowDirection <= 0 && candle.HighPrice >= slowHighest)
			_slowDirection = 1;
		else if (_slowDirection >= 0 && candle.LowPrice <= slowLowest)
			_slowDirection = -1;

		// When both fast and slow pivot in the same direction, generate signal
		var fastFlippedUp = prevFastDir <= 0 && _fastDirection > 0;
		var fastFlippedDown = prevFastDir >= 0 && _fastDirection < 0;
		var slowFlippedUp = prevSlowDir <= 0 && _slowDirection > 0;
		var slowFlippedDown = prevSlowDir >= 0 && _slowDirection < 0;

		if (fastFlippedUp && (slowFlippedUp || _slowDirection > 0))
		{
			if (Position <= 0)
				BuyMarket();
		}
		else if (fastFlippedDown && (slowFlippedDown || _slowDirection < 0))
		{
			if (Position >= 0)
				SellMarket();
		}
	}

	private static decimal GetMax(List<decimal> data, int length)
	{
		var max = decimal.MinValue;
		var start = Math.Max(0, data.Count - length);
		for (var i = start; i < data.Count; i++)
		{
			if (data[i] > max)
				max = data[i];
		}
		return max;
	}

	private static decimal GetMin(List<decimal> data, int length)
	{
		var min = decimal.MaxValue;
		var start = Math.Max(0, data.Count - length);
		for (var i = start; i < data.Count; i++)
		{
			if (data[i] < min)
				min = data[i];
		}
		return min;
	}
}