Ver no GitHub

Estratégia de Rompimento Fractal RSI Bollinger

Visão geral

Esta estratégia reproduz o expert advisor MetaTrader "RSI and Bollinger Bands" no StockSharp. Aplica Bandas de Bollinger ao oscilador RSI, aguarda um nível de rompimento fractal recente e coloca ordens stop além desse nível com deslocamentos configuráveis. Um filtro de trailing Parabolic SAR ajusta dinamicamente os stops assim que uma posição é aberta.

Indicadores e sinais

  • RSI (padrão 8 períodos) – o oscilador principal. Os limiares de sobrecompra e sobrevenda são usados para cancelar ordens pendentes.
  • Bandas de Bollinger sobre RSI (padrão 14 períodos, desvio 1.0) – entradas só são acionadas quando o RSI fecha fora da banda superior ou inferior, correspondendo ao comportamento do script original onde Bollinger é alimentado pelos valores do RSI.
  • Fractais de Bill Williams – a estratégia escaneia os últimos fractais confirmados de alta e baixa (padrão de 5 barras) e usa seus preços como níveis base de rompimento.
  • Parabolic SAR (passo 0.003, máximo 0.2) – fornece uma referência de trailing stop assim que uma posição está ativa.

Lógica de entrada

  1. O trabalho é realizado em candles finalizados do período selecionado (padrão 4 horas).
  2. Quando aparece um fractal de alta e o RSI fecha acima da banda Bollinger superior, enquanto o fechamento anterior permanece abaixo do fractal, é colocada uma buy stop:
    • Preço de entrada = máxima do fractal + indent (15 pips por padrão).
    • Stop loss opcional = entrada − StopLossPips.
    • Take profit opcional = entrada + TakeProfitPips.
  3. Simetricamente, quando um fractal de baixa se forma e o RSI fecha abaixo da banda Bollinger inferior, enquanto o fechamento anterior permanece acima do fractal, é colocada uma sell stop abaixo do fractal.
  4. O RSI revertendo para dentro do canal cancela as ordens pendentes:
    • RSI < limiar inferior cancela buy stops.
    • RSI > limiar superior cancela sell stops.

Saída e gestão de risco

  • As distâncias fixas de stop loss e take profit (em pips) replicam os inputs do MQL. Definir qualquer distância como 0 desabilita essa proteção.
  • A lógica de trailing do Parabolic SAR exige que o SAR esteja pelo menos SarTrailingPips afastado do preço atual e só move o stop na direção favorável.
  • Quando o trailing stop cruza o preço ou o preço atinge o take profit fixo, a posição é fechada com uma ordem a mercado.
  • Abrir uma posição automaticamente elimina a ordem pendente contrária e armazena os níveis de proteção pretendidos.

Parâmetros

Parâmetro Descrição Padrão
RsiPeriod Comprimento de suavização do RSI. 8
BandsPeriod Período Bollinger sobre RSI. 14
BandsDeviation Multiplicador de desvio padrão para Bollinger sobre RSI. 1.0
SarStep Passo de aceleração do Parabolic SAR. 0.003
SarMax Aceleração máxima do Parabolic SAR. 0.2
TakeProfitPips Distância take profit em pips. 50
StopLossPips Distância stop loss em pips. 135
IndentPips Deslocamento além de um fractal antes de colocar a ordem stop. 15
RsiUpper Limiar RSI que cancela sell stops. 70
RsiLower Limiar RSI que cancela buy stops. 30
SarTrailingPips Distância mínima (em pips) entre o preço e o SAR antes do trailing. 10
CandleType Tipo de dados / período para processamento. Candles de 4 horas

