Auf GitHub ansehen

ZigZag EvgeTrofi 1 Strategie

Überblick

ZigZag EvgeTrofi 1 reproduziert das Verhalten des ursprünglichen MetaTrader-Expertenberaters, der auf den neuesten ZigZag-Schwungpunkt reagiert. Die Strategie überwacht jede abgeschlossene Kerze, identifiziert den aktuellsten ZigZag-Pivot mithilfe der klassischen Tiefen-, Abweichungs- und Backstep-Konfiguration und steigt in den Markt ein, wenn der Pivot noch aktuell ist. Ein Swing-Hoch löst eine Long-Position aus, während ein Swing-Tief eine Short-Position eröffnet, was der ursprünglichen EA-Signalkarte entspricht.

Handelslogik

  • Abonnieren Sie den konfigurierten Kerzentyp und füttern Sie die höchsten/niedrigsten Indikatoren, deren Länge mit dem ZigZag-Tiefenparameter übereinstimmt. Das Indikatorenpaar emuliert die native ZigZag-Schwungerkennung, ohne auf benutzerdefinierte Puffer angewiesen zu sein.
  • Wenn eine Kerze schließt, prüfen Sie, ob ihr Hoch das verfolgte Maximum oder ihr Tief das verfolgte Minimum berührt. Wechseln Sie nur dann zu einem neuen Pivot, wenn die erforderliche Abweichung in den Preisschritten erfüllt ist und der Backstep-Abstand (Mindestbalken zwischen gegenüberliegenden Pivots) eingehalten wird.
  • Sobald ein Pivot aufgezeichnet wurde, zählen Sie weiter, wie viele Balken vergangen sind. Der Dringlichkeitsparameter definiert, wie viele Balken nach dem Pivot noch als umsetzbar gelten. Signale, die älter als dieser Grenzwert sind, werden ignoriert, wodurch verspätete Eingaben verhindert werden.
  • Bei einem hohen Pivot bereitet die Strategie den Kauf vor, bei einem niedrigen Pivot bereitet sie den Verkauf vor. Wenn eine offene Position bereits der beabsichtigten Richtung entspricht, wird das Signal als bearbeitet markiert und es werden keine weiteren Aufträge übermittelt.
  • Wenn das Konto derzeit ein Engagement in der entgegengesetzten Richtung aufweist, sendet die Strategie eine Marktorder zur Abflachung, bevor ein neuer Handel eröffnet wird. Anschließend wird umgehend eine Marktorder mit dem konfigurierten Volumen zum Aufbau der neuen Position übermittelt.
  • Jede Aktion erfordert einen vollständig ausgebildeten Indikatorzustand, eine fertige Kerze und ein positives Handelsvolumen. Die Strategie überprüft die Konnektivität und Berechtigungen mithilfe von IsFormedAndOnlineAndAllowTrading(), bevor mit dem Markt interagiert wird, und stellt so sicher, dass Aufträge nur unter gesunden Handelsbedingungen gesendet werden.

Parameter

Name Beschreibung Standard
Depth Zickzacktiefe, die das Schwungerkennungsfenster definiert. 17
Deviation Mindestpreisbewegung in Punkten, die zur Bestätigung des Pivots desselben Typs erforderlich ist. Intern in Instrumentenpreisschritte umgerechnet. 7
Backstep Mindestanzahl an Balken, die durchlaufen werden müssen, bevor zu einem entgegengesetzten Pivot gewechselt wird. 5
Urgency Maximale Anzahl an Balken nach einem Pivot, während derer Trades zulässig sind. 2
Candle Type Für Berechnungen verwendeter Kerzendatentyp (Zeitrahmen oder benutzerdefinierte Aggregation). Zeitrahmen von 5 Minuten
Volume Marktauftragsvolumen, das bei jedem Eintrag übermittelt wird. 0,1

