Ver no GitHub

Estratégia de Retrocesso e Rebote

Visão geral

A Estratégia de Retrocesso e Rebote é uma conversão C# do assessor especialista MQL5 "TST (barabashkakvn's edition)". Ela monitora um único instrumento no período especificado pelo parâmetro CandleType e busca movimentos fortes que retrocedam de volta para dentro do intervalo da barra. Quando uma barra de alta recua a partir de sua máxima por mais do que o limiar de retrocesso, a estratégia compra, enquanto um retrocesso baixista equivalente desencadeia uma venda. A implementação usa a API de subscrição de velas de alto nível do StockSharp e gerencia todas as ordens protetoras em unidades de pip que são convertidas em deslocamentos de preço absolutos.

As distâncias em pip são calculadas a partir do PriceStep do instrumento. Para símbolos que cotam com três ou cinco decimais, a estratégia automaticamente multiplica o passo por dez para corresponder à definição de pip do MetaTrader. Todo o dimensionamento de posição é retirado da propriedade base Volume da estratégia.

Lógica de entrada

  • Processar apenas velas terminadas da série CandleType configurada.
  • Com ReverseSignal = false (padrão):
    • Configuração comprada: a vela fecha abaixo de sua abertura e a diferença entre a máxima da vela e o fechamento excede RollbackRatePips (convertido para preço). Isso indica que o preço se expandiu para cima e então retrocedeu profundamente o suficiente para qualificar para uma entrada contrária comprada.
    • Configuração vendida: a vela fecha acima de sua abertura e a diferença entre o fechamento e a mínima da vela excede RollbackRatePips. Isso reflete a lógica comprada no lado baixista.
  • Quando ReverseSignal = true, os papéis das condições comprada e vendida são trocados, permitindo ao trader mudar a direção sem alterar os outros parâmetros.
  • Novas entradas só são colocadas quando a posição atual está plana ou na direção oposta. O volume executado é igual a Volume + |Position| para que uma posição oposta seja fechada antes de estabelecer o novo trade.

Lógica de saída

  • Na entrada, a estratégia armazena os níveis de stop-loss e take-profit com base nos deslocamentos de pip configurados. Quando o intervalo da vela toca um nível, a posição é fechada com uma ordem de mercado.
  • StopLossPips = 0 ou TakeProfitPips = 0 desabilita o nível de proteção correspondente.
  • A lógica de trailing é ativada uma vez que o lucro flutuante excede TrailingStopPips + TrailingStepPips (em termos de preço).
    • Para trades comprados, o stop se desloca para preço mais alto - TrailingStopPips sempre que o novo nível estiver pelo menos TrailingStepPips acima do stop anterior.
    • Para trades vendidos, o stop se desloca para preço mais baixo + TrailingStopPips quando o novo nível estiver pelo menos TrailingStepPips abaixo do stop anterior.
    • Se o mercado se reverter e cruzar o trailing stop, a posição é encerrada imediatamente.
  • Quando nenhuma posição está aberta, todas as variáveis de estado internas são limpas para evitar dados obsoletos.

Parâmetros

Parâmetro Descrição Padrão
CandleType Série de velas usada para cálculo de sinais. Período de 15 minutos
StopLossPips Distância do stop de proteção em pips. Definir como zero para desabilitar. 30
TakeProfitPips Distância do take-profit em pips. Definir como zero para desabilitar. 90
TrailingStopPips Offset do trailing stop em pips. Definir como zero para desabilitar o trailing. 1
TrailingStepPips Lucro extra (em pips) necessário antes que o trailing stop possa se mover novamente. Deve ser positivo quando o trailing está habilitado. 15
RollbackRatePips Recuo mínimo do extremo da barra que valida um sinal. 15
ReverseSignal Inverte a direção de entrada (sinais comprados tornam-se vendidos e vice-versa). false