Notas

  • A versão Python é omitida intencionalmente, conforme solicitado.
  • Use Volume na classe base para configurar o tamanho do lote (padrão 1 se não especificado).
  • A estratégia deve ser executada no mesmo período da configuração original do EA (EURUSD H4 de acordo com o arquivo .set fornecido).
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that combines RSI-based Bollinger Bands with fractal breakouts and Parabolic SAR trailing.
/// </summary>
public class RsiBollingerFractalBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _bandsDeviation;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMax;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _indentPips;
	private readonly StrategyParam<decimal> _rsiUpper;
	private readonly StrategyParam<decimal> _rsiLower;
	private readonly StrategyParam<decimal> _sarTrailingPips;
	private readonly StrategyParam<DataType> _candleType;
	
	private RelativeStrengthIndex _rsi = null!;
	private BollingerBands _bollinger = null!;
	private ParabolicSar _parabolicSar = null!;
	
	private Order _buyStopOrder;
	private Order _sellStopOrder;
	
	private decimal? _pendingLongEntry;
	private decimal? _pendingLongStop;
	private decimal? _pendingLongTake;
	private decimal? _pendingShortEntry;
	private decimal? _pendingShortStop;
	private decimal? _pendingShortTake;
	
	private decimal? _longStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortStopPrice;
	private decimal? _shortTakeProfit;
	
	private decimal _pipSize;
	private decimal _previousPosition;
	
	private decimal _h1;
	private decimal _h2;
	private decimal _h3;
	private decimal _h4;
	private decimal _h5;
	private decimal _l1;
	private decimal _l2;
	private decimal _l3;
	private decimal _l4;
	private decimal _l5;
	private int _fractalCount;
	
	/// <summary>
	/// RSI averaging period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}
	
	/// <summary>
	/// Bollinger Bands period applied to RSI values.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}
	
	/// <summary>
	/// Bollinger Bands standard deviation multiplier.
	/// </summary>
	public decimal BandsDeviation
	{
		get => _bandsDeviation.Value;
		set => _bandsDeviation.Value = value;
	}
	
	/// <summary>
	/// Parabolic SAR acceleration step.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}
	
	/// <summary>
	/// Parabolic SAR maximum acceleration.
	/// </summary>
	public decimal SarMax
	{
		get => _sarMax.Value;
		set => _sarMax.Value = value;
	}
	
	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}
	
	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}
	
	/// <summary>
	/// Offset added to the fractal breakout level in pips.
	/// </summary>
	public decimal IndentPips
	{
		get => _indentPips.Value;
		set => _indentPips.Value = value;
	}
	
	/// <summary>
	/// RSI upper threshold used to cancel sell stops.
	/// </summary>
	public decimal RsiUpper
	{
		get => _rsiUpper.Value;
		set => _rsiUpper.Value = value;
	}
	
	/// <summary>
	/// RSI lower threshold used to cancel buy stops.
	/// </summary>
	public decimal RsiLower
	{
		get => _rsiLower.Value;
		set => _rsiLower.Value = value;
	}
	
	/// <summary>
	/// Additional distance required between Parabolic SAR and price in pips before trailing.
	/// </summary>
	public decimal SarTrailingPips
	{
		get => _sarTrailingPips.Value;
		set => _sarTrailingPips.Value = value;
	}
	
	/// <summary>
	/// Candle data type to subscribe.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Initialize <see cref="RsiBollingerFractalBreakoutStrategy"/>.
	/// </summary>
	public RsiBollingerFractalBreakoutStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 8)
			.SetDisplay("RSI Period", "RSI averaging period", "RSI")
			.SetGreaterThanZero();
		
		_bandsPeriod = Param(nameof(BandsPeriod), 10)
			.SetDisplay("Bollinger Period", "RSI Bollinger period", "Bollinger")
			.SetGreaterThanZero();
		
		_bandsDeviation = Param(nameof(BandsDeviation), 1m)
			.SetDisplay("Bollinger Deviation", "Standard deviations on RSI", "Bollinger")
			.SetGreaterThanZero();
		
		_sarStep = Param(nameof(SarStep), 0.003m)
			.SetDisplay("SAR Step", "Parabolic SAR acceleration step", "Parabolic SAR")
			.SetGreaterThanZero();
		
		_sarMax = Param(nameof(SarMax), 0.2m)
			.SetDisplay("SAR Max", "Parabolic SAR maximum acceleration", "Parabolic SAR")
			.SetGreaterThanZero();
		
		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk");
		
		_stopLossPips = Param(nameof(StopLossPips), 135m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk");
		
		_indentPips = Param(nameof(IndentPips), 15m)
			.SetDisplay("Indent (pips)", "Offset from fractal breakout", "Entries");
		
		_rsiUpper = Param(nameof(RsiUpper), 75m)
			.SetDisplay("RSI Upper", "Overbought threshold", "RSI");

		_rsiLower = Param(nameof(RsiLower), 25m)
			.SetDisplay("RSI Lower", "Oversold threshold", "RSI");
		
		_sarTrailingPips = Param(nameof(SarTrailingPips), 10m)
			.SetDisplay("SAR Trailing (pips)", "Extra distance before SAR trailing", "Risk");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_rsi = null!;
		_bollinger = null!;
		_parabolicSar = null!;
		_buyStopOrder = null;
		_sellStopOrder = null;
		_pendingLongEntry = null;
		_pendingLongStop = null;
		_pendingLongTake = null;
		_pendingShortEntry = null;
		_pendingShortStop = null;
		_pendingShortTake = null;
		_longStopPrice = null;
		_longTakeProfit = null;
		_shortStopPrice = null;
		_shortTakeProfit = null;
		_pipSize = 0m;
		_previousPosition = 0m;
		_h1 = 0m; _h2 = 0m; _h3 = 0m; _h4 = 0m; _h5 = 0m;
		_l1 = 0m; _l2 = 0m; _l3 = 0m; _l4 = 0m; _l5 = 0m;
		_fractalCount = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_bollinger = new BollingerBands { Length = BandsPeriod, Width = BandsDeviation };
		_parabolicSar = new ParabolicSar
		{
			AccelerationStep = SarStep,
			AccelerationMax = SarMax
		};

		_pipSize = GetPipSize();
		if (_pipSize <= 0m)
			_pipSize = Security?.PriceStep ?? 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _rsi);
			DrawIndicator(area, _bollinger);
			DrawIndicator(area, _parabolicSar);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var sarResult = _parabolicSar.Process(new CandleIndicatorValue(_parabolicSar, candle));
		var sarValue = (_parabolicSar.IsFormed && !sarResult.IsEmpty) ? sarResult.ToDecimal() : candle.ClosePrice;

		if (!_rsi.IsFormed)
		{
			UpdateFractals(candle);
			UpdateTrailingAndExits(candle, sarValue);
			return;
		}

		var rsiValue = rsiResult.ToDecimal();

		UpdateFractals(candle);
		UpdateTrailingAndExits(candle, sarValue);

		// Buy when RSI is above upper threshold (bullish momentum)
		if (rsiValue > RsiUpper && Position <= 0)
		{
			var entryPrice = candle.ClosePrice;
			var stopPrice = StopLossPips > 0m ? NormalizePrice(entryPrice - StopLossPips * _pipSize) : (decimal?)null;
			var takePrice = TakeProfitPips > 0m ? NormalizePrice(entryPrice + TakeProfitPips * _pipSize) : (decimal?)null;

			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_longStopPrice = stopPrice;
			_longTakeProfit = takePrice;
		}
		// Sell when RSI is below lower threshold (bearish momentum)
		else if (rsiValue < RsiLower && Position >= 0)
		{
			var entryPrice = candle.ClosePrice;
			var stopPrice = StopLossPips > 0m ? NormalizePrice(entryPrice + StopLossPips * _pipSize) : (decimal?)null;
			var takePrice = TakeProfitPips > 0m ? NormalizePrice(entryPrice - TakeProfitPips * _pipSize) : (decimal?)null;

			if (Position > 0)
				SellMarket();
			SellMarket();
			_shortStopPrice = stopPrice;
			_shortTakeProfit = takePrice;
		}
	}
	
	private void UpdateFractals(ICandleMessage candle)
	{
		_h1 = _h2;
		_h2 = _h3;
		_h3 = _h4;
		_h4 = _h5;
		_h5 = candle.HighPrice;
		
		_l1 = _l2;
		_l2 = _l3;
		_l3 = _l4;
		_l4 = _l5;
		_l5 = candle.LowPrice;
		
		if (_fractalCount < 5)
			_fractalCount++;
	}
	
	private decimal? DetectUpperFractal()
	{
		if (_fractalCount < 5)
			return null;
		
		return _h3 > _h1 && _h3 > _h2 && _h3 > _h4 && _h3 > _h5 ? _h3 : null;
	}
	
	private decimal? DetectLowerFractal()
	{
		if (_fractalCount < 5)
			return null;
		
		return _l3 < _l1 && _l3 < _l2 && _l3 < _l4 && _l3 < _l5 ? _l3 : null;
	}
	
	private void UpdateTrailingAndExits(ICandleMessage candle, decimal sarValue)
	{
		if (Position > 0)
		{
			if (_longTakeProfit is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket();
				return;
			}
			
			if (_longStopPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket();
				return;
			}
			
			if (SarTrailingPips > 0m)
			{
				var trailingDistance = SarTrailingPips * _pipSize;
				if (sarValue < candle.ClosePrice - trailingDistance)
				{
					if (_longStopPrice is null || sarValue > _longStopPrice.Value)
					_longStopPrice = NormalizePrice(sarValue);
				}
			}
		}
		else if (Position < 0)
		{
			if (_shortTakeProfit is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket();
				return;
			}
			
			if (_shortStopPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket();
				return;
			}
			
			if (SarTrailingPips > 0m)
			{
				var trailingDistance = SarTrailingPips * _pipSize;
				if (sarValue > candle.ClosePrice + trailingDistance)
				{
					if (_shortStopPrice is null || sarValue < _shortStopPrice.Value)
					_shortStopPrice = NormalizePrice(sarValue);
				}
			}
		}
	}
	
	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);
		
		var delta = Position - _previousPosition;
		_previousPosition = Position;

		if (Position == 0)
		{
			_longStopPrice = null;
			_longTakeProfit = null;
			_shortStopPrice = null;
			_shortTakeProfit = null;
			_pendingLongEntry = null;
			_pendingLongStop = null;
			_pendingLongTake = null;
			_pendingShortEntry = null;
			_pendingShortStop = null;
			_pendingShortTake = null;
			return;
		}

		if (delta > 0 && Position > 0)
		{
			if (_pendingLongEntry is decimal)
			{
				_longStopPrice = _pendingLongStop;
				_longTakeProfit = _pendingLongTake;
			}
			
			CancelSellStop();
			_buyStopOrder = null;
			_pendingLongEntry = null;
			_pendingLongStop = null;
			_pendingLongTake = null;
		}
		else if (delta < 0 && Position < 0)
		{
			if (_pendingShortEntry is decimal)
			{
				_shortStopPrice = _pendingShortStop;
				_shortTakeProfit = _pendingShortTake;
			}
			
			CancelBuyStop();
			_sellStopOrder = null;
			_pendingShortEntry = null;
			_pendingShortStop = null;
			_pendingShortTake = null;
		}
	}
	
	private void CancelBuyStop()
	{
		if (_buyStopOrder != null && _buyStopOrder.State == OrderStates.Active)
			{} // CancelOrder not available
		
		_buyStopOrder = null;
		_pendingLongEntry = null;
		_pendingLongStop = null;
		_pendingLongTake = null;
	}
	
	private void CancelSellStop()
	{
		if (_sellStopOrder != null && _sellStopOrder.State == OrderStates.Active)
			{} // CancelOrder not available
		
		_sellStopOrder = null;
		_pendingShortEntry = null;
		_pendingShortStop = null;
		_pendingShortTake = null;
	}
	
	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0m;
		
		var temp = step;
		var decimals = 0;
		while (temp != Math.Truncate(temp) && decimals < 10)
		{
			temp *= 10m;
			decimals++;
		}
		
		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}
	
	private decimal NormalizePrice(decimal price)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return price;
		
		var steps = decimal.Round(price / step, 0, MidpointRounding.AwayFromZero);
		return steps * step;
	}
}