Ver no GitHub

Estratégia Stopreversal Trailing

A Estratégia Stopreversal Trailing reproduz o expert MT5 Exp_Stopreversal.mq5. Ela usa o indicador personalizado Stopreversal para construir uma linha de trailing stop dinâmica ao redor do preço de vela selecionado. Quando o preço perfura esta linha de trailing para cima, a estratégia trata isso como uma reversão altista, opcionalmente fecha a exposição vendida e abre uma nova posição comprada. Uma perfuração para baixo produz a ação baixista simétrica. Os sinais podem ser atrasados por um número configurável de barras fechadas para corresponder ao comportamento do assessor especialista original.

Detalhes

  • Lógica de entrada: reage às setas do indicador Stopreversal produzidas quando o preço cruza o trailing stop adaptativo.
  • Comprado/Vendido: ambas as direções são suportadas com interruptores independentes para habilitar entradas compradas ou vendidas.
  • Lógica de saída: sinais Stopreversal opostos podem fechar posições existentes; níveis protetores de stop-loss e take-profit também estão disponíveis.
  • Stops: stop-loss e take-profit estáticos em passos de preço mais as reversões impulsionadas pelo indicador.
  • Fonte de dados: qualquer período; o padrão usa velas de 4 horas, espelhando a chamada multi-período do expert original.
  • Atraso de sinal: o parâmetro SignalBar atrasa a execução de ordens pelo número especificado de barras completadas (padrão 1 barra).
  • Gestão de risco: stops duros opcionais expressos em passos de preço do instrumento; o serviço de proteção de posição é ativado no início.
  • Parâmetros do indicador: o offset de trailing Npips controla a distância entre o preço e o stop; PriceMode seleciona o preço de vela usado pelo trailing stop.
  • Valores padrão:
    • Volume = 1
    • StopLossSteps = 1000
    • TakeProfitSteps = 2000
    • BuyPositionOpen = true
    • SellPositionOpen = true
    • BuyPositionClose = true
    • SellPositionClose = true
    • Npips = 0.004
    • PriceMode = Close
    • SignalBar = 1

Parâmetros

Parâmetro Descrição
CandleType Subscrição de velas usada tanto para cálculos de Stopreversal quanto para trading. O padrão é um período de 4 horas.
Volume Tamanho base da ordem enviada ao entrar em uma nova posição.
StopLossSteps Distância da entrada ao stop-loss em passos de preço; definir como 0 para desativar.
TakeProfitSteps Distância da entrada ao take-profit em passos de preço; definir como 0 para desativar.
BuyPositionOpen Habilita a abertura de posições compradas quando ocorre um sinal altista.
SellPositionOpen Habilita a abertura de posições vendidas quando ocorre um sinal baixista.
BuyPositionClose Fecha quaisquer posições compradas existentes quando um sinal baixista é recebido.
SellPositionClose Fecha quaisquer posições vendidas existentes quando um sinal altista é recebido.
Npips Multiplicador fracional aplicado ao trailing stop para ampliar ou reduzir a distância de reversão.
PriceMode Variante de preço aplicada (fechamento, abertura, máximo, mínimo, mediana, típico, ponderado, média simples, média quádrupla, seguidor de tendência ou Demark).
SignalBar Número de velas completamente fechadas a aguardar antes de reagir a um sinal, correspondendo ao parâmetro MT5.