Implementierungshinweise

  • Die Höchst-/Tiefstindikatoren sind über die obere Ebene SubscribeCandles().Bind() API gebunden, sodass die Strategie nur auf die letzten Kerzen angewendet wird und eine manuelle Pufferung vermieden wird.
  • Der Abweichungsparameter wird mithilfe der Instrumentenpreisstufe in eine absolute Preisdifferenz umgewandelt. Wenn für das Symbol keine Preisschritt-Metadaten vorhanden sind, wird ein Wert von 1 als Fallback verwendet, um die Logik über alle Börsen hinweg konsistent zu halten.
  • Ein boolescher Schutz verhindert doppelte Trades pro Pivot und entspricht dem MetaTrader EA-Verhalten, das nur einmal pro Schwung auftritt.
  • Die integrierte Chart-Integration zeichnet Kerzen und ausgeführte Trades automatisch, wenn Charts verfügbar sind, was dabei hilft, Swing-Punkte und Einstiege visuell zu validieren.
  • Das Positionsmanagement ist symmetrisch: Jedes gegensätzliche Engagement wird mit einer Marktorder gleichen Volumens abgeflacht, bevor der neue Handel eingerichtet wird, sodass das Portfolio wie beim ursprünglichen Expertenberater einseitig bleibt.
namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// ZigZag swing strategy that reproduces the ZigZagEvgeTrofi 1 expert advisor.
/// Enters when a fresh ZigZag turning point appears within a limited number of bars.
/// </summary>
public class ZigZagEvgeTrofi1Strategy : 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<DataType> _candleType;
	private readonly StrategyParam<decimal> _volume;

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

	/// <summary>
	/// ZigZag depth parameter identical to the original expert advisor.
	/// </summary>
	public int Depth
	{
		get => _depth.Value;
		set => _depth.Value = value;
	}

	/// <summary>
	/// Minimum deviation expressed in points that confirms a new swing.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// Minimum bars required before switching to an opposite pivot.
	/// </summary>
	public int Backstep
	{
		get => _backstep.Value;
		set => _backstep.Value = value;
	}

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

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

	/// <summary>
	/// Volume applied to each market order.
	/// </summary>
	public decimal VolumePerTrade
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ZigZagEvgeTrofi1Strategy"/> class.
	/// </summary>
	public ZigZagEvgeTrofi1Strategy()
	{
		_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)
			.SetNotNegative()
			.SetDisplay("Backstep", "Bars to wait before switching pivots", "ZigZag")
			
			.SetOptimize(0, 10, 1);

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

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

		_volume = Param(nameof(VolumePerTrade), 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;
		_signalHandled = true;
		_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)
	{
		// React only on completed candles to mirror the original bar-by-bar logic.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure both indicators collected enough data before generating signals.
		if (_highest == null || _lowest == null || !_highest.IsFormed || !_lowest.IsFormed)
			return;

		// Track how many bars have passed since the latest identified pivot.
		if (_pivotType != PivotTypes.None && _barsSincePivot < int.MaxValue)
			_barsSincePivot++;

		var deviationPrice = GetDeviationPrice();
		var canSwitch = _pivotType == PivotTypes.None || _barsSincePivot >= Backstep;

		// Detect a new swing high whenever price matches 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 new swing low whenever price touches 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);
		}

		// Stop if no pivot is available after the checks above.
		if (_pivotType == PivotTypes.None)
			return;

		// Skip if the pivot is already considered stale by the urgency filter.
		if (_barsSincePivot > Urgency)
			return;

		// Avoid firing multiple orders for the same pivot.
		if (_signalHandled)
			return;

		// Confirm that trading permissions and connections are valid.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var volume = VolumePerTrade;
		if (volume <= 0m)
		{
			_signalHandled = true;
			return;
		}

		var isBuySignal = _pivotType == PivotTypes.High;

		// Do nothing if a position in the same direction is already open.
		if (isBuySignal)
		{
			if (Position > 0m)
			{
				_signalHandled = true;
				return;
			}
		}
		else
		{
			if (Position < 0m)
			{
				_signalHandled = true;
				return;
			}
		}

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

		_signalHandled = true;
	}

	// Update the internal pivot state when a new turning point is registered.
	private void SetPivot(PivotTypes type, decimal price)
	{
		_pivotType = type;
		_pivotPrice = price;
		_barsSincePivot = 0;
		_signalHandled = false;
	}

	// Convert the deviation parameter expressed in points to an absolute price move.
	private decimal GetDeviationPrice()
	{
		var step = _priceStep > 0m ? _priceStep : 1m;
		var deviation = Deviation;
		if (deviation <= 0m)
			return step;

		var value = deviation * step;
		return value >= step ? value : step;
	}

	// Determine the effective price step for transforming point-based inputs.
	private decimal GetEffectivePriceStep()
	{
		if (Security != null)
		{
			if (Security.PriceStep.HasValue && Security.PriceStep.Value > 0m)
				return Security.PriceStep.Value;
		}

		return 1m;
	}
}