Ver en GitHub

Estrategia de Daily Range

Descripción general

Esta estrategia es una conversión de StockSharp del asesor experto de MetaTrader 5 MQL/23334/Daily range.mq5. El EA original rastrea los precios más altos y más bajos alcanzados durante los últimos días, desplaza estos niveles por un porcentaje configurable del rango diario, y opera rupturas. El port en C# preserva el comportamiento adoptando la API de estrategia de alto nivel de StockSharp.

Lógica de la estrategia

Cálculo del rango

  • La estrategia almacena estadísticas agregadas para cada día de negociación (máximo, mínimo, último cierre).
  • Se mantiene una ventana deslizante de SlidingWindowDays días recientes (incluido el actual).
  • RangeMode selecciona cómo se calcula el rango de referencia:
    • HighestLowest – la distancia entre el máximo más alto y el mínimo más bajo en la ventana.
    • CloseToClose – el cambio absoluto promedio entre precios de cierre diarios consecutivos dentro de la ventana.
  • Una vez que se alcanza el StartTime configurado en un nuevo día, la estrategia reconstruye los niveles de ruptura superior e inferior:
    • Upper = Highest + Range × OffsetCoefficient
    • Lower = Lowest − Range × OffsetCoefficient
  • Hasta que se alcanza StartTime, los niveles de ruptura del día anterior permanecen activos (reflejando la implementación MQL).

Reglas de entrada

  • Una entrada larga se activa cuando el precio de cierre de la vela procesada es mayor o igual al nivel superior actual y se han abierto menos de MaxPositionsPerDay entradas largas el mismo día.
  • Una entrada corta se activa cuando el precio de cierre cae al nivel inferior o por debajo y el límite diario de entradas cortas no se ha alcanzado.
  • Al cambiar de una posición existente al lado opuesto, la estrategia primero compensa el volumen pendiente y luego añade el nuevo Volume encima, coincidiendo con el comportamiento de neteo del EA original.
  • Las señales se evalúan solo en velas terminadas entregadas por la suscripción CandleType configurada y solo cuando IsFormedAndOnlineAndAllowTrading() informa que se permite operar.

Reglas de salida

  • Las distancias de stop-loss y take-profit se derivan del rango actual: Range × StopLossCoefficient y Range × TakeProfitCoefficient respectivamente.
  • Para posiciones largas, se envía una orden de cierre si el mínimo de la vela toca el nivel de stop o el máximo supera el nivel de take-profit.
  • Para posiciones cortas, se envía una orden de cierre si el máximo de la vela toca el nivel de stop o el mínimo cruza el nivel de take-profit.
  • Establecer cualquiera de los coeficientes en cero desactiva la protección correspondiente.

Controles de riesgo y límites

  • Se mantienen contadores diarios separados para entradas largas y cortas. Se reinician cada vez que comienza un nuevo día de negociación.
  • La propiedad Volume de la Strategy base controla el tamaño de las entradas adicionales.
  • No se registran órdenes pendientes; las salidas se ejecutan con órdenes de mercado en la siguiente iteración de la estrategia después de que se detecta la condición.

Parámetros

Nombre Descripción Valor predeterminado
RangeMode Determina cómo se calcula el rango diario (HighestLowest o CloseToClose). HighestLowest
SlidingWindowDays Número de días calendario incluidos en la ventana deslizante utilizada para el cálculo del rango. 3
StopLossCoefficient Multiplicador aplicado al rango actual para definir la distancia del stop-loss. 0.03
TakeProfitCoefficient Multiplicador aplicado al rango actual para definir la distancia del take-profit. 0.05
OffsetCoefficient Desplazamiento adicional aplicado a los niveles de ruptura por encima del máximo y por debajo del mínimo. 0.01
MaxPositionsPerDay Número máximo de entradas permitidas por dirección durante un único día de negociación. 3
StartTime Hora del día cuando se calcula un rango fresco para la sesión actual. 10:05
CandleType Suscripción de velas usada para el cálculo del rango y la evaluación de señales. Marco temporal de 15 minutos

Notas de implementación

  • La estrategia se basa exclusivamente en la infraestructura de alto nivel Strategy de StockSharp (SubscribeCandles, WhenNew, y órdenes de mercado) y no manipula libros de órdenes sin procesar.
  • Las estadísticas de rango se almacenan sin usar búsquedas de valores de indicadores; todos los cálculos ocurren dentro de la estrategia, en línea con las directrices del repositorio.
  • Las órdenes protectoras se simulan monitoreando los extremos de las velas en lugar de registrar órdenes stop/límite separadas, lo que mantiene la implementación portable entre diferentes adaptadores.
  • El soporte de Python se omite intencionalmente según lo solicitado. Solo se proporciona la versión en C# en esta carpeta.
  • Para operaciones en vivo, asegúrese de que haya suficientes velas históricas disponibles para que el primer cálculo del rango tenga suficientes datos con los que trabajar.
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 DailyRangeStrategy : 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 DailyRangeStrategy()
	{
		_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;
	}
}