Ver en GitHub

Estrategia de Explosion Range Expansion

Descripción general

Explosion Range Expansion Strategy es un sistema de ruptura convertido del asesor experto de MetaTrader 5 "Explosion". El algoritmo compara el rango de la vela completada actual con la vela anterior y abre una posición de mercado en la dirección del cuerpo de la vela siempre que la expansión del rango supere una proporción configurable. La versión de StockSharp mantiene las características originales de gestión de dinero y añade parámetros convenientes para control de horario y gestión del trailing stop.

Reglas de trading

  • Expansión del rango: Calcula el rango de la vela actual (High - Low) y lo compara con el rango de la vela anterior. Si el rango actual es mayor que el rango anterior multiplicado por Range Ratio, se genera una señal.
  • Filtro de dirección:
    • Si la vela cierra por encima de su apertura y la posición actual es plana o corta, se envía una orden de mercado larga.
    • Si la vela cierra por debajo de su apertura y la posición actual es plana o larga, se envía una orden de mercado corta.
  • Ventana de trading: Las señales se aceptan solo cuando el tiempo de cierre de la vela cae entre Start Hour y End Hour (inclusive).
  • Límite diario: Cuando One Trade Per Day está habilitado, solo se ejecuta la primera entrada calificada del día de trading.
  • Pausa entre operaciones: Después de una entrada de posición, la estrategia espera Pause (sec) segundos antes de aceptar una nueva señal.
  • Exposición máxima: El tamaño neto de la posición no puede superar Max Positions * Order Volume.

Salidas y gestión de riesgo

  • Protección inicial: Los niveles opcionales de stop-loss y take-profit se definen en pasos de precio y se calculan desde el precio de entrada.
  • Trailing Stop: Cuando está habilitado, el stop-loss se mueve más cerca del precio después de alcanzar un umbral mínimo de beneficio (Trailing Stop + Trailing Step). La lógica de trailing mantiene el mismo comportamiento que en el EA original.
  • Cierre manual en objetivos: Si el rango de la vela alcanza el nivel de stop-loss o take-profit intrabarra, la posición se cierra usando una orden de mercado.

Parámetros

  • Candle Type – Tipo de datos usado para la suscripción de velas.
  • Order Volume – Tamaño de cada posición en lotes.
  • Range Ratio – Multiplicador aplicado al rango de la vela anterior para disparar entradas.
  • Max Positions – Número máximo de lotes permitidos simultáneamente.
  • Pause (sec) – Tiempo mínimo en segundos entre entradas.
  • Start Hour / End Hour – Filtro de horas de trading (0–23).
  • One Trade Per Day – Restringe la estrategia a una entrada por día calendario.
  • Stop Loss – Distancia inicial de stop-loss en pasos de precio.
  • Take Profit – Distancia inicial de take-profit en pasos de precio.
  • Trailing Stop – Distancia de trailing stop en pasos de precio.
  • Trailing Step – Distancia adicional requerida antes de actualizar el trailing.

Notas de conversión

  • La estrategia usa la API SubscribeCandles y Bind de alto nivel para el procesamiento de señales sin indicadores.
  • El trailing stop, la ventana de trading, la pausa y el límite diario reproducen la lógica MQ5 original.
  • La gestión de dinero se expresa mediante un único parámetro de volumen; el dimensionamiento de lote basado en porcentaje de riesgo del script original no está soportado en esta versión.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class ExplosionRangeExpansionStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ExplosionRangeExpansionStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}