Ver en GitHub

Estrategia Last ZZ50

Descripción general

La estrategia Last ZZ50 reproduce el asesor experto "Last ZZ50" de Vladimir Karputov para MetaTrader. Utiliza el indicador ZigZag para rastrear los tres puntos de inflexión más recientes y coloca órdenes pendientes en el punto medio de las dos últimas piernas del ZigZag. El enfoque intenta unirse a los breakouts desde el último swing mientras cancela o reposiciona órdenes cada vez que la estructura del ZigZag cambia.

Lógica de trading

  • Detección de pivotes – Un indicador ZigZag (profundidad 12, desviación 5, backstep 3 por defecto) proporciona los últimos pivotes etiquetados A (más reciente), B y C.
  • Orden del tramo BC – Cuando el pivote C difiere de B y el nuevo pivote A no invalida la dirección del tramo, la estrategia coloca una orden pendiente en (B + C) / 2.
    • Si el tramo BC está subiendo la orden es larga, de lo contrario es corta.
    • El tipo límite versus stop se selecciona según el precio actual relativo al punto medio.
  • Orden del tramo AB – La misma lógica de punto medio se aplica al tramo AB, nuevamente usando órdenes límite o stop dependiendo del precio actual.
  • Filtro de sesión – El trading está limitado a un día de la semana configurable y ventana intradía (por defecto lunes 09:01 a viernes 21:01). Fuera de la ventana la estrategia cancela órdenes pendientes y puede opcionalmente aplanar cualquier posición.
  • Salida con trailing – Una vez que una posición gana más que la suma de los umbrales de trailing stop y trailing step, una orden stop protectora se arrastra detrás del precio para asegurar ganancias.

Gestión de riesgos

  • El volumen de órdenes pendientes es igual al parámetro multiplicador por el volumen mínimo negociable del instrumento.
  • Tanto las órdenes AB como BC se cancelan y recrean cada vez que los pivotes del ZigZag cambian, evitando que órdenes obsoletas queden en el libro.
  • Los trailing stops solo se activan después de que la posición está cómodamente en ganancia, reduciendo salidas prematuras en condiciones agitadas.

Parámetros

  • LotMultiplier – Multiplicador aplicado al volumen mínimo negociable al enviar órdenes.
  • ZigZagDepth, ZigZagDeviation, ZigZagBackstep – Valores de configuración para el indicador ZigZag.
  • TrailingStopPips, TrailingStepPips – Distancia y umbral de activación para el trailing stop medido en pips.
  • StartDay, EndDay, StartTime, EndTime – Límites de la sesión de trading.
  • CloseOutsideSession – Si se deben aplanar posiciones cuando el filtro de tiempo está inactivo.
  • CandleType – Serie de velas usada para cálculos del ZigZag (por defecto 1 hora).

Indicadores

  • ZigZag – Proporciona puntos pivote que impulsan la colocación de órdenes y la validación de estructura.
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>
/// Strategy that mirrors the "Last ZZ50" MetaTrader expert.
/// It reads the latest ZigZag pivots and enters at the midpoint of the last two legs.
/// </summary>
public class LastZz50Strategy : Strategy
{
	private readonly StrategyParam<decimal> _zigZagDeviation;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ZigZag _zigZag = null!;
	private readonly List<decimal> _pivots = new();
	private decimal _entryPrice;

	/// <summary>
	/// ZigZag deviation (0..1).
	/// </summary>
	public decimal ZigZagDeviation
	{
		get => _zigZagDeviation.Value;
		set => _zigZagDeviation.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public LastZz50Strategy()
	{
		_zigZagDeviation = Param(nameof(ZigZagDeviation), 0.003m)
			.SetDisplay("ZigZag Deviation", "Percentage change threshold (0..1)", "ZigZag")
			.SetRange(0.000001m, 0.999999m);

		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Protective stop distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Target distance in price steps", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles used to evaluate the ZigZag", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_pivots.Clear();
		_zigZag?.Reset();
		_entryPrice = 0m;
	}

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

		_zigZag = new ZigZag
		{
			Deviation = ZigZagDeviation
		};

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

		var step = Security?.PriceStep ?? 1m;
		var stopLoss = StopLossPoints > 0 ? new Unit(step * StopLossPoints, UnitTypes.Absolute) : (Unit)null;
		var takeProfit = TakeProfitPoints > 0 ? new Unit(step * TakeProfitPoints, UnitTypes.Absolute) : (Unit)null;

		if (stopLoss != null || takeProfit != null)
			StartProtection(takeProfit, stopLoss);

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

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

		var result = _zigZag.Process(new CandleIndicatorValue(_zigZag, candle));

		if (!_zigZag.IsFormed)
			return;

		// Store new pivot if zigzag returned a value
		if (result is ZigZagIndicatorValue zzVal && !zzVal.IsEmpty)
		{
			var pivotPrice = zzVal.ToDecimal();
			if (pivotPrice > 0)
			{
				// Update or add pivot
				if (_pivots.Count > 0 && Math.Abs(_pivots[^1] - pivotPrice) < (Security?.PriceStep ?? 0.01m))
				{
					_pivots[^1] = pivotPrice;
				}
				else
				{
					_pivots.Add(pivotPrice);
					if (_pivots.Count > 50)
						_pivots.RemoveAt(0);
				}
			}
		}

		if (_pivots.Count < 3)
			return;

		var priceA = _pivots[^1];
		var priceB = _pivots[^2];
		var priceC = _pivots[^3];
		var price = candle.ClosePrice;

		// Midpoint of BC leg
		var midBC = (priceB + priceC) / 2m;

		// Midpoint of AB leg
		var midAB = (priceA + priceB) / 2m;

		// If last pivot is a low (B < C means upswing), buy at midpoint
		// If last pivot is a high (B > C means downswing), sell at midpoint
		if (priceB < priceC)
		{
			// Upswing from B to C, expect continuation up
			// Buy if price pulls back to midpoint
			if (price <= midBC && Position <= 0)
			{
					BuyMarket();
				_entryPrice = price;
			}
		}
		else if (priceB > priceC)
		{
			// Downswing from B to C, expect continuation down
			// Sell if price rallies to midpoint
			if (price >= midBC && Position >= 0)
			{
					SellMarket();
				_entryPrice = price;
			}
		}
	}
}