Открыть на GitHub

Стратегия Rollback Rebound

Обзор

Стратегия Rollback Rebound — это C#-адаптация советника MQL5 «TST (barabashkakvn's edition)». Она отслеживает один инструмент на таймфрейме, заданном параметром CandleType, и ищет свечи, которые сначала расширяются, а затем откатываются внутрь диапазона. Когда бычья свеча откатывается от максимума больше заданного порога, стратегия покупает; аналогичный откат от минимума формирует сигнал на продажу. Реализация использует высокоуровневый интерфейс StockSharp для подписки на свечи, а все защитные уровни задаются в пунктах и переводятся в абсолютные цены.

Пункты рассчитываются от шага цены инструмента. Для инструментов с тремя или пятью знаками после запятой шаг автоматически умножается на десять, чтобы повторить метатрейдеровскую трактовку pip. Объём сделок берётся из свойства Volume стратегии.

Логика входа

  • Сигналы вычисляются только по закрытым свечам выбранного CandleType.
  • При ReverseSignal = false (значение по умолчанию):
    • Покупка: свеча закрывается ниже открытия, а разница между максимумом и закрытием превышает RollbackRatePips (в ценовом выражении). Это означает, что цена выросла и затем откатилась достаточно глубоко для контртрендового входа.
    • Продажа: свеча закрывается выше открытия, а разница между закрытием и минимумом превышает RollbackRatePips, что является зеркальным условием.
  • Если ReverseSignal = true, условия покупок и продаж меняются местами.
  • Новая позиция открывается только при отсутствии позиции или наличии встречной. Объём ордера равен Volume + |Position|, что позволяет одновременно закрыть противоположную позицию и открыть новую.

Логика выхода

  • При открытии сделки сохраняются уровни стоп-лосса и тейк-профита, вычисленные из параметров в пунктах. Если свеча достигает одного из уровней, позиция закрывается рыночным ордером.
  • Значения StopLossPips = 0 или TakeProfitPips = 0 отключают соответствующий защитный уровень.
  • Трейлинг включается, когда плавающая прибыль превышает TrailingStopPips + TrailingStepPips.
    • Для длинных позиций стоп подтягивается к максимум - TrailingStopPips, если новое значение минимум на TrailingStepPips выше предыдущего.
    • Для коротких позиций стоп подтягивается к минимум + TrailingStopPips, если новое значение минимум на TrailingStepPips ниже предыдущего.
    • Если цена возвращается за текущий трейлинг, позиция закрывается немедленно.
  • После закрытия всех позиций внутреннее состояние очищается, чтобы исключить повторное использование старых данных.

Параметры

Параметр Описание Значение по умолчанию
CandleType Свечная серия, на которой рассчитываются сигналы. 15-минутные свечи
StopLossPips Стоп-лосс в пунктах (0 — отключить). 30
TakeProfitPips Тейк-профит в пунктах (0 — отключить). 90
TrailingStopPips Базовая дистанция трейлинга в пунктах (0 — отключить). 1
TrailingStepPips Дополнительная прибыль в пунктах для очередного смещения трейлинга; при включённом трейлинге должно быть > 0. 15
RollbackRatePips Минимальный откат от экстремума бара для подтверждения сигнала. 15
ReverseSignal Инвертирует направление входов. false

Рекомендации по использованию

  • Перед запуском задайте свойство Volume, оно определяет объём ордеров.
  • При активном трейлинге необходимо, чтобы TrailingStopPips > 0 и TrailingStepPips > 0, иначе стратегия завершит запуск с ошибкой.
  • Оригинальный советник анализировал тики внутри формирующейся свечи; порт использует закрытые свечи (максимум, минимум, закрытие), что приближает исходную логику и позволяет работать с высокоуровневым API StockSharp.
  • Стратегия рассчитана на один инструмент. Для торговли несколькими инструментами запустите отдельные экземпляры стратегии.
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;
	}
}