Ver no GitHub

Estratégia ZigZag EvgeTrofi

A estratégia ZigZag EvgeTrofi porta o clássico assessor especialista do MetaTrader para a API de alto nível do StockSharp. Ela observa o swing mais recente detectado por um processo de estilo ZigZag e reage rapidamente enquanto o pivô ainda está fresco.

Conceito

  • O advisor original analisa o primeiro ponto não-nulo do buffer ZigZag e decide se o último swing confirmado foi um máximo ou um mínimo.
  • Um swing máximo gera uma entrada comprada por padrão. Ativar SignalReverse inverte a lógica.
  • As posições são abertas apenas enquanto o novo pivô é considerado recente. O parâmetro Urgency limita o número de barras após um pivô quando as operações podem ser iniciadas.
  • As posições existentes na direção oposta são achatadas imediatamente antes de novas ordens serem colocadas. A estratégia pode escalar na mesma direção em barras consecutivas enquanto a janela de urgência está aberta.

Este port mantém o comportamento contrário: novos máximos acionam operações compradas enquanto mínimos frescos acionam vendidos, imitando a configuração original.

Como funciona

  1. Dois indicadores móveis (Highest e Lowest) aproximam a lógica de profundidade ZigZag do MetaTrader.
  2. Sempre que o preço imprime um novo extremo acima/abaixo dessas bandas e o movimento excede Deviation (em passos de preço), um pivô é registrado.
  3. O algoritmo rastreia quantas barras passaram desde o pivô. Assim que o contador excede Urgency, o sinal expira.
  4. Em cada vela fechada durante a janela ativa, a estratégia entra usando VolumePerTrade. A exposição oposta é fechada primeiro, para que os giros de posição aconteçam de forma limpa.

Parâmetros

Parâmetro Padrão Descrição
Depth 17 Janela em barras para olhar para trás em busca de swing máximos/mínimos. Espelha a entrada de profundidade do ZigZag.
Deviation 7 Deslocamento mínimo de preço em pontos (multiplicado pelo passo de preço do símbolo) necessário para aceitar um novo pivô.
Backstep 5 Barras que devem decorrer antes que o indicador possa mudar para a direção de pivô oposta.
Urgency 2 Número máximo de barras após o pivô quando as entradas são permitidas.
SignalReverse false Inverte o mapeamento de máximos/mínimos para sinais comprados/vendidos.
CandleType Velas de 5 minutos Período usado para a análise. Ajuste ao gráfico que deseja espelhar.
VolumePerTrade 0.10 Tamanho da ordem enviada em cada entrada. Corresponde à entrada de lotes original.

Notas de trading

  • A lógica não inclui stops ou alvos. O controle de risco deve ser adicionado via overlays ou configurações de portfólio se necessário.
  • Como o sistema pode adicionar a uma posição em cada barra dentro da janela de urgência, o tamanho da posição pode crescer rapidamente em tendências fortes.
  • Use profundidades maiores em símbolos voláteis para evitar pivôs excessivos. Profundidades menores tornam a estratégia mais reativa, mas mais ruidosa.
  • Quando SignalReverse é true, o comportamento se torna seguimento de rompimento: swing máximos acionam vendidos e swing mínimos acionam comprados.

Arquivos

  • CS/ZigZagEvgeTrofiStrategy.cs – Implementação em C# da estratégia.
  • A versão em Python não é fornecida intencionalmente.
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;
using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;



/// <summary>
/// ZigZag pivot strategy based on the original ZigZagEvgeTrofi expert advisor.
/// Reacts to the most recent zigzag swing and enters within a limited number of bars.
/// </summary>
public class ZigZagEvgeTrofiStrategy : Strategy
{
	private enum PivotTypes
	{
		None,
		High,
		Low
	}

	private readonly StrategyParam<int> _depth;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<int> _backstep;
	private readonly StrategyParam<int> _urgency;
	private readonly StrategyParam<bool> _signalReverse;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _volume;

	private Highest _highest;
	private Lowest _lowest;
	private PivotTypes _pivotType;
	private decimal _pivotPrice;
	private int _barsSincePivot;
	private decimal _priceStep;

	/// <summary>
	/// ZigZag depth parameter controlling the swing detection window.
	/// </summary>
	public int Depth
	{
		get => _depth.Value;
		set => _depth.Value = value;
	}

	/// <summary>
	/// Minimum deviation in price steps required to confirm a new pivot.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// Minimum number of bars between opposite pivot updates.
	/// </summary>
	public int Backstep
	{
		get => _backstep.Value;
		set => _backstep.Value = value;
	}

	/// <summary>
	/// Maximum number of bars after a pivot when entries are allowed.
	/// </summary>
	public int Urgency
	{
		get => _urgency.Value;
		set => _urgency.Value = value;
	}

