Las barras de volumen y rango iguales transfieren el script MetaTrader 4 equalvolumebars.mq4 a StockSharp. El script original generaba gráficos fuera de línea cuyas velas se cerraban después de un número fijo de ticks o después de que el precio había atravesado un rango de puntos configurable. La estrategia reproduce la misma lógica de creación de velas dentro del entorno StockSharp: escucha ticks en vivo, opcionalmente precarga velas M1 históricas y emite entradas de registro detalladas cada vez que se completa una barra sintética.
Lógica de construcción de velas
Modos de funcionamiento duales: EqualVolumeBars cierra la barra una vez que el volumen de ticks acumulado supera el umbral configurado, mientras que RangeBars requiere que el rango alto-bajo de la vela (medido en pasos de precio de seguridad) supere el mismo umbral numérico.
Actualizaciones basadas en ticks: cada actualización comercial actualiza el volumen máximo, mínimo, de cierre y de tick de la vela actual. Cuando se excede el umbral, la estrategia finaliza la vela anterior con las estadísticas existentes e inmediatamente comienza una nueva barra con el tick actual como primera entrada.
Semilla de historial de minutos (opcional): cuando FromMinuteHistory está habilitado, la estrategia reproduce velas M1 terminadas como una secuencia de ticks sintéticos (apertura → extremos intermedios → cierre). Esto se aproxima al paso de inicialización del gráfico fuera de línea sin necesidad de archivos CSV externos.
Marcas de tiempo monótonas: el constructor aplica marcas de tiempo estrictamente crecientes para que los consumidores de registros o los módulos posteriores puedan cargar los datos sin encontrar claves de tiempo duplicadas.
Parámetros
Modo de trabajo: selecciona entre EqualVolumeBars y RangeBars construcción de velas.
Ticks In Bar: número de ticks por vela (modo de igual volumen) o rango de puntos medido en pasos de precio (modo de rango).
Usar historial de minutos: permite la reproducción sintética de velas M1 terminadas antes de que lleguen los ticks activos.
Tipo de vela de minuto: suscripción de vela utilizada para el paso de siembra histórico (el valor predeterminado es un período de tiempo de un minuto).
Notas adicionales
La estrategia infiere el tamaño en puntos de Security.PriceStep (recurriendo a Security.MinPriceStep o 0.0001 cuando no hay metadatos disponibles) para reflejar la constante _Point utilizada por MetaTrader.
En lugar de escribir archivos .hst y actualizar una ventana de gráfico, el puerto C# registra cada vela terminada con datos OHLCV completos, lo que facilita alimentar otro componente o comparar resultados con el generador de gráficos sin conexión MT4.
Nunca se envían pedidos; la clase se centra exclusivamente en la transformación de datos al igual que el guión original.
Sólo se proporciona la versión C#. Una versión y una carpeta de Python se omiten intencionalmente según los requisitos de conversión.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Equal Volume Range Bars strategy - ATR-based volatility breakout.
/// Buys when close breaks above EMA + ATR band.
/// Sells when close breaks below EMA - ATR band.
/// </summary>
public class EqualVolumeRangeBarsStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public EqualVolumeRangeBarsStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetDisplay("EMA Period", "EMA lookback", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "ATR lookback", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "ATR band multiplier", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var upper = ema + atr * AtrMultiplier;
var lower = ema - atr * AtrMultiplier;
if (close > upper && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (close < lower && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class equal_volume_range_bars_strategy(Strategy):
def __init__(self):
super(equal_volume_range_bars_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "ATR lookback", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetDisplay("ATR Multiplier", "ATR band multiplier", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def ema_period(self): return self._ema_period.Value
@property
def atr_period(self): return self._atr_period.Value
@property
def atr_multiplier(self): return self._atr_multiplier.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self): super(equal_volume_range_bars_strategy, self).OnReseted()
def OnStarted2(self, time):
super(equal_volume_range_bars_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
atr = AverageTrueRange(); atr.Length = self.atr_period
sub = self.SubscribeCandles(self.candle_type)
sub.Bind(ema, atr, self.process_candle).Start()
def process_candle(self, candle, ema, atr):
if candle.State != CandleStates.Finished: return
close = float(candle.ClosePrice); e = float(ema); a = float(atr)
upper = e + a * self.atr_multiplier; lower = e - a * self.atr_multiplier
if close > upper and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif close < lower and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
def CreateClone(self): return equal_volume_range_bars_strategy()