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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class daily_range_strategy(Strategy):
def __init__(self):
super(daily_range_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(daily_range_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(daily_range_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return daily_range_strategy()