Filtros

  • Categoria: Reversão com seguidor de tendência
  • Direção: Bidirecional
  • Indicadores: Stopreversal (trailing stop baseado em ATR)
  • Stops: Stop-loss e take-profit estáticos, opcional
  • Período: Configurável (padrão H4)
  • Sazonalidade: Nenhum
  • Redes neurais: Não
  • Divergência: Não
  • Complexidade: Moderado devido à lógica de trailing personalizada
  • Nível de risco: Ajustável através da distância do stop e offset de trailing
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 indicator based trailing stop strategy.
/// </summary>
public class StopreversalTrailingStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stopLossSteps;
	private readonly StrategyParam<int> _takeProfitSteps;
	private readonly StrategyParam<bool> _buyPositionOpen;
	private readonly StrategyParam<bool> _sellPositionOpen;
	private readonly StrategyParam<bool> _buyPositionClose;
	private readonly StrategyParam<bool> _sellPositionClose;
	private readonly StrategyParam<decimal> _npips;
	private readonly StrategyParam<AppliedPriceModes> _priceMode;
	private readonly StrategyParam<int> _signalBar;

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

	private AverageTrueRange _atr = null!;
	private decimal? _previousStopLevel;
	private decimal? _previousPrice;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

	/// <summary>
	/// Initializes a new instance of <see cref="StopreversalTrailingStrategy"/>.
	/// </summary>
	public StopreversalTrailingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Stopreversal timeframe", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR lookback for trailing stop", "Indicator");

		_stopLossSteps = Param(nameof(StopLossSteps), 10)
		.SetNotNegative()
		.SetDisplay("Stop Loss Steps", "Stop loss distance in price steps", "Risk")
		;

		_takeProfitSteps = Param(nameof(TakeProfitSteps), 20)
		.SetNotNegative()
		.SetDisplay("Take Profit Steps", "Take profit distance in price steps", "Risk")
		;

		_buyPositionOpen = Param(nameof(BuyPositionOpen), true)
		.SetDisplay("Open Long", "Allow opening long positions", "Trading");

		_sellPositionOpen = Param(nameof(SellPositionOpen), true)
		.SetDisplay("Open Short", "Allow opening short positions", "Trading");

		_buyPositionClose = Param(nameof(BuyPositionClose), true)
		.SetDisplay("Close Long", "Close long positions on sell signals", "Trading");

		_sellPositionClose = Param(nameof(SellPositionClose), true)
		.SetDisplay("Close Short", "Close short positions on buy signals", "Trading");

		_npips = Param(nameof(Npips), 0.004m)
		.SetGreaterThanZero()
		.SetDisplay("Trailing Offset", "Fractional offset applied to the stop line", "Indicator")
		;

		_priceMode = Param(nameof(PriceMode), AppliedPriceModes.Close)
		.SetDisplay("Applied Price", "Price source used by the trailing stop", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetNotNegative()
		.SetDisplay("Signal Bar", "Bar delay before acting on a signal", "Indicator")
		;
	}

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

	/// <summary>
	/// ATR period used for the trailing stop calculation.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Enable long entries.
	/// </summary>
	public bool BuyPositionOpen
	{
		get => _buyPositionOpen.Value;
		set => _buyPositionOpen.Value = value;
	}

	/// <summary>
	/// Enable short entries.
	/// </summary>
	public bool SellPositionOpen
	{
		get => _sellPositionOpen.Value;
		set => _sellPositionOpen.Value = value;
	}

	/// <summary>
	/// Close long positions on short signals.
	/// </summary>
	public bool BuyPositionClose
	{
		get => _buyPositionClose.Value;
		set => _buyPositionClose.Value = value;
	}

	/// <summary>
	/// Close short positions on long signals.
	/// </summary>
	public bool SellPositionClose
	{
		get => _sellPositionClose.Value;
		set => _sellPositionClose.Value = value;
	}

	/// <summary>
	/// Fractional offset used by the trailing stop calculation.
	/// </summary>
	public decimal Npips
	{
		get => _npips.Value;
		set => _npips.Value = value;
	}

	/// <summary>
	/// Price source used when computing the trailing level.
	/// </summary>
	public AppliedPriceModes PriceMode
	{
		get => _priceMode.Value;
		set => _priceMode.Value = value;
	}

	/// <summary>
	/// Number of bars to delay before reacting to a signal.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_signals.Clear();
		_previousStopLevel = null;
		_previousPrice = null;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

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

		_atr = new AverageTrueRange
		{
			Length = AtrPeriod
		};

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

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

		// no protection
	}

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

		UpdateStops(candle);

		var price = GetAppliedPrice(candle);
		var prevStop = _previousStopLevel ?? price * (1m - Npips);
		var prevPrice = _previousPrice ?? price;
		var hasPrev = _previousStopLevel.HasValue && _previousPrice.HasValue;

		var stop = CalculateStop(price, prevPrice, prevStop);

		var buySignal = hasPrev && price > stop && prevPrice < prevStop && prevStop != 0m;
		var sellSignal = hasPrev && price < stop && prevPrice > prevStop && prevStop != 0m;

		_previousPrice = price;
		_previousStopLevel = stop;

		_signals.Add(new SignalInfo
		{
			BuySignal = buySignal,
			SellSignal = sellSignal,
			ClosePrice = candle.ClosePrice,
			Time = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime
		});

		TrimSignals();

		if (_signals.Count <= SignalBar)
		return;

		var index = _signals.Count - 1 - SignalBar;
		if (index < 0)
		return;

		var signal = _signals[index];
		var allowTrading = _atr.IsFormed;

		ExecuteSignal(signal, allowTrading);
	}

	private void ExecuteSignal(SignalInfo signal, bool allowTrading)
	{
		if (SellPositionClose && signal.BuySignal && Position < 0)
		{
			BuyMarket();
			ResetShortStops();
		}

		if (BuyPositionClose && signal.SellSignal && Position > 0)
		{
			SellMarket();
			ResetLongStops();
		}

		if (!allowTrading || Position != 0)
		return;

		if (BuyPositionOpen && signal.BuySignal)
		{
			if (Volume > 0)
			{
				BuyMarket();
				ResetShortStops();
				SetLongStops(signal.ClosePrice);
			}
		}
		else if (SellPositionOpen && signal.SellSignal)
		{
			if (Volume > 0)
			{
				SellMarket();
				ResetLongStops();
				SetShortStops(signal.ClosePrice);
			}
		}
	}

	private void UpdateStops(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop is decimal longStop && candle.LowPrice <= longStop)
			{
				SellMarket();
				ResetLongStops();
				return;
			}

			if (_longTake is decimal longTake && candle.HighPrice >= longTake)
			{
				SellMarket();
				ResetLongStops();
			}
		}
		else if (Position < 0)
		{
			if (_shortStop is decimal shortStop && candle.HighPrice >= shortStop)
			{
				BuyMarket();
				ResetShortStops();
				return;
			}

			if (_shortTake is decimal shortTake && candle.LowPrice <= shortTake)
			{
				BuyMarket();
				ResetShortStops();
			}
		}
	}

	private void TrimSignals()
	{
		var max = Math.Max(SignalBar + 5, 10);
		while (_signals.Count > max)
		{
			try { _signals.RemoveAt(0); } catch { break; }
		}
	}

	private decimal CalculateStop(decimal price, decimal prevPrice, decimal prevStop)
	{
		var offset = Npips;

		if (price == prevStop)
		return prevStop;

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

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

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

	private void SetLongStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_longStop = StopLossSteps > 0 ? basePrice - step * StopLossSteps : null;
		_longTake = TakeProfitSteps > 0 ? basePrice + step * TakeProfitSteps : null;
	}

	private void SetShortStops(decimal basePrice)
	{
		var step = GetEffectiveStep();

		_shortStop = StopLossSteps > 0 ? basePrice + step * StopLossSteps : null;
		_shortTake = TakeProfitSteps > 0 ? basePrice - step * TakeProfitSteps : null;
	}

	private void ResetLongStops()
	{
		_longStop = null;
		_longTake = null;
	}

	private void ResetShortStops()
	{
		_shortStop = null;
		_shortTake = null;
	}

	private decimal GetEffectiveStep()
	{
		var step = Security?.PriceStep;
		if (step is decimal s && s > 0)
		return s;

		return 0.0001m;
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		return PriceMode switch
		{
			AppliedPriceModes.Close => close,
			AppliedPriceModes.Open => open,
			AppliedPriceModes.High => high,
			AppliedPriceModes.Low => low,
			AppliedPriceModes.Median => (high + low) / 2m,
			AppliedPriceModes.Typical => (close + high + low) / 3m,
			AppliedPriceModes.Weighted => (2m * close + high + low) / 4m,
			AppliedPriceModes.Simple => (open + close) / 2m,
			AppliedPriceModes.Quarter => (open + close + high + low) / 4m,
			AppliedPriceModes.TrendFollow0 => close > open ? high : close < open ? low : close,
			AppliedPriceModes.TrendFollow1 => close > open ? (high + close) / 2m : close < open ? (low + close) / 2m : close,
			AppliedPriceModes.Demark => CalculateDemarkPrice(open, high, low, close),
			_ => close
		};
	}

	private static decimal CalculateDemarkPrice(decimal open, decimal high, decimal low, decimal close)
	{
		var result = high + low + close;

		if (close < open)
		result = (result + low) / 2m;
		else if (close > open)
		result = (result + high) / 2m;
		else
		result = (result + close) / 2m;

		return ((result - low) + (result - high)) / 2m;
	}

	private sealed class SignalInfo
	{
		public bool BuySignal { get; init; }
		public bool SellSignal { get; init; }
		public decimal ClosePrice { get; init; }
		public DateTimeOffset Time { get; init; }
	}

	/// <summary>
	/// Available price calculation modes.
	/// </summary>
	public enum AppliedPriceModes
	{
		/// <summary>
		/// Closing price.
		/// </summary>
		Close,

		/// <summary>
		/// Opening price.
		/// </summary>
		Open,

		/// <summary>
		/// Highest price.
		/// </summary>
		High,

		/// <summary>
		/// Lowest price.
		/// </summary>
		Low,

		/// <summary>
		/// Median price (high + low) / 2.
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (close + high + low) / 3.
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted close price (2 * close + high + low) / 4.
		/// </summary>
		Weighted,

		/// <summary>
		/// Simple average of open and close.
		/// </summary>
		Simple,

		/// <summary>
		/// Average of open, close, high and low.
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend follow price variant 0.
		/// </summary>
		TrendFollow0,

		/// <summary>
		/// Trend follow price variant 1.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Demark price formula.
		/// </summary>
		Demark
	}
}