Ver en GitHub

Estrategia de Reacción Inversa

Descripción general

La estrategia de Reacción Inversa es un sistema de reversión a la media inspirado en el asesor experto original de MetaTrader "IREA". Reacciona a movimientos de barra única inusualmente grandes y anticipa una reacción inversa en la siguiente barra. La estrategia calcula un nivel de confianza dinámico a partir de rangos de velas recientes y solo opera cuando los movimientos de precio superan ese nivel pero permanecen dentro de los límites definidos por el usuario. Solo puede estar abierta una posición a la vez.

Lógica de trading

  1. Indicador de Reacción Inversa – Para cada vela completada la estrategia mide el cambio apertura/cierre y alimenta su valor absoluto a una media móvil simple de longitud MaPeriod. El cambio promediado se multiplica por Coefficient para formar un umbral dinámico similar al Nivel de Confianza Dinámico (DCL) del indicador original.
  2. Validación de señal – El cambio absoluto apertura/cierre de la última vela debe ser mayor que el umbral dinámico, mayor que MinCriteriaPoints * PriceStep, y menor que MaxCriteriaPoints * PriceStep. Las señales se ignoran si la vela anterior ya cumplió la misma condición, lo cual refleja el asesor experto original.
  3. Dirección – Un cambio negativo (vela bajista) sugiere un rebote al alza, por lo que se abre una posición larga. Un cambio positivo implica una expectativa de reversión bajista y desencadena una posición corta. Las nuevas operaciones se envían solo cuando no hay posición existente.
  4. Gestión de riesgos – Después de la entrada, la estrategia monitorea las velas subsiguientes. Si el precio toca los niveles predefinidos de stop-loss o take-profit (convertidos de puntos a precios absolutos usando el PriceStep del instrumento), cierra inmediatamente la posición abierta usando órdenes de mercado. StartProtection() también se habilita para soportar las protecciones integradas de StockSharp.

Parámetros

Parámetro Descripción
StopLossPoints Distancia de stop-loss en puntos (multiplicado por PriceStep).
TakeProfitPoints Distancia de take-profit en puntos.
TradeVolume Volumen usado para cada orden de mercado.
SlippagePoints Configuración informativa que refleja la versión MQL; actualmente no se aplica a las órdenes.
MinCriteriaPoints Distancia mínima apertura/cierre (en puntos) requerida para una señal válida.
MaxCriteriaPoints Distancia máxima apertura/cierre permitida (en puntos).
Coefficient Multiplicador usado para construir el umbral de confianza dinámico.
MaPeriod Longitud de la media móvil usada dentro del indicador. Debe ser al menos 3.
CandleType Marco temporal de las velas procesadas (por defecto: 1 hora).

Directrices de uso

  • Asegúrese de que el instrumento seleccionado tenga un PriceStep válido. Cuando no está disponible, la estrategia recurre a un paso de 1.0, lo que puede distorsionar los umbrales.
  • Ajuste MinCriteriaPoints y MaxCriteriaPoints para adaptarlos a la volatilidad del marco temporal elegido. Una ventana demasiado estrecha filtrará la mayoría de las señales, mientras que una ventana demasiado amplia permitirá movimientos extremadamente grandes que pueden no revertir.
  • El Coefficient predeterminado de 1.618 replica el escalado de ratio áureo del indicador original. Los valores más altos demandan velas de mayor amplitud antes de operar.
  • Dado que las posiciones se cierran por órdenes de mercado en el próximo cierre de vela que viola los niveles de stop o target, la ejecución real puede diferir de los niveles límite exactos. Considere probar con datos intradía para un control más preciso si es necesario.
  • Solo se mantiene una posición a la vez. La estrategia esperará que la operación actual se cierre antes de reaccionar a una nueva señal.

Notas

  • Realice backtesting de la configuración en datos históricos antes de usarlo en vivo. El EA original fue diseñado para mercados FX; puede requerirse ajuste de parámetros para otros activos.
  • El parámetro SlippagePoints se conserva para completitud pero intencionalmente no se usa porque StockSharp maneja el deslizamiento de forma diferente a MetaTrader.
  • Asegúrese de que MaPeriod se mantenga en 3 o más; los valores más pequeños estaban prohibidos en la implementación original y pueden llevar a umbrales inestables.
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>
/// Strategy that reacts to large single-bar moves expecting a mean reversion.
/// </summary>
public class InverseReactionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _coefficient;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _absChanges = new();

	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public decimal Coefficient { get => _coefficient.Value; set => _coefficient.Value = value; }
	public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public InverseReactionStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 250m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take-profit distance in points", "Risk");

		_coefficient = Param(nameof(Coefficient), 1.618m)
			.SetGreaterThanZero()
			.SetDisplay("Coefficient", "Confidence coefficient", "Signal");

		_maPeriod = Param(nameof(MaPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average length", "Signal");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_absChanges.Clear();
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0m) step = 1m;

		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
			useMarketOrders: true);

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

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

		var change = candle.ClosePrice - candle.OpenPrice;
		var absChange = Math.Abs(change);

		_absChanges.Add(absChange);
		if (_absChanges.Count > MaPeriod)
			_absChanges.RemoveAt(0);

		if (_absChanges.Count < MaPeriod)
			return;

		var avg = 0m;
		for (int i = 0; i < _absChanges.Count; i++)
			avg += _absChanges[i];
		avg /= _absChanges.Count;

		var threshold = avg * Coefficient;

		if (Position != 0)
			return;

		if (absChange > threshold && absChange > 0m)
		{
			if (change < 0m)
			{
				// Large bearish bar => expect reversion upward
				BuyMarket();
			}
			else
			{
				// Large bullish bar => expect reversion downward
				SellMarket();
			}
		}
	}
}