	/// <summary>
	/// Reverses the direction of the generated signals.
	/// </summary>
	public bool SignalReverse
	{
		get => _signalReverse.Value;
		set => _signalReverse.Value = value;
	}

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

	/// <summary>
	/// Trading volume submitted on every entry.
	/// </summary>
	public decimal VolumePerTrade
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ZigZagEvgeTrofiStrategy"/> class.
	/// </summary>
	public ZigZagEvgeTrofiStrategy()
	{
		_depth = Param(nameof(Depth), 17)
			.SetGreaterThanZero()
			.SetDisplay("Depth", "ZigZag depth parameter", "ZigZag")
			
			.SetOptimize(5, 40, 1);

		_deviation = Param(nameof(Deviation), 7m)
			.SetGreaterThanZero()
			.SetDisplay("Deviation", "Minimum price movement in points", "ZigZag")
			
			.SetOptimize(1m, 20m, 1m);

		_backstep = Param(nameof(Backstep), 5)
			.SetGreaterThanZero()
			.SetDisplay("Backstep", "Bars to lock a pivot before switching", "ZigZag")
			
			.SetOptimize(1, 15, 1);

		_urgency = Param(nameof(Urgency), 2)
			.SetNotNegative()
			.SetDisplay("Urgency", "Maximum bars to use the latest signal", "Trading")
			
			.SetOptimize(0, 5, 1);

		_signalReverse = Param(nameof(SignalReverse), false)
			.SetDisplay("Signal Reverse", "Flip long and short entries", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis", "General");

		_volume = Param(nameof(VolumePerTrade), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume per trade", "Trading");
	}

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

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

		_highest = null;
		_lowest = null;
		_pivotType = PivotTypes.None;
		_pivotPrice = 0m;
		_barsSincePivot = int.MaxValue;
		_priceStep = 0m;
	}

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

		_priceStep = GetEffectivePriceStep();
		_highest = new Highest { Length = Depth };
		_lowest = new Lowest { Length = Depth };

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

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

	private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
	{
		// Skip unfinished candles to ensure decisions are made on closed bars only.
		if (candle.State != CandleStates.Finished)
			return;

		// Wait until both indicators are fully formed before reacting.
		if (_highest == null || _lowest == null || !_highest.IsFormed || !_lowest.IsFormed)
			return;

		// Increment the bar counter that measures freshness of the latest pivot.
		if (_pivotType != PivotTypes.None && _barsSincePivot < int.MaxValue)
			_barsSincePivot++;

		var deviationPrice = Math.Max(GetDeviationInPrice(), _priceStep);
		var canSwitch = _pivotType == PivotTypes.None || _barsSincePivot >= Backstep;

		// Detect a fresh swing high if price pushes above the tracked maximum.
		if (candle.HighPrice >= highestValue && highestValue > 0m)
		{
			var difference = candle.HighPrice - _pivotPrice;
			if ((_pivotType != PivotTypes.High && canSwitch) || (_pivotType == PivotTypes.High && difference >= deviationPrice))
				SetPivot(PivotTypes.High, candle.HighPrice);
		}
		// Detect a fresh swing low when price dips under the tracked minimum.
		else if (candle.LowPrice <= lowestValue && lowestValue > 0m)
		{
			var difference = _pivotPrice - candle.LowPrice;
			if ((_pivotType != PivotTypes.Low && canSwitch) || (_pivotType == PivotTypes.Low && difference >= deviationPrice))
				SetPivot(PivotTypes.Low, candle.LowPrice);
		}

		if (_pivotType == PivotTypes.None)
			return;

		var isBuySignal = _pivotType == PivotTypes.High ? !SignalReverse : SignalReverse;

		// Close opposite exposure before entering in the new direction.
		if (isBuySignal)
		{
			if (Position < 0)
			{
				var closeVolume = Math.Abs(Position);
				if (closeVolume > 0m)
					BuyMarket(closeVolume);
			}
		}
		else
		{
			if (Position > 0)
			{
				var closeVolume = Math.Abs(Position);
				if (closeVolume > 0m)
					SellMarket(closeVolume);
			}
		}

		// Enter the market while the pivot is still considered fresh.
		if (_barsSincePivot > Urgency)
			return;

		var volume = VolumePerTrade;
		if (volume <= 0m)
			return;

		if (isBuySignal)
			BuyMarket(volume);
		else
			SellMarket(volume);
	}

	// Update the stored pivot information when a new swing is confirmed.
	private void SetPivot(PivotTypes type, decimal price)
	{
		_pivotType = type;
		_pivotPrice = price;
		_barsSincePivot = 0;
	}

	// Convert the deviation input expressed in points to a price value.
	private decimal GetDeviationInPrice()
	{
		return Deviation * _priceStep;
	}

	// Determine the effective price step for translating point-based parameters.
	private decimal GetEffectivePriceStep()
	{
		if (Security?.PriceStep is > 0m)
			return Security.PriceStep.Value;

		return 1m;
	}
}