Ver no GitHub

Estratégia Stopreversal Tm

Visão geral

A estratégia Stopreversal Tm é uma tradução direta do consultor especialista original de MetaTrader 5 Exp_Stopreversal_Tm.mq5. A ideia de negociação segue o indicador personalizado Stopreversal, que mantém um trailing stop dinâmico em torno do preço e gera alertas de reversão sempre que o preço cruza esse limite de rastreamento. A estratégia opera em um único instrumento e um único feed de candles e é projetada para negociação de reversão de tendência com um filtro de sessão definido pelo usuário.

Geração de sinais

O indicador Stopreversal calcula um preço de referência a partir do modo de preço aplicado selecionado e então ajusta um nível de trailing stop por Sensitivity (o parâmetro nPips). Sempre que o novo preço aplicado cruza acima do trailing stop enquanto a barra anterior estava abaixo, um sinal altista é produzido. Inversamente, um sinal baixista aparece quando o novo preço cai abaixo do trailing stop após ter estado acima. Cada sinal altista solicita simultaneamente o fechamento de posições vendidas existentes e a abertura de uma nova posição comprada, enquanto cada sinal baixista fecha comprados e abre vendidos.

Para reproduzir o comportamento da implementação original do MetaTrader, a estratégia pode atrasar a execução de sinais por várias barras completadas (Signal Bar Delay). Isso replica a entrada SignalBar do consultor especialista e impede a negociação no candle ainda em formação.

Filtro de sessão e gerenciamento de posição

O consultor especialista permitia negociações apenas dentro de uma janela de tempo especificada. A estratégia convertida mantém a mesma lógica: quando o sinalizador Use Time Filter está habilitado, as ordens são permitidas apenas dentro da sessão configurada por Start Hour/Minute e End Hour/Minute. Se o horário atual sair da janela permitida, qualquer posição aberta é fechada imediatamente. As saídas impulsionadas por sinais permanecem ativas mesmo quando a sessão está desabilitada.

A estratégia trabalha em posições líquidas. Uma ação de fechamento é sempre executada antes de uma entrada oposta, garantindo que a direção muda sem exposições sobrepostas.

Parâmetros

  • Allow Buy Entries / Allow Sell Entries – habilitar ou desabilitar a abertura de novas posições compradas ou vendidas quando o sinal correspondente é recebido.
  • Allow Long Exits / Allow Short Exits – controlar se os sinais opostos podem fechar posições existentes.
  • Use Time Filter – ativa a janela de sessão de negociação.
  • Start Hour / Start Minute / End Hour / End Minute – define o início inclusivo e o fim exclusivo da janela de negociação. O filtro de tempo suporta sessões noturnas onde o horário de término é anterior ao horário de início.
  • Sensitivity (nPips) – distância relativa (expressa como multiplicador, ex.: 0.004 = 0.4%) usada para mover o trailing stop mais perto ou mais longe do preço.
  • Signal Bar Delay (SignalBar) – número de candles completados a aguardar antes de agir sobre um sinal. 0 executa imediatamente no candle de fechamento, 1 reproduz o comportamento padrão de agir na barra anterior.
  • Candle Type – período da assinatura de candles usada para os cálculos do indicador.
  • Applied Price – escolha da série de preço (fechamento, abertura, preço mediano, modos de seguimento de tendência, preço Demark, etc.) que alimenta o cálculo do trailing stop.

Notas de implementação

  • O indicador é implementado diretamente dentro da estratégia sem depender de buffers externos, garantindo que a lógica do trailing stop nPips corresponda ao código MQL5 original.
  • O gerenciamento de sessão e o sequenciamento de sinais seguem o especialista original, incluindo a prioridade de fechar a exposição existente antes de abrir novas negociações.
  • A conversão foca na API de alto nível do StockSharp: assinaturas de candles, fila de sinais atrasados e ordens de mercado (BuyMarket / SellMarket). As características de gerenciamento de dinheiro vinculadas às métricas de conta do MetaTrader foram omitidas porque as estratégias do StockSharp já operam com tamanhos de posição explícitos.
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>
/// Stopreversal trailing stop strategy with a configurable trading session filter.
/// </summary>
public class StopreversalTmStrategy : Strategy
{
	/// <summary>
	/// Available price sources for the Stopreversal trailing stop.
	/// </summary>
	public enum StopreversalAppliedPrices
	{
		Close = 1,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted,
		Simple,
		Quarter,
		TrendFollow0,
		TrendFollow1,
		Demark
	}

