ZigAndZagScalpelStrategy é uma porta StockSharp do kit de ferramentas MetaTrader 4 "ZigAndZag" (pasta 8304).
O pacote original combina um indicador personalizado e um consultor especializado. Duas janelas ZigZag são usadas:
KeelOver – um detector de balanço longo que marca a tendência dominante.
Slalom – um detector de oscilação de retrospectiva curta que define interrupções acionáveis.
Quando o ZigZag de longo prazo sobe, a estratégia procura o próximo mínimo do Slalom e espera pelo preço
para subir um número configurável de pontos acima desse pivô. Uma ordem de compra a mercado é emitida assim que o
a distância de fuga é atingida. Uma regra simétrica abre uma posição curta quando a tendência KeelOver muda
para baixo, o Slalom imprime uma nova máxima e o preço cai abaixo dela. As posições podem opcionalmente ser fechadas
assim que o pivô oposto do Slalom for confirmado, imitando a remoção da seta limite do indicador.
A implementação mantém o limitador diário de negociação do consultor especialista. Apenas um número configurável
de negociações é permitido por dia de negociação, zerando automaticamente à meia-noite (horário de câmbio). Isto
reproduz o sinalizador "novo dia" do código original.
Como funciona
Assine o stream de vela principal definido por CandleType.
Alimente duas instâncias ZigZagIndicator:
Profundidade = KeelOverLength para o detector de tendências.
Profundidade = SlalomLength para sinais de entrada.
Acompanhe o pivô KeelOver mais recente para determinar se a tendência é de alta (o último pivô é baixo)
ou para baixo (o último pivô é alto).
Quando o indicador Slalom publicar um novo pivô, prepare um rompimento nessa direção.
Calcule o preço ponderado (5×Close + 2×Open + High + Low) / 9. Se o preço se mover mais do que
BreakoutDistancePoints (convertido em unidades de preço) longe do pivô enquanto a tendência suporta
o movimento, execute uma ordem de mercado.
Feche as posições existentes quando a tendência global mudar ou o pivô oposto do Slalom aparecer e
CloseOnOppositePivot está ativado.
Redefina o contador diário de negociações a cada mudança de dia do calendário.
Os parâmetros DeviationPoints e Backstep são compartilhados entre ambas as instâncias do ZigZag para que o
a estrutura swing corresponde aos buffers do indicador MetaTrader.
Parâmetros
Nome
Padrão
Descrição
CandleType
15m
Período primário usado para construir ambas as escadas ZigZag.
KeelOverLength
55
Lookback ZigZag de longo prazo que define a tendência (original KeelOver).
SlalomLength
17
Lookback ZigZag de curto prazo usado para entradas (original Slalom).
DeviationPoints
5
Tamanho mínimo de swing em pontos antes que um novo pivô ZigZag seja confirmado.
Backstep
3
Distância necessária da barra entre pivôs consecutivos.
BreakoutDistancePoints
2
Distância de um pivô (em pontos) antes de disparar uma ordem.
MaxTradesPerDay
1
Número máximo de entradas por dia corrido. Espelha o sinalizador newday original.
CloseOnOppositePivot
true
Fechar posições abertas quando o Slalom ZigZag produzir o balanço oposto.
Todos os parâmetros baseados em pontos são convertidos em unidades de preço usando Security.PriceStep. Se o instrumento
não tem nenhuma etapa de preço configurada, um valor de 1 é usado para manter a estratégia funcional durante o teste.
Notas de uso
A estratégia opera com ordens de mercado (BuyMarket / SellMarket). Anexe suas próprias regras de risco
ou ajudantes de stop-loss se for necessária uma gestão de risco mais rigorosa.
Como ambos os indicadores ZigZag compartilham o mesmo fluxo de velas, certifique-se de que o CandleType escolhido seja
suportado pelo seu adaptador de dados.
MaxTradesPerDay = 1 reproduz o comportamento "uma negociação por dia". Aumente o valor se precisar
múltiplas entradas durante a mesma sessão.
Defina CloseOnOppositePivot = false para manter as posições abertas até que a tendência global seja revertida em vez de
reagindo a cada oscilação de curto prazo.
Diferenças versus o consultor especialista MT4
A versão MetaTrader colocou setas de limite pendentes. A porta StockSharp executa breakouts com
ordens imediatas de mercado para permanecer dentro do nível superior API.
A gestão de riscos, o dimensionamento de lotes e os fechamentos parciais são intencionalmente omitidos. Use a posição StockSharp
dimensionando ajudantes se você precisar de controle avançado de capital.
Os buffers de indicadores 4/5/6 são substituídos por lógica de estratégia direta e anotações de gráfico via
DrawIndicator e DrawOwnTrades.
Extensões recomendadas
Adicione parâmetros de stop-loss e take-profit vinculados a ATR ou oscilações recentes do ZigZag.
Sobreponha o indicador original com BreakoutDistancePoints = 0 para visualizar a escada dinâmica bruta.
Combine com um filtro de sessão (IsFormedAndOnlineAndAllowTrading) para limitar o horário de negociação.
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()