ZigAndZagScalpelStrategy ist ein StockSharp-Port des MetaTrader 4 „ZigAndZag“-Toolkits (Ordner 8304).
Das Originalpaket kombiniert einen benutzerdefinierten Indikator und einen Expertenberater. Es werden zwei ZigZag-Fenster verwendet:
KeelOver – ein Long-Lookback-Swing-Detektor, der den vorherrschenden Trend markiert.
Slalom – ein kurzer Lookback-Swing-Detektor, der umsetzbare Ausbrüche definiert.
Wenn sich der langfristige ZigZag nach oben dreht, sucht die Strategie nach dem nächsten Slalom-Tief und wartet auf den Preis
um eine konfigurierbare Anzahl von Punkten über diesen Pivot zu steigen. Eine Marktkauforder wird erteilt, sobald die
Ausbruchsdistanz erreicht ist. Eine symmetrische Regel eröffnet eine Short-Position, wenn sich der KeelOver-Trend dreht
Nach unten markiert der Slalom ein neues Hoch und der Preis fällt darunter. Positionen können optional geschlossen werden
Sobald der entgegengesetzte Slalom-Drehpunkt bestätigt wird, wird die Entfernung des Grenzpfeils des Indikators nachgeahmt.
Die Implementierung hält den täglichen Handelslimiter vom Fachberater fern. Nur eine konfigurierbare Nummer
Anzahl der Trades ist pro Handelstag zulässig und wird automatisch um Mitternacht (Börsenzeit) zurückgesetzt. Dies
reproduziert die Flagge „Neuer Tag“ aus dem Originalcode.
Wie es funktioniert
Abonnieren Sie den durch CandleType definierten primären Kerzenstream.
Füttere zwei ZigZagIndicator-Instanzen:
Tiefe = KeelOverLength für den Trenddetektor.
Tiefe = SlalomLength für Eintrittssignale.
Verfolgen Sie den letzten KeelOver-Pivot, um festzustellen, ob der Trend nach oben zeigt (der letzte Pivot ist ein Tief).
oder nach unten (letzter Pivot ist ein Hoch).
Wenn der Slalom-Indikator einen neuen Pivot meldet, aktivieren Sie einen Ausbruch in diese Richtung.
Berechnen Sie den gewichteten Preis (5×Close + 2×Open + High + Low) / 9. Wenn sich der Preis um mehr als bewegt
BreakoutDistancePoints (umgerechnet in Preiseinheiten) vom Pivot entfernt, während der Trend unterstützt
Wenn Sie sich bewegen, führen Sie eine Market-Order aus.
Schließen Sie bestehende Positionen, wenn der globale Trend umkehrt oder der entgegengesetzte Slalom-Pivot erscheint und
CloseOnOppositePivot ist aktiviert.
Setzen Sie den täglichen Handelszähler bei jedem Kalendertagswechsel zurück.
Die Parameter DeviationPoints und Backstep werden von beiden ZigZag-Instanzen gemeinsam genutzt, sodass die
Die Swing-Struktur entspricht den MetaTrader-Indikatorpuffern.
Parameter
Name
Standard
Beschreibung
CandleType
15m
Primärer Zeitrahmen für den Bau beider ZigZag-Leitern.
KeelOverLength
55
Langfristiger ZigZag-Lookback, der den Trend definiert (ursprünglich KeelOver).
SlalomLength
17
Kurzfristiger ZigZag-Lookback, der für Einträge verwendet wird (ursprünglich Slalom).
DeviationPoints
5
Mindestschwunggröße in Punkten, bevor ein neuer ZigZag-Pivot bestätigt wird.
Backstep
3
Erforderlicher Stangenabstand zwischen aufeinanderfolgenden Drehpunkten.
BreakoutDistancePoints
2
Entfernung von einem Drehpunkt (in Punkten), bevor ein Befehl abgegeben wird.
MaxTradesPerDay
1
Maximale Anzahl Einträge pro Kalendertag. Spiegelt die ursprüngliche newday-Flagge wider.
CloseOnOppositePivot
true
Schließen Sie offene Positionen, wenn der Slalom ZigZag den entgegengesetzten Schwung erzeugt.
Alle punktbasierten Parameter werden mit Security.PriceStep in Preiseinheiten umgewandelt. Wenn das Instrument
Ist keine Preisstufe konfiguriert, wird ein Wert von 1 verwendet, um die Strategie während des Tests funktionsfähig zu halten.
Nutzungshinweise
Die Strategie arbeitet mit Marktaufträgen (BuyMarket / SellMarket). Fügen Sie Ihre eigenen Risikoregeln hinzu
oder Stop-Loss-Helfer, wenn ein strengeres Risikomanagement erforderlich ist.
Da beide ZigZag-Indikatoren denselben Kerzenstrom teilen, stellen Sie sicher, dass der ausgewählte CandleType ist
wird von Ihrem Datenadapter unterstützt.
MaxTradesPerDay = 1 reproduziert das Verhalten „ein Trade pro Tag“. Erhöhen Sie den Wert bei Bedarf
mehrere Einträge während derselben Sitzung.
Legen Sie CloseOnOppositePivot = false fest, um Positionen offen zu halten, bis sich der globale Trend umkehrt
auf jede kurzfristige Schwankung reagieren.
Unterschiede zum MT4-Expertenberater
Die MetaTrader-Version hat Pfeile für ausstehende Grenzwerte platziert. Der Port StockSharp führt Breakouts mit aus
sofortige Marktaufträge, um innerhalb des hohen Niveaus API zu bleiben.
Auf Risikomanagement, Losgrößenbestimmung und Teilabschlüsse wird bewusst verzichtet. Verwenden Sie die Position StockSharp
Dimensionierungshilfen, wenn Sie eine erweiterte Kapitalkontrolle benötigen.
Die Indikatorpuffer 4/5/6 werden durch direkte Strategielogik und Diagrammanmerkungen über ersetzt
DrawIndicator und DrawOwnTrades.
Empfohlene Erweiterungen
Fügen Sie Stop-Loss- und Take-Profit-Parameter hinzu, die an ATR oder aktuelle ZigZag-Schwankungen gebunden sind.
Überlagern Sie den ursprünglichen Indikator mit BreakoutDistancePoints = 0, um die rohe Pivot-Leiter zu visualisieren.
Kombinieren Sie es mit einem Sitzungsfilter (IsFormedAndOnlineAndAllowTrading), um die Handelszeiten zu begrenzen.
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
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTime, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ZigZag
# Pivot type constants
PIVOT_NONE = 0
PIVOT_LOW = 1
PIVOT_HIGH = 2
class zig_and_zag_scalpel_strategy(Strategy):
def __init__(self):
super(zig_and_zag_scalpel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary timeframe for all calculations", "General")
self._max_trades_per_day = self.Param("MaxTradesPerDay", 1) \
.SetDisplay("Max Trades Per Day", "Daily limit matching the original expert advisor", "Trading")
self._close_on_opposite_pivot = self.Param("CloseOnOppositePivot", True) \
.SetDisplay("Close On Opposite Pivot", "Exit when the entry ZigZag prints the opposite swing", "Risk")
self._previous_major_pivot = Decimal(0)
self._last_major_pivot = Decimal(0)
self._previous_minor_pivot = Decimal(0)
self._last_minor_pivot = Decimal(0)
self._current_day = DateTime.MinValue
self._trades_today = 0
self._trend_up = False
self._last_minor_pivot_type = PIVOT_NONE
self._minor_pivot_used = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def MaxTradesPerDay(self):
return self._max_trades_per_day.Value
@property
def CloseOnOppositePivot(self):
return self._close_on_opposite_pivot.Value
def OnStarted2(self, time):
super(zig_and_zag_scalpel_strategy, self).OnStarted2(time)
major_zigzag = ZigZag()
major_zigzag.Deviation = Decimal(0.02)
minor_zigzag = ZigZag()
minor_zigzag.Deviation = Decimal(0.005)
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindWithEmpty(major_zigzag, minor_zigzag, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, major_zigzag)
self.DrawIndicator(area, minor_zigzag)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, major_value, minor_value):
if candle.State != CandleStates.Finished:
return
self._update_daily_counter(candle.OpenTime)
if major_value is not None:
self._update_major_trend(major_value)
if minor_value is not None:
self._update_minor_pivot(minor_value)
if not self.IsFormedAndOnlineAndAllowTrading():
return
self._manage_existing_position()
if self.Position != 0:
return
if self._minor_pivot_used:
return
if self._last_minor_pivot_type == PIVOT_NONE:
return
if self._trades_today >= self.MaxTradesPerDay:
return
navel = self._calculate_navel(candle)
if self._last_minor_pivot_type == PIVOT_LOW and self._trend_up:
if navel > self._last_minor_pivot:
self.BuyMarket()
self._minor_pivot_used = True
self._trades_today += 1
elif self._last_minor_pivot_type == PIVOT_HIGH and not self._trend_up:
if navel < self._last_minor_pivot:
self.SellMarket()
self._minor_pivot_used = True
self._trades_today += 1
def _update_daily_counter(self, time):
date = time.Date
if date == self._current_day:
return
self._current_day = date
self._trades_today = 0
def _update_major_trend(self, major_value):
if self._last_major_pivot == Decimal(0):
self._last_major_pivot = major_value
self._previous_major_pivot = major_value
return
if major_value == self._last_major_pivot:
return
self._previous_major_pivot = self._last_major_pivot
self._last_major_pivot = major_value
self._trend_up = self._last_major_pivot < self._previous_major_pivot
def _update_minor_pivot(self, minor_value):
if self._last_minor_pivot == Decimal(0):
self._last_minor_pivot = minor_value
self._previous_minor_pivot = minor_value
self._last_minor_pivot_type = PIVOT_LOW
self._minor_pivot_used = False
return
if minor_value == self._last_minor_pivot:
return
self._previous_minor_pivot = self._last_minor_pivot
self._last_minor_pivot = minor_value
self._last_minor_pivot_type = PIVOT_LOW if self._last_minor_pivot < self._previous_minor_pivot else PIVOT_HIGH
self._minor_pivot_used = False
def _manage_existing_position(self):
if self.Position > 0:
if not self._trend_up or (self.CloseOnOppositePivot and self._last_minor_pivot_type == PIVOT_HIGH):
self.SellMarket(self.Position)
elif self.Position < 0:
if self._trend_up or (self.CloseOnOppositePivot and self._last_minor_pivot_type == PIVOT_LOW):
self.BuyMarket(abs(self.Position))
def _calculate_navel(self, candle):
return (Decimal(5) * candle.ClosePrice + Decimal(2) * candle.OpenPrice +
candle.HighPrice + candle.LowPrice) / Decimal(9)
def OnReseted(self):
super(zig_and_zag_scalpel_strategy, self).OnReseted()
self._previous_major_pivot = Decimal(0)
self._last_major_pivot = Decimal(0)
self._previous_minor_pivot = Decimal(0)
self._last_minor_pivot = Decimal(0)
self._current_day = DateTime.MinValue
self._trades_today = 0
self._trend_up = False
self._last_minor_pivot_type = PIVOT_NONE
self._minor_pivot_used = False
def CreateClone(self):
return zig_and_zag_scalpel_strategy()