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
- Zwei rollende Indikatoren (
Highest und Lowest) approximieren die MetaTrader ZigZag-Tiefenlogik.
- Wann immer der Preis ein neues Extrem über/unter diesen Bändern druckt und die Bewegung Deviation (in Preisschritten) übersteigt, wird ein Pivot aufgezeichnet.
- Der Algorithmus verfolgt, wie viele Balken seit dem Pivot vergangen sind. Sobald der Zähler Urgency überschreitet, läuft das Signal ab.
- 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
# Pivot type constants
PIVOT_NONE = 0
PIVOT_HIGH = 1
PIVOT_LOW = 2
class zig_zag_evge_trofi_strategy(Strategy):
def __init__(self):
super(zig_zag_evge_trofi_strategy, self).__init__()
self._depth = self.Param("Depth", 17)
self._deviation = self.Param("Deviation", 7.0)
self._backstep = self.Param("Backstep", 5)
self._urgency = self.Param("Urgency", 2)
self._signal_reverse = self.Param("SignalReverse", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._highest = None
self._lowest = None
self._pivot_type = PIVOT_NONE
self._pivot_price = 0.0
self._bars_since_pivot = 999999
self._price_step = 1.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Depth(self):
return self._depth.Value
@property
def Deviation(self):
return self._deviation.Value
@property
def Backstep(self):
return self._backstep.Value
@property
def Urgency(self):
return self._urgency.Value
@property
def SignalReverse(self):
return self._signal_reverse.Value
def OnStarted2(self, time):
super(zig_zag_evge_trofi_strategy, self).OnStarted2(time)
sec = self.Security
if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0:
self._price_step = float(sec.PriceStep)
else:
self._price_step = 1.0
self._highest = Highest()
self._highest.Length = self.Depth
self._lowest = Lowest()
self._lowest.Length = self.Depth
self._pivot_type = PIVOT_NONE
self._pivot_price = 0.0
self._bars_since_pivot = 999999
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._highest, self._lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, highest_v, lowest_v):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
if self._pivot_type != PIVOT_NONE and self._bars_since_pivot < 999999:
self._bars_since_pivot += 1
hv = float(highest_v)
lv = float(lowest_v)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
deviation_price = max(self.Deviation * self._price_step, self._price_step)
can_switch = self._pivot_type == PIVOT_NONE or self._bars_since_pivot >= self.Backstep
if high >= hv and hv > 0:
difference = high - self._pivot_price
if (self._pivot_type != PIVOT_HIGH and can_switch) or (self._pivot_type == PIVOT_HIGH and difference >= deviation_price):
self._set_pivot(PIVOT_HIGH, high)
elif low <= lv and lv > 0:
difference = self._pivot_price - low
if (self._pivot_type != PIVOT_LOW and can_switch) or (self._pivot_type == PIVOT_LOW and difference >= deviation_price):
self._set_pivot(PIVOT_LOW, low)
if self._pivot_type == PIVOT_NONE:
return
is_buy_signal = (self._pivot_type != PIVOT_HIGH) if self.SignalReverse else (self._pivot_type == PIVOT_HIGH)
if is_buy_signal:
if self.Position < 0:
self.BuyMarket()
else:
if self.Position > 0:
self.SellMarket()
if self._bars_since_pivot > self.Urgency:
return
if is_buy_signal:
self.BuyMarket()
else:
self.SellMarket()
def _set_pivot(self, pivot_type, price):
self._pivot_type = pivot_type
self._pivot_price = price
self._bars_since_pivot = 0
def OnReseted(self):
super(zig_zag_evge_trofi_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._pivot_type = PIVOT_NONE
self._pivot_price = 0.0
self._bars_since_pivot = 999999
self._price_step = 1.0
def CreateClone(self):
return zig_zag_evge_trofi_strategy()