	private readonly StrategyParam<bool> _allowBuyEntry;
	private readonly StrategyParam<bool> _allowSellEntry;
	private readonly StrategyParam<bool> _allowBuyExit;
	private readonly StrategyParam<bool> _allowSellExit;
	private readonly StrategyParam<bool> _useTimeFilter;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _endMinute;
	private readonly StrategyParam<decimal> _nPips;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<StopreversalAppliedPrices> _appliedPrice;

	private readonly List<SignalInfo> _signalQueue = new();

	private decimal? _previousAppliedPrice;
	private decimal? _previousStopLevel;

	public StopreversalTmStrategy()
	{
		_allowBuyEntry = Param(nameof(AllowBuyEntry), true)
			.SetDisplay("Allow Buy Entries", "Enable opening long positions on bullish signals", "Signals")
			;

		_allowSellEntry = Param(nameof(AllowSellEntry), true)
			.SetDisplay("Allow Sell Entries", "Enable opening short positions on bearish signals", "Signals")
			;

		_allowBuyExit = Param(nameof(AllowBuyExit), true)
			.SetDisplay("Allow Long Exits", "Close existing long positions when a sell signal arrives", "Signals")
			;

		_allowSellExit = Param(nameof(AllowSellExit), true)
			.SetDisplay("Allow Short Exits", "Close existing short positions when a buy signal arrives", "Signals")
			;

		_useTimeFilter = Param(nameof(UseTimeFilter), false)
			.SetDisplay("Use Time Filter", "Restrict trading to the configured session", "Session");

		_startHour = Param(nameof(StartHour), 0)
			.SetRange(0, 23)
			.SetDisplay("Start Hour", "Session start hour (0-23)", "Session")
			;

		_startMinute = Param(nameof(StartMinute), 0)
			.SetRange(0, 59)
			.SetDisplay("Start Minute", "Session start minute (0-59)", "Session")
			;

		_endHour = Param(nameof(EndHour), 23)
			.SetRange(0, 23)
			.SetDisplay("End Hour", "Session end hour (0-23)", "Session")
			;

		_endMinute = Param(nameof(EndMinute), 59)
			.SetRange(0, 59)
			.SetDisplay("End Minute", "Session end minute (0-59)", "Session")
			;

		_nPips = Param(nameof(Npips), 0.004m)
			.SetGreaterThanZero()
			.SetDisplay("Sensitivity", "Relative offset used to build the trailing stop", "Indicator")
			;

		_signalBar = Param(nameof(SignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Signal Bar Delay", "Number of completed bars to wait before acting", "Indicator")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for calculations", "General");

		_appliedPrice = Param(nameof(AppliedPrice), StopreversalAppliedPrices.Close)
			.SetDisplay("Applied Price", "Price source for the trailing stop", "Indicator")
			;
	}

	public bool AllowBuyEntry { get => _allowBuyEntry.Value; set => _allowBuyEntry.Value = value; }
	public bool AllowSellEntry { get => _allowSellEntry.Value; set => _allowSellEntry.Value = value; }
	public bool AllowBuyExit { get => _allowBuyExit.Value; set => _allowBuyExit.Value = value; }
	public bool AllowSellExit { get => _allowSellExit.Value; set => _allowSellExit.Value = value; }
	public bool UseTimeFilter { get => _useTimeFilter.Value; set => _useTimeFilter.Value = value; }
	public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }
	public int StartMinute { get => _startMinute.Value; set => _startMinute.Value = value; }
	public int EndHour { get => _endHour.Value; set => _endHour.Value = value; }
	public int EndMinute { get => _endMinute.Value; set => _endMinute.Value = value; }
	public decimal Npips { get => _nPips.Value; set => _nPips.Value = value; }
	public int SignalBar { get => _signalBar.Value; set => _signalBar.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public StopreversalAppliedPrices AppliedPrice { get => _appliedPrice.Value; set => _appliedPrice.Value = value; }

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousAppliedPrice = null;
		_previousStopLevel = null;
		_signalQueue.Clear();
	}

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

		_previousAppliedPrice = null;
		_previousStopLevel = null;
		_signalQueue.Clear();

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

