Auf GitHub ansehen

ZigZag EvgeTrofi Strategie

Die ZigZag EvgeTrofi Strategie portiert den klassischen MetaTrader Expert Advisor in die StockSharp High-Level-API. Sie beobachtet den jüngsten Swing, der durch einen ZigZag-ähnlichen Prozess erkannt wurde, und reagiert schnell, solange der Pivot noch frisch ist.

Konzept

  • Der ursprüngliche Advisor analysiert den ersten Nicht-Null-Punkt des ZigZag-Puffers und entscheidet, ob der letzte bestätigte Swing ein Hoch oder ein Tief war.
  • Ein Swing-Hoch generiert standardmäßig einen Long-Einstieg. Durch Aktivierung von SignalReverse wird die Logik invertiert.
  • Positionen werden nur eröffnet, solange der neue Pivot als frisch gilt. Der Urgency-Parameter begrenzt die Anzahl der Balken nach einem Pivot, wenn Trades initiiert werden können.
  • Bestehende Positionen in der entgegengesetzten Richtung werden sofort vor der Platzierung neuer Orders geflacht. Die Strategie kann auf aufeinanderfolgenden Balken in dieselbe Richtung skalieren, während das Dringlichkeitsfenster offen ist.

Dieser Port behält das konträre Verhalten bei: Neue Hochs lösen Long-Trades aus, während frische Tiefs Shorts auslösen, was dem ursprünglichen Setup entspricht.

Funktionsweise

  1. Zwei rollende Indikatoren (Highest und Lowest) approximieren die MetaTrader ZigZag-Tiefenlogik.
  2. Wann immer der Preis ein neues Extrem über/unter diesen Bändern druckt und die Bewegung Deviation (in Preisschritten) übersteigt, wird ein Pivot aufgezeichnet.
  3. Der Algorithmus verfolgt, wie viele Balken seit dem Pivot vergangen sind. Sobald der Zähler Urgency überschreitet, läuft das Signal ab.
  4. Auf jeder geschlossenen Kerze während des aktiven Fensters gibt die Strategie unter Verwendung von VolumePerTrade ein. Entgegengesetzte Exposure wird zuerst geschlossen, sodass Flip-Trades sauber vonstattengehen.

Parameter

Parameter Standard Beschreibung
Depth 17 Fenster in Balken, um nach Swing-Hochs/-Tiefs zurückzuschauen. Entspricht dem ZigZag-Tiefen-Input.
Deviation 7 Mindest-Preisverschiebung in Punkten (multipliziert mit dem Symbol-Preisschritt), die zum Akzeptieren eines neuen Pivots erforderlich ist.
Backstep 5 Balken, die ablaufen müssen, bevor der Indikator zur entgegengesetzten Pivot-Richtung wechseln kann.
Urgency 2 Maximale Anzahl von Balken nach dem Pivot, wenn Einstiege erlaubt sind.
SignalReverse false Kehrt die Zuordnung von Hochs/Tiefs zu Long-/Short-Signalen um.
CandleType 5-Minuten-Kerzen Zeitrahmen für die Analyse. An den gewünschten Chart anpassen.
VolumePerTrade 0.10 Ordergröße bei jedem Einstieg. Entspricht dem ursprünglichen Lot-Input.

Handelshinweise

  • Die Logik enthält keine Stops oder Ziele. Risikosteuerung muss bei Bedarf über Overlays oder Portfolio-Einstellungen hinzugefügt werden.
  • Da das System auf jedem Balken innerhalb des Dringlichkeitsfensters zu einer Position hinzufügen kann, kann die Positionsgröße bei starken Trends schnell wachsen.
  • Verwenden Sie höhere Tiefen bei volatilen Symbolen, um übermäßige Pivots zu vermeiden. Niedrigere Tiefen machen die Strategie reaktiver, aber rauschiger.
  • Wenn SignalReverse true ist, wird das Verhalten zum Breakout-Folgen: Swing-Hochs lösen Shorts aus und Swing-Tiefs lösen Longs aus.

Dateien

  • CS/ZigZagEvgeTrofiStrategy.cs – C#-Implementierung der Strategie.
  • Die Python-Version wird absichtlich nicht bereitgestellt.
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;
	}
}