Ver en GitHub

Estrategia IBS RSI CCI v4

Descripción general

La Estrategia IBS RSI CCI v4 es un sistema de trading contrario que combina tres osciladores de momentum:

  • Internal Bar Strength (IBS) – mide la posición relativa del cierre dentro del rango máximo-mínimo de la barra y se suaviza con una media móvil configurable.
  • Relative Strength Index (RSI) – captura el momentum del mercado alrededor del nivel neutro de 50.
  • Commodity Channel Index (CCI) – evalúa la desviación del precio de una línea base de media móvil.

Los tres componentes se escalan y mezclan en un oscilador compuesto. La señal compuesta está restringida por un umbral de paso configurable y filtrada a través de un envolvente de máximos/mínimos de estilo Donchian. Los cruces entre la señal compuesta y su línea media generan oportunidades de reversión.

Lógica de trading

  1. Suscribirse a velas con el marco temporal seleccionado (predeterminado: 4 horas).
  2. Calcular el valor IBS para cada vela terminada y suavizarlo con el tipo de media móvil elegido.
  3. Obtener valores RSI y CCI usando sus respectivas longitudes de lookback.
  4. Construir el oscilador compuesto usando la ponderación original del script de MetaTrader:
    • Contribución IBS × 700
    • Desviación RSI de 50 × 9
    • Valor CCI bruto × 1
  5. Aplicar un umbral de paso para evitar saltos repentinos en la señal compuesta.
  6. Rastrear el máximo y mínimo rodantes de la señal compuesta y suavizar ambos bordes para formar una banda dinámica. La línea media de la banda se usa como "línea base" (equivalente al segundo buffer de indicador en la versión MQL).
  7. Gestión de posiciones
    • Cerrar posiciones largas cuando la señal compuesta está por debajo de la línea base en la barra confirmada.
    • Cerrar posiciones cortas cuando la señal compuesta está por encima de la línea base en la barra confirmada.
    • Abrir posiciones largas cuando la barra confirmada previamente estaba por encima de la línea base y la señal más reciente cruza hacia abajo a través de la línea base (entrada contraria).
    • Abrir posiciones cortas cuando la barra confirmada previamente estaba por debajo de la línea base y la señal más reciente cruza hacia arriba a través de la línea base.

Parámetros

Parámetro Descripción
CandleType Serie de velas utilizada para cálculos de indicadores.
IbsPeriod Longitud de lookback utilizada para suavizar el componente IBS.
IbsAverageType Tipo de media móvil para suavizado IBS (Simple, Exponencial, Suavizado, Ponderado Lineal).
RsiPeriod Longitud de lookback RSI.
CciPeriod Longitud de lookback CCI.
RangePeriod Tamaño de ventana para la banda rodante de máximos/mínimos aplicada a la señal compuesta.
SmoothPeriod Longitud de la media móvil utilizada para suavizar los bordes de la banda de máximos/mínimos.
RangeAverageType Tipo de media móvil para el suavizado de la banda (Simple, Exponencial, Suavizado, Ponderado Lineal).
StepThreshold Ajuste máximo aplicado cuando la señal compuesta salta bruscamente entre barras.
SignalBar Número de velas ya cerradas utilizadas para confirmación (predeterminado 1 replica el comportamiento original).
EnableLongOpen Permitir abrir nuevas posiciones largas.
EnableShortOpen Permitir abrir nuevas posiciones cortas.
EnableLongClose Permitir cerrar posiciones largas existentes.
EnableShortClose Permitir cerrar posiciones cortas existentes.
OrderVolume Volumen base de la orden de mercado enviada en las entradas.

Notas de implementación

  • La restricción de paso replica la lógica de limitación del buffer del indicador MQL. Un StepThreshold más alto permite saltos más grandes en el oscilador compuesto.
  • Solo se admiten las cuatro familias de medias móviles más comunes para el suavizado IBS y del envolvente, porque la biblioteca estándar de StockSharp no incluye los filtros personalizados del archivo de recursos de MetaTrader.
  • La estrategia usa SignalBar para retrasar las señales en una vela completamente cerrada, coincidiendo con el comportamiento del asesor experto original.
  • Por defecto la estrategia es completamente contraria: las señales se generan en contra de la dirección del cruce más reciente. Alterne los booleanos de entrada/salida para limitar la estrategia a una sola dirección si se desea.