		// no protection needed
	}

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

		var price = GetAppliedPrice(candle);

		if (_previousAppliedPrice is null || _previousStopLevel is null)
		{
			_previousAppliedPrice = price;
			_previousStopLevel = price;
			EnqueueSignal(new SignalInfo(false, false, false, false), candle.CloseTime);
			return;
		}

		var prevPrice = _previousAppliedPrice.Value;
		var prevStop = _previousStopLevel.Value;

		var trailingStop = CalculateTrailingStop(price, prevPrice, prevStop);

		var buySignal = price > trailingStop && prevPrice < prevStop;
		var sellSignal = price < trailingStop && prevPrice > prevStop;

		_previousStopLevel = trailingStop;
		_previousAppliedPrice = price;

		var action = new SignalInfo(
			buySignal && AllowBuyEntry,
			sellSignal && AllowSellEntry,
			sellSignal && AllowBuyExit,
			buySignal && AllowSellExit
		);

		EnqueueSignal(action, candle.CloseTime);
	}

	private void EnqueueSignal(SignalInfo signal, DateTime currentTime)
	{
		_signalQueue.Add(signal);

		while (_signalQueue.Count > SignalBar)
		{
			var action = _signalQueue[0];
			try { _signalQueue.RemoveAt(0); } catch { }
			HandleSignal(action, currentTime);
		}
	}

	private void HandleSignal(SignalInfo signal, DateTime currentTime)
	{
		var inWindow = !UseTimeFilter || IsWithinTradingWindow(currentTime);

		if (UseTimeFilter && !inWindow && Position != 0)
		{
			if (Position > 0)
				SellMarket();
			else
				BuyMarket();
		}

		if (signal.CloseLong && Position > 0)
			SellMarket();

		if (signal.CloseShort && Position < 0)
			BuyMarket();

		if (!UseTimeFilter || inWindow)
		{
			if (signal.OpenLong && Position <= 0)
				BuyMarket();

			if (signal.OpenShort && Position >= 0)
				SellMarket();
		}
	}

	private decimal CalculateTrailingStop(decimal price, decimal prevPrice, decimal prevStop)
	{
		var shift = Npips;

		if (price == prevStop)
			return prevStop;

		if (prevPrice < prevStop && price < prevStop)
			return Math.Min(prevStop, price * (1 + shift));

		if (prevPrice > prevStop && price > prevStop)
			return Math.Max(prevStop, price * (1 - shift));

		return price > prevStop
			? price * (1 - shift)
			: price * (1 + shift);
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		return AppliedPrice switch
		{
			StopreversalAppliedPrices.Close => candle.ClosePrice,
			StopreversalAppliedPrices.Open => candle.OpenPrice,
			StopreversalAppliedPrices.High => candle.HighPrice,
			StopreversalAppliedPrices.Low => candle.LowPrice,
			StopreversalAppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			StopreversalAppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
			StopreversalAppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
			StopreversalAppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
			StopreversalAppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
			StopreversalAppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice
				? candle.HighPrice
				: candle.ClosePrice < candle.OpenPrice
					? candle.LowPrice
					: candle.ClosePrice,
			StopreversalAppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
				? (candle.HighPrice + candle.ClosePrice) / 2m
				: candle.ClosePrice < candle.OpenPrice
					? (candle.LowPrice + candle.ClosePrice) / 2m
					: candle.ClosePrice,
			StopreversalAppliedPrices.Demark => CalculateDemarkPrice(candle),
			_ => candle.ClosePrice
		};
	}

	private static decimal CalculateDemarkPrice(ICandleMessage candle)
	{
		var result = candle.HighPrice + candle.LowPrice + candle.ClosePrice;

		if (candle.ClosePrice < candle.OpenPrice)
			result = (result + candle.LowPrice) / 2m;
		else if (candle.ClosePrice > candle.OpenPrice)
			result = (result + candle.HighPrice) / 2m;
		else
			result = (result + candle.ClosePrice) / 2m;

		return ((result - candle.LowPrice) + (result - candle.HighPrice)) / 2m;
	}

	private bool IsWithinTradingWindow(DateTime time)
	{
		var start = new TimeSpan(StartHour, StartMinute, 0);
		var end = new TimeSpan(EndHour, EndMinute, 0);
		var current = time.TimeOfDay;

		if (start == end)
			return false;

		if (start < end)
			return current >= start && current < end;

		return current >= start || current < end;
	}

	private readonly struct SignalInfo
	{
		public SignalInfo(bool openLong, bool openShort, bool closeLong, bool closeShort)
		{
			OpenLong = openLong;
			OpenShort = openShort;
			CloseLong = closeLong;
			CloseShort = closeShort;
		}

		public bool OpenLong { get; }
		public bool OpenShort { get; }
		public bool CloseLong { get; }
		public bool CloseShort { get; }
	}
}