Notas de uso

  • Definir a propriedade Volume antes de iniciar a estratégia; ela define a quantidade negociada para cada ordem.
  • O trailing requer TrailingStopPips > 0 e TrailingStepPips > 0. A estratégia lança um erro na inicialização se essa relação for violada.
  • Como o especialista original avaliava ticks dentro da barra ativa, o porto C# usa a máxima/mínima/fechamento da vela terminada para aproximar o mesmo comportamento. A diferença é insignificante para a maioria dos backtests e mantém a implementação alinhada com a API de alto nível do StockSharp.
  • A estratégia funciona com um único instrumento. Para operar múltiplos instrumentos, criar instâncias de estratégia separadas.
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>
/// Rollback Rebound strategy that follows the TST (barabashkakvn's edition) expert advisor logic.
/// The strategy buys after a bullish bar pulls back from its high and sells after a bearish bar rebounds from its low.
/// Protective orders are managed in pips and include optional trailing logic with a rollback filter.
/// </summary>
public class RollbackReboundStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _rollbackRatePips;
	private readonly StrategyParam<bool> _reverseSignal;

	private decimal _pipSize;
	private decimal _stopLossOffset;
	private decimal _takeProfitOffset;
	private decimal _trailingStopOffset;
	private decimal _trailingStepOffset;
	private decimal _rollbackOffset;

	private decimal _longEntryPrice;
	private decimal _longStopPrice;
	private decimal _longTakeProfitPrice;

	private decimal _shortEntryPrice;
	private decimal _shortStopPrice;
	private decimal _shortTakeProfitPrice;

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

	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Additional profit in pips required before the trailing stop moves.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Pullback threshold in pips to validate signals.
	/// </summary>
	public decimal RollbackRatePips
	{
		get => _rollbackRatePips.Value;
		set => _rollbackRatePips.Value = value;
	}

	/// <summary>
	/// Inverts entry direction.
	/// </summary>
	public bool ReverseSignal
	{
		get => _reverseSignal.Value;
		set => _reverseSignal.Value = value;
	}

	/// <summary>
	/// Initialize parameters with defaults derived from the original MQL expert.
	/// </summary>
	public RollbackReboundStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Candle series used for calculations.", "General");

		_stopLossPips = Param(nameof(StopLossPips), 30m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Distance of the protective stop in pips.", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 90m)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Distance of the take profit in pips.", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (pips)", "Trailing stop offset in pips.", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Step (pips)", "Additional profit required before trailing adjusts.", "Risk");

		_rollbackRatePips = Param(nameof(RollbackRatePips), 40m)
			.SetNotNegative()
			.SetDisplay("Rollback Threshold (pips)", "Minimum pullback from the bar extreme to trigger entries.", "Signal");

		_reverseSignal = Param(nameof(ReverseSignal), false)
			.SetDisplay("Reverse Signal", "Invert entry logic (buy becomes sell).", "Signal");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_pipSize = 0m;
		_stopLossOffset = 0m;
		_takeProfitOffset = 0m;
		_trailingStopOffset = 0m;
		_trailingStepOffset = 0m;
		_rollbackOffset = 0m;

		ResetLongState();
		ResetShortState();
	}

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

		// Validate that trailing configuration matches the behaviour of the original expert.
		if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
			throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");

		// Convert pip-based parameters into absolute price offsets.
		_pipSize = Security?.PriceStep ?? 1m;
		if (Security != null && (Security.Decimals == 3 || Security.Decimals == 5))
			_pipSize *= 10m;

		_stopLossOffset = StopLossPips * _pipSize;
		_takeProfitOffset = TakeProfitPips * _pipSize;
		_trailingStopOffset = TrailingStopPips * _pipSize;
		_trailingStepOffset = TrailingStepPips * _pipSize;
		_rollbackOffset = RollbackRatePips * _pipSize;

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

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

	private void ProcessCandle(ICandleMessage candle)
	{
		// Work only with finished candles to emulate the IsNewBar check from the MQL expert.
		if (candle.State != CandleStates.Finished)
			return;

		// Update trailing stops and exit conditions before generating new signals.
		ManageOpenPosition(candle);

		// Skip signal generation until the strategy is online and allowed to trade.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Translate the rollback filters from the original EA using candle statistics.
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		var longCondition = open > close && high - close > _rollbackOffset;
		var shortCondition = close > open && close - low > _rollbackOffset;

		if (ReverseSignal)
		{
			(longCondition, shortCondition) = (shortCondition, longCondition);
		}

		if (longCondition && Position <= 0)
		{
			// Enter long when the rollback condition is met and the strategy is not already in a long position.
			var volume = Volume + Math.Abs(Position);
			if (volume <= 0m)
				return;

			BuyMarket(volume);
			InitializeLongState(candle);
		}
		else if (shortCondition && Position >= 0)
		{
			// Enter short when the bearish rollback occurs and we are not currently short.
			var volume = Volume + Math.Abs(Position);
			if (volume <= 0m)
				return;

			SellMarket(volume);
			InitializeShortState(candle);
		}
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		// Mirror the trailing and exit logic from the MetaTrader expert to keep behaviour identical.
		if (Position > 0)
		{
			// Use the candle high as the most optimistic price for long positions.
			var extreme = candle.HighPrice;

			if (_longEntryPrice == 0m)
				// Store the actual entry price once the trade is filled.
				_longEntryPrice = candle.ClosePrice;

			if (_trailingStopOffset > 0m)
			{
				// Apply the trailing algorithm for the active position.
				// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
				if (extreme - _longEntryPrice > _trailingStopOffset + _trailingStepOffset)
				{
					var threshold = extreme - (_trailingStopOffset + _trailingStepOffset);
					if (_longStopPrice == 0m || _longStopPrice < threshold)
						_longStopPrice = extreme - _trailingStopOffset;
				}
			}

			if (_longTakeProfitPrice > 0m && candle.HighPrice >= _longTakeProfitPrice)
			{
				// Exit the long position once the take-profit level is touched.
				SellMarket(Math.Abs(Position));
				ResetLongState();
				return;
			}

			if (_longStopPrice > 0m && candle.LowPrice <= _longStopPrice)
			{
				// Close the long position if the initial or trailing stop is triggered.
				SellMarket(Math.Abs(Position));
				ResetLongState();
				return;
			}
		}
		else if (Position < 0)
		{
			// Use the candle low as the best price in favour of the short position.
			var extreme = candle.LowPrice;

			if (_shortEntryPrice == 0m)
				// Capture the short entry price after execution.
				_shortEntryPrice = candle.ClosePrice;

			if (_trailingStopOffset > 0m)
			{
				// Apply the trailing algorithm for short positions.
				// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
				if (_shortEntryPrice - extreme > _trailingStopOffset + _trailingStepOffset)
				{
					var threshold = extreme + (_trailingStopOffset + _trailingStepOffset);
					if (_shortStopPrice == 0m || _shortStopPrice > threshold)
						_shortStopPrice = extreme + _trailingStopOffset;
				}
			}

			if (_shortTakeProfitPrice > 0m && candle.LowPrice <= _shortTakeProfitPrice)
			{
				// Exit the short position when the take-profit is hit.
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				return;
			}

			if (_shortStopPrice > 0m && candle.HighPrice >= _shortStopPrice)
			{
				// Cover the short position if the stop level is breached.
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				return;
			}
		}
		else
		{
			// Clear cached levels when no position is open.
			ResetLongState();
			ResetShortState();
		}
	}

	private void InitializeLongState(ICandleMessage candle)
	{
		// Clear short-side state because the strategy operates in netting mode.
		ResetShortState();

		var entry = candle.ClosePrice;
		// Save reference prices for managing the long position.
		_longEntryPrice = entry;
		_longStopPrice = StopLossPips > 0m ? entry - _stopLossOffset : 0m;
		_longTakeProfitPrice = TakeProfitPips > 0m ? entry + _takeProfitOffset : 0m;
	}

	private void InitializeShortState(ICandleMessage candle)
	{
		// Clear long-side state before opening a short position.
		ResetLongState();

		var entry = candle.ClosePrice;
		// Store price references for the short position.
		_shortEntryPrice = entry;
		_shortStopPrice = StopLossPips > 0m ? entry + _stopLossOffset : 0m;
		_shortTakeProfitPrice = TakeProfitPips > 0m ? entry - _takeProfitOffset : 0m;
	}

	private void ResetLongState()
	{
		_longEntryPrice = 0m;
		_longStopPrice = 0m;
		_longTakeProfitPrice = 0m;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = 0m;
		_shortStopPrice = 0m;
		_shortTakeProfitPrice = 0m;
	}
}