Uso

  1. Configure el CandleType para que coincida con el marco temporal de su instrumento objetivo.
  2. Ajuste las longitudes de los indicadores y el umbral de paso para adaptarse a la volatilidad del instrumento.
  3. Habilite o deshabilite las entradas y salidas largas/cortas según su preferencia de trading.
  4. Configure el parámetro OrderVolume para controlar el tamaño de la orden e inicie la estrategia. StartProtection() está habilitado por defecto y puede personalizarse si se requieren reglas de riesgo adicionales.
  5. Revise el panel de gráfico (si está disponible) para monitorear los precios de las velas, el oscilador compuesto y las operaciones registradas.

Diferencias con la versión de MetaTrader

  • Los parámetros de gestión de dinero y desviación de órdenes del EA original se reemplazan con el parámetro OrderVolume de StockSharp y órdenes de mercado de alto nivel.
  • La conversión de StockSharp mantiene las ponderaciones originales del indicador y la lógica de reversión, pero se centra en los filtros de media móvil más utilizados.
  • Los stops protectores no están preconfigurados; combine la estrategia con los módulos de riesgo de StockSharp si se requieren stops fijos o take-profits.
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>
/// Conversion of the Exp_IBS_RSI_CCI_v4 MetaTrader strategy to StockSharp.
/// Combines internal bar strength, RSI, and CCI into a smoothed oscillator for contrarian entries.
/// </summary>
public class IbsRsiCciV4Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _ibsPeriod;
	private readonly StrategyParam<MovingAverageKinds> _ibsAverageType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _rangePeriod;
	private readonly StrategyParam<int> _smoothPeriod;
	private readonly StrategyParam<MovingAverageKinds> _rangeAverageType;
	private readonly StrategyParam<decimal> _stepThreshold;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<bool> _enableLongOpen;
	private readonly StrategyParam<bool> _enableShortOpen;
	private readonly StrategyParam<bool> _enableLongClose;
	private readonly StrategyParam<bool> _enableShortClose;
	private readonly StrategyParam<decimal> _volume;
	private readonly StrategyParam<decimal> _cciWeight;

	private RelativeStrengthIndex _rsi = null!;
	private CommodityChannelIndex _cci = null!;
	private IIndicator _ibsAverage = null!;

	private bool _hasSignal;
	private decimal _lastSignal;
	private readonly List<decimal> _signalHistory = [];
	private readonly List<decimal> _baselineHistory = [];

	private readonly StrategyParam<decimal> _ibsWeight;
	private readonly StrategyParam<decimal> _rsiWeight;

	/// <summary>
	/// Initializes a new instance of the <see cref="IbsRsiCciV4Strategy"/> class.
	/// </summary>
	public IbsRsiCciV4Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for calculations", "General")
		;

		_ibsPeriod = Param(nameof(IbsPeriod), 5)
		.SetDisplay("IBS Period", "Smoothing period for the internal bar strength component", "Indicator")
		;

		_ibsAverageType = Param(nameof(IbsAverageType), MovingAverageKinds.Simple)
		.SetDisplay("IBS MA Type", "Moving average type applied to the IBS series", "Indicator")
		;

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetDisplay("RSI Period", "Lookback period for the RSI filter", "Indicator")
		;

		_cciPeriod = Param(nameof(CciPeriod), 14)
		.SetDisplay("CCI Period", "Lookback period for the CCI filter", "Indicator")
		;

		_rangePeriod = Param(nameof(RangePeriod), 25)
		.SetDisplay("Range Period", "Window size for highest/lowest range calculation", "Indicator")
		;

		_smoothPeriod = Param(nameof(SmoothPeriod), 3)
		.SetDisplay("Range Smooth", "Smoothing period for the range bands", "Indicator")
		;

		_rangeAverageType = Param(nameof(RangeAverageType), MovingAverageKinds.Simple)
		.SetDisplay("Range MA Type", "Moving average type applied to the range envelopes", "Indicator")
		;

		_stepThreshold = Param(nameof(StepThreshold), 50m)
		.SetDisplay("Step Threshold", "Maximum adjustment applied when the composite signal jumps", "Trading")
		;
		_cciWeight = Param(nameof(CciWeight), 1m)
		.SetDisplay("CCI Weight", "Weight applied to the CCI component within the composite signal", "Indicator")
		;

		_ibsWeight = Param(nameof(IbsWeight), 700m)
		.SetDisplay("IBS Weight", "Weight applied to IBS component", "Trading");

		_rsiWeight = Param(nameof(RsiWeight), 9m)
		.SetDisplay("RSI Weight", "Weight applied to RSI component", "Trading");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetDisplay("Signal Bar", "Number of closed candles used for confirmation", "Trading")
		;

		_enableLongOpen = Param(nameof(EnableLongOpen), true)
		.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");

		_enableShortOpen = Param(nameof(EnableShortOpen), true)
		.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");

		_enableLongClose = Param(nameof(EnableLongClose), true)
		.SetDisplay("Enable Long Exits", "Allow closing existing long positions", "Trading");

		_enableShortClose = Param(nameof(EnableShortClose), true)
		.SetDisplay("Enable Short Exits", "Allow closing existing short positions", "Trading");

		_volume = Param(nameof(OrderVolume), 1m)
		.SetDisplay("Order Volume", "Base volume used for market orders", "Trading")
		;
	}

	/// <summary>
	/// Candle type used to feed the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Period for smoothing the IBS component.
	/// </summary>
	public int IbsPeriod
	{
		get => _ibsPeriod.Value;
		set => _ibsPeriod.Value = value;
	}

	/// <summary>
	/// Moving average type applied to the IBS series.
	/// </summary>
	public MovingAverageKinds IbsAverageType
	{
		get => _ibsAverageType.Value;
		set => _ibsAverageType.Value = value;
	}

	/// <summary>
	/// RSI lookback period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// CCI lookback period.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Window used to search for highs and lows of the composite signal.
	/// </summary>
	public int RangePeriod
	{
		get => _rangePeriod.Value;
		set => _rangePeriod.Value = value;
	}

	/// <summary>
	/// Smoothing period for the signal envelopes.
	/// </summary>
	public int SmoothPeriod
	{
		get => _smoothPeriod.Value;
		set => _smoothPeriod.Value = value;
	}

	/// <summary>
	/// Moving average type used for the envelope smoothing.
	/// </summary>
	public MovingAverageKinds RangeAverageType
	{
		get => _rangeAverageType.Value;
		set => _rangeAverageType.Value = value;
	}

	/// <summary>
	/// Maximum step applied when the composite signal changes sharply.
	/// </summary>
	public decimal StepThreshold
	{
		get => _stepThreshold.Value;
		set => _stepThreshold.Value = value;
	}

	/// <summary>
	/// Weight applied to the IBS component.
	/// </summary>
	public decimal IbsWeight
	{
		get => _ibsWeight.Value;
		set => _ibsWeight.Value = value;
	}

	/// <summary>
	/// Weight applied to the RSI component.
	/// </summary>
	public decimal RsiWeight
	{
		get => _rsiWeight.Value;
		set => _rsiWeight.Value = value;
  }
  
	/// <summary>
	/// Weight applied to the CCI component within the composite oscillator.
	/// </summary>
	public decimal CciWeight
	{
		get => _cciWeight.Value;
		set => _cciWeight.Value = value;
	}

	/// <summary>
	/// Number of closed candles used for confirmation logic.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Enables long entries when <c>true</c>.
	/// </summary>
	public bool EnableLongOpen
	{
		get => _enableLongOpen.Value;
		set => _enableLongOpen.Value = value;
	}

	/// <summary>
	/// Enables short entries when <c>true</c>.
	/// </summary>
	public bool EnableShortOpen
	{
		get => _enableShortOpen.Value;
		set => _enableShortOpen.Value = value;
	}

	/// <summary>
	/// Enables long exits when <c>true</c>.
	/// </summary>
	public bool EnableLongClose
	{
		get => _enableLongClose.Value;
		set => _enableLongClose.Value = value;
	}

	/// <summary>
	/// Enables short exits when <c>true</c>.
	/// </summary>
	public bool EnableShortClose
	{
		get => _enableShortClose.Value;
		set => _enableShortClose.Value = value;
	}

	/// <summary>
	/// Volume used for new market orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _volume.Value;
		set => _volume.Value = value;
	}

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

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

		_hasSignal = false;
		_lastSignal = 0m;
		_signalHistory.Clear();
		_baselineHistory.Clear();
	}

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

		Volume = OrderVolume;

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_ibsAverage = CreateMovingAverage(IbsAverageType, Math.Max(1, IbsPeriod));
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_rsi, _cci, ProcessCandle)
		.Start();

		// removed StartProtection(null, null)

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

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

		// removed IFOAAT

		if (!_rsi.IsFormed || !_cci.IsFormed)
		return;

		var range = candle.HighPrice - candle.LowPrice;
		if (range == 0m)
		{
			var step = Security?.PriceStep ?? 0.0001m;
			if (step == 0m)
			step = 0.0001m;
			range = step;
		}

		var ibsRaw = (candle.ClosePrice - candle.LowPrice) / range;
		var ibsValue = _ibsAverage.Process(new DecimalIndicatorValue(_ibsAverage, ibsRaw, candle.OpenTime) { IsFinal = true });
		if (ibsValue is not DecimalIndicatorValue { IsFinal: true, Value: var ibsSmoothed })
		return;

		var compositeTarget = ((ibsSmoothed - 0.5m) * IbsWeight + cciValue * CciWeight + (rsiValue - 50m) * RsiWeight) / 3m;
		var adjustedSignal = ApplyStepConstraint(compositeTarget);

		_signalHistory.Add(adjustedSignal);
		var maxSignalHistory = Math.Max(2, Math.Max(RangePeriod, SignalBar + 2) + Math.Max(1, SmoothPeriod));
		if (_signalHistory.Count > maxSignalHistory)
			_signalHistory.RemoveAt(0);

		if (_signalHistory.Count < Math.Max(1, RangePeriod))
			return;

		var highest = decimal.MinValue;
		var lowest = decimal.MaxValue;
		var startIndex = Math.Max(0, _signalHistory.Count - RangePeriod);

		for (var i = startIndex; i < _signalHistory.Count; i++)
		{
			var value = _signalHistory[i];
			if (value > highest)
				highest = value;
			if (value < lowest)
				lowest = value;
		}

		var baseline = (highest + lowest) / 2m;

		UpdateHistory(baseline);

		var historyLength = Math.Min(_signalHistory.Count, _baselineHistory.Count);
		if (historyLength <= SignalBar)
		return;

		var previousIndex = historyLength - 1 - Math.Max(0, SignalBar);
		var previousSignal = _signalHistory[previousIndex];
		var previousBaseline = _baselineHistory[previousIndex];
		var currentSignal = _signalHistory[historyLength - 1];
		var currentBaseline = _baselineHistory[historyLength - 1];

		var position = Position;

		if (position > 0 && EnableLongClose && previousSignal < previousBaseline)
		{
			SellMarket();
			position = 0m;
		}
		else if (position < 0 && EnableShortClose && previousSignal > previousBaseline)
		{
			BuyMarket();
			position = 0m;
		}

		if (EnableLongOpen && position <= 0m && previousSignal > previousBaseline && currentSignal <= currentBaseline)
		{
			BuyMarket();
		}
		else if (EnableShortOpen && position >= 0m && previousSignal < previousBaseline && currentSignal >= currentBaseline)
		{
			SellMarket();
		}
	}

	private decimal ApplyStepConstraint(decimal target)
	{
		if (!_hasSignal)
		{
			_lastSignal = target;
			_hasSignal = true;
			return _lastSignal;
		}

		var threshold = Math.Abs(StepThreshold);
		if (threshold <= 0m)
		{
			_lastSignal = target;
			return _lastSignal;
		}

		var diff = target - _lastSignal;
		if (Math.Abs(diff) > threshold)
		{
			var direction = diff > 0m ? 1m : -1m;
			_lastSignal = target - direction * threshold;
		}
		else
		{
			_lastSignal = target;
		}

		return _lastSignal;
	}

	private void UpdateHistory(decimal baseline)
	{
		var maxSize = Math.Max(2, Math.Max(RangePeriod, SignalBar + 2) + Math.Max(1, SmoothPeriod));
		_baselineHistory.Add(baseline);
		if (_baselineHistory.Count > maxSize)
			_baselineHistory.RemoveAt(0);
	}

	private static IIndicator CreateMovingAverage(MovingAverageKinds kind, int length)
	{
		return kind switch
		{
			MovingAverageKinds.Exponential => new EMA { Length = length },
			MovingAverageKinds.Smoothed => new SmoothedMovingAverage { Length = length },
			MovingAverageKinds.LinearWeighted => new WeightedMovingAverage { Length = length },
			_ => new SMA { Length = length },
		};
	}

	/// <summary>
	/// Supported moving average families.
	/// </summary>
	public enum MovingAverageKinds
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average.
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		LinearWeighted
	}
}