Ver no GitHub

Estratégia Last ZZ50

Visão geral

A estratégia Last ZZ50 reproduz o consultor especializado "Last ZZ50" de Vladimir Karputov para MetaTrader. Usa o indicador ZigZag para rastrear os três pontos de inflexão mais recentes e coloca ordens pendentes no ponto médio das duas últimas pernas do ZigZag. A abordagem tenta se juntar aos rompimentos do último swing enquanto cancela ou reposiciona ordens sempre que a estrutura do ZigZag muda.

Lógica de negociação

  • Detecção de pivôs – Um indicador ZigZag (profundidade 12, desvio 5, backstep 3 por padrão) fornece os pivôs mais recentes rotulados A (mais recente), B e C.
  • Ordem do segmento BC – Quando o pivô C difere de B e o novo pivô A não invalida a direção do segmento, a estratégia coloca uma ordem pendente em (B + C) / 2.
    • Se o segmento BC está subindo a ordem é comprada, caso contrário é vendida.
    • O tipo limite versus stop é selecionado de acordo com o preço atual relativo ao ponto médio.
  • Ordem do segmento AB – A mesma lógica de ponto médio é aplicada ao segmento AB, novamente usando ordens limite ou stop dependendo do preço atual.
  • Filtro de sessão – A negociação é limitada a um dia da semana configurável e janela intradiária (padrão segunda-feira 09:01 a sexta-feira 21:01). Fora da janela, a estratégia cancela ordens pendentes e pode opcionalmente nivelar qualquer posição.
  • Saída com trailing – Uma vez que uma posição ganha mais do que a soma dos limiares de trailing stop e trailing step, uma ordem stop protetora é arrastada atrás do preço para fixar os lucros.

Gestão de risco

  • O volume de ordens pendentes é igual ao parâmetro multiplicador vezes o volume mínimo negociável do instrumento.
  • Tanto as ordens AB quanto BC são canceladas e recriadas sempre que os pivôs do ZigZag mudam, impedindo que ordens obsoletas permaneçam no livro.
  • Os trailing stops só são ativados depois que a posição está confortavelmente no lucro, reduzindo saídas prematuras em condições agitadas.

Parâmetros

  • LotMultiplier – Multiplicador aplicado ao volume mínimo negociável ao enviar ordens.
  • ZigZagDepth, ZigZagDeviation, ZigZagBackstep – Valores de configuração para o indicador ZigZag.
  • TrailingStopPips, TrailingStepPips – Distância e limite de ativação para o trailing stop medido em pips.
  • StartDay, EndDay, StartTime, EndTime – Limites da sessão de negociação.
  • CloseOutsideSession – Se as posições devem ser niveladas quando o filtro de tempo está inativo.
  • CandleType – Série de candles usada para cálculos do ZigZag (padrão 1 hora).

Indicadores

  • ZigZag – Fornece pontos pivô que impulsionam a colocação de ordens e a validação de estrutura.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that mirrors the "Last ZZ50" MetaTrader expert.
/// It reads the latest ZigZag pivots and enters at the midpoint of the last two legs.
/// </summary>
public class LastZz50Strategy : Strategy
{
	private readonly StrategyParam<decimal> _zigZagDeviation;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ZigZag _zigZag = null!;
	private readonly List<decimal> _pivots = new();
	private decimal _entryPrice;

	/// <summary>
	/// ZigZag deviation (0..1).
	/// </summary>
	public decimal ZigZagDeviation
	{
		get => _zigZagDeviation.Value;
		set => _zigZagDeviation.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public LastZz50Strategy()
	{
		_zigZagDeviation = Param(nameof(ZigZagDeviation), 0.003m)
			.SetDisplay("ZigZag Deviation", "Percentage change threshold (0..1)", "ZigZag")
			.SetRange(0.000001m, 0.999999m);

		_stopLossPoints = Param(nameof(StopLossPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pts)", "Protective stop distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pts)", "Target distance in price steps", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles used to evaluate the ZigZag", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_pivots.Clear();
		_zigZag?.Reset();
		_entryPrice = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_zigZag = new ZigZag
		{
			Deviation = ZigZagDeviation
		};

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		var step = Security?.PriceStep ?? 1m;
		var stopLoss = StopLossPoints > 0 ? new Unit(step * StopLossPoints, UnitTypes.Absolute) : (Unit)null;
		var takeProfit = TakeProfitPoints > 0 ? new Unit(step * TakeProfitPoints, UnitTypes.Absolute) : (Unit)null;

		if (stopLoss != null || takeProfit != null)
			StartProtection(takeProfit, stopLoss);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _zigZag);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var result = _zigZag.Process(new CandleIndicatorValue(_zigZag, candle));

		if (!_zigZag.IsFormed)
			return;

		// Store new pivot if zigzag returned a value
		if (result is ZigZagIndicatorValue zzVal && !zzVal.IsEmpty)
		{
			var pivotPrice = zzVal.ToDecimal();
			if (pivotPrice > 0)
			{
				// Update or add pivot
				if (_pivots.Count > 0 && Math.Abs(_pivots[^1] - pivotPrice) < (Security?.PriceStep ?? 0.01m))
				{
					_pivots[^1] = pivotPrice;
				}
				else
				{
					_pivots.Add(pivotPrice);
					if (_pivots.Count > 50)
						_pivots.RemoveAt(0);
				}
			}
		}

		if (_pivots.Count < 3)
			return;

		var priceA = _pivots[^1];
		var priceB = _pivots[^2];
		var priceC = _pivots[^3];
		var price = candle.ClosePrice;

		// Midpoint of BC leg
		var midBC = (priceB + priceC) / 2m;

		// Midpoint of AB leg
		var midAB = (priceA + priceB) / 2m;

		// If last pivot is a low (B < C means upswing), buy at midpoint
		// If last pivot is a high (B > C means downswing), sell at midpoint
		if (priceB < priceC)
		{
			// Upswing from B to C, expect continuation up
			// Buy if price pulls back to midpoint
			if (price <= midBC && Position <= 0)
			{
					BuyMarket();
				_entryPrice = price;
			}
		}
		else if (priceB > priceC)
		{
			// Downswing from B to C, expect continuation down
			// Sell if price rallies to midpoint
			if (price >= midBC && Position >= 0)
			{
					SellMarket();
				_entryPrice = price;
			}
		}
	}
}