Ver en GitHub

Estrategia de bisturí ZigAndZag

Descripción general

ZigAndZagScalpelStrategy es un puerto StockSharp del kit de herramientas MetaTrader 4 "ZigAndZag" (carpeta 8304). El paquete original combina un indicador personalizado y un asesor experto. Se utilizan dos ventanas ZigZag:

  • KeelOver: un detector de oscilación retrospectivo que marca la tendencia dominante.
  • Slalom: un breve detector de oscilación retrospectiva que define rupturas procesables.

Cuando el ZigZag a largo plazo gira hacia arriba, la estrategia busca el próximo mínimo de Slalom y espera el precio. para elevar un número configurable de puntos por encima de ese pivote. Una orden de compra de mercado se emite una vez que se alcanza la distancia de ruptura. Una regla simétrica abre una posición corta cuando cambia la tendencia KeelOver baja, el Slalom alcanza un nuevo máximo y el precio cae por debajo de él. Las posiciones se pueden cerrar opcionalmente. tan pronto como se confirma el pivote de Slalom opuesto, imitando la eliminación de la flecha de límite del indicador.

La implementación mantiene el limitador de comercio diario del asesor experto. Sólo un número configurable Se permite un número de operaciones por día de negociación, restableciéndose automáticamente a la medianoche (hora de cambio). esto reproduce la bandera de "nuevo día" del código original.

como funciona

  1. Suscríbase al flujo de velas principal definido por CandleType.
  2. Alimenta dos instancias ZigZagIndicator:
    • Profundidad = KeelOverLength para el detector de tendencias.
    • Profundidad = SlalomLength para señales de entrada.
  3. Realice un seguimiento del pivote KeelOver más reciente para determinar si la tendencia es alcista (el último pivote es bajo) o hacia abajo (el último pivote es alto).
  4. Cuando el indicador Slalom publique un nuevo pivote, arme una ruptura en esa dirección.
  5. Calcule el precio ponderado (5×Close + 2×Open + High + Low) / 9. Si el precio se mueve más de BreakoutDistancePoints (convertido en unidades de precio) lejos del pivote mientras la tendencia respalda el movimiento, ejecutar una orden de mercado.
  6. Cerrar las posiciones existentes cuando la tendencia global cambie o aparezca el pivote de Slalom opuesto y CloseOnOppositePivot está habilitado.
  7. Reinicie el contador de operaciones diario con cada cambio de día calendario.

Los parámetros DeviationPoints y Backstep se comparten entre ambas instancias de ZigZag, por lo que La estructura swing coincide con los buffers del indicador MetaTrader.

Parámetros

Nombre Predeterminado Descripción
CandleType 15m Plazo principal utilizado para construir ambas escaleras ZigZag.
KeelOverLength 55 Lookback en ZigZag de largo plazo que define la tendencia (original KeelOver).
SlalomLength 17 Retrospectiva en ZigZag a corto plazo utilizada para las entradas (original Slalom).
DeviationPoints 5 Tamaño mínimo del swing en puntos antes de que se confirme un nuevo pivote en ZigZag.
Backstep 3 Distancia de barra requerida entre pivotes consecutivos.
BreakoutDistancePoints 2 Distancia desde un pivote (en puntos) antes de disparar una orden.
MaxTradesPerDay 1 Número máximo de entradas por día natural. Refleja la bandera newday original.
CloseOnOppositePivot true Cerrar posiciones abiertas cuando el Slalom ZigZag produzca el swing contrario.

Todos los parámetros basados en puntos se convierten a unidades de precio usando Security.PriceStep. Si el instrumento no tiene ningún paso de precio configurado, se utiliza un valor de 1 para mantener la estrategia funcional durante la prueba.

Notas de uso

  • La estrategia opera con órdenes de mercado (BuyMarket / SellMarket). Adjunte sus propias reglas de riesgo o ayudantes de stop-loss si se requiere una gestión de riesgos más estricta.
  • Debido a que ambos indicadores ZigZag comparten el mismo flujo de velas, asegúrese de que el CandleType elegido sea compatible con su adaptador de datos.
  • MaxTradesPerDay = 1 reproduce el comportamiento de "una operación por día". Aumente el valor si lo necesita múltiples entradas durante la misma sesión.
  • Configure CloseOnOppositePivot = false para mantener las posiciones abiertas hasta que la tendencia global se revierta en lugar de reaccionando a cada cambio a corto plazo.

Diferencias vs. el asesor experto MT4

  • La versión MetaTrader colocó flechas de límite pendiente. El puerto StockSharp ejecuta rupturas con órdenes de mercado inmediatas para permanecer dentro del nivel alto API.
  • Se omiten intencionalmente la gestión de riesgos, el tamaño de los lotes y los cierres parciales. Utilice la posición StockSharp Dimensionamiento de ayudantes si necesita un control de capital avanzado.
  • Los buffers de indicadores 4/5/6 se reemplazan por lógica de estrategia directa y anotaciones de gráficos a través de DrawIndicator y DrawOwnTrades.

Extensiones recomendadas

  • Agregue parámetros de stop-loss y take-profit vinculados a ATR o cambios recientes en ZigZag.
  • Superponga el indicador original con BreakoutDistancePoints = 0 para visualizar la escalera de pivote sin formato.
  • Combínelo con un filtro de sesión (IsFormedAndOnlineAndAllowTrading) para limitar el horario comercial.
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

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

/// <summary>
/// ZigAndZagScalpel translation that trades on breakouts from short-term pivots confirmed by a long-term ZigZag trend.
/// </summary>
public class ZigAndZagScalpelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maxTradesPerDay;
	private readonly StrategyParam<bool> _closeOnOppositePivot;

	private decimal _previousMajorPivot;
	private decimal _lastMajorPivot;
	private decimal _previousMinorPivot;
	private decimal _lastMinorPivot;
	private DateTime _currentDay = DateTime.MinValue;
	private int _tradesToday;
	private bool _trendUp;
	private PivotTypes _lastMinorPivotType = PivotTypes.None;
	private bool _minorPivotUsed;

	/// <summary>
	/// Initializes a new instance of the <see cref="ZigAndZagScalpelStrategy"/> class.
	/// </summary>
	public ZigAndZagScalpelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for all calculations", "General");

		_maxTradesPerDay = Param(nameof(MaxTradesPerDay), 1)
			.SetDisplay("Max Trades Per Day", "Daily limit matching the original expert advisor", "Trading");

		_closeOnOppositePivot = Param(nameof(CloseOnOppositePivot), true)
			.SetDisplay("Close On Opposite Pivot", "Exit when the entry ZigZag prints the opposite swing", "Risk");
	}

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

	/// <summary>
	/// Maximum number of trades allowed per trading day.
	/// </summary>
	public int MaxTradesPerDay
	{
		get => _maxTradesPerDay.Value;
		set => _maxTradesPerDay.Value = value;
	}

	/// <summary>
	/// Determines whether open positions should be closed on the opposite entry pivot.
	/// </summary>
	public bool CloseOnOppositePivot
	{
		get => _closeOnOppositePivot.Value;
		set => _closeOnOppositePivot.Value = value;
	}

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

		_previousMajorPivot = 0m;
		_lastMajorPivot = 0m;
		_previousMinorPivot = 0m;
		_lastMinorPivot = 0m;
		_currentDay = DateTime.MinValue;
		_tradesToday = 0;
		_trendUp = false;
		_lastMinorPivotType = PivotTypes.None;
		_minorPivotUsed = false;
	}

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

		var majorZigZag = new ZigZag { Deviation = 0.02m };
		var minorZigZag = new ZigZag { Deviation = 0.005m };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindWithEmpty(majorZigZag, minorZigZag, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal? majorValue, decimal? minorValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		UpdateDailyCounter(candle.OpenTime);

		if (majorValue is not null)
			UpdateMajorTrend(majorValue.Value);

		if (minorValue is not null)
			UpdateMinorPivot(minorValue.Value);

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		ManageExistingPosition();

		if (Position != 0)
			return;

		if (_minorPivotUsed)
			return;

		if (_lastMinorPivotType == PivotTypes.None)
			return;

		if (_tradesToday >= MaxTradesPerDay)
			return;

		var navel = CalculateNavel(candle);

		if (_lastMinorPivotType == PivotTypes.Low && _trendUp)
		{
			if (navel > _lastMinorPivot)
			{
				BuyMarket();
				_minorPivotUsed = true;
				_tradesToday++;
			}
		}
		else if (_lastMinorPivotType == PivotTypes.High && !_trendUp)
		{
			if (navel < _lastMinorPivot)
			{
				SellMarket();
				_minorPivotUsed = true;
				_tradesToday++;
			}
		}
	}

	private void UpdateDailyCounter(DateTime time)
	{
		var date = time.Date;
		if (date == _currentDay)
			return;

		_currentDay = date;
		_tradesToday = 0;
	}

	private void UpdateMajorTrend(decimal majorValue)
	{
		if (_lastMajorPivot == 0m)
		{
			_lastMajorPivot = majorValue;
			_previousMajorPivot = majorValue;
			return;
		}

		if (majorValue == _lastMajorPivot)
			return;

		_previousMajorPivot = _lastMajorPivot;
		_lastMajorPivot = majorValue;
		_trendUp = _lastMajorPivot < _previousMajorPivot;
	}

	private void UpdateMinorPivot(decimal minorValue)
	{
		if (_lastMinorPivot == 0m)
		{
			_lastMinorPivot = minorValue;
			_previousMinorPivot = minorValue;
			_lastMinorPivotType = PivotTypes.Low;
			_minorPivotUsed = false;
			return;
		}

		if (minorValue == _lastMinorPivot)
			return;

		_previousMinorPivot = _lastMinorPivot;
		_lastMinorPivot = minorValue;
		_lastMinorPivotType = _lastMinorPivot < _previousMinorPivot ? PivotTypes.Low : PivotTypes.High;
		_minorPivotUsed = false;
	}

	private void ManageExistingPosition()
	{
		if (Position > 0)
		{
			if (!_trendUp || (CloseOnOppositePivot && _lastMinorPivotType == PivotTypes.High))
				SellMarket(Position);
		}
		else if (Position < 0)
		{
			if (_trendUp || (CloseOnOppositePivot && _lastMinorPivotType == PivotTypes.Low))
				BuyMarket(Position.Abs());
		}
	}

	private static decimal CalculateNavel(ICandleMessage candle)
	{
		return (5m * candle.ClosePrice + 2m * candle.OpenPrice + candle.HighPrice + candle.LowPrice) / 9m;
	}

	private enum PivotTypes
	{
		None,
		Low,
		High
	}
}