La estrategia de Histograma ATR Normalizado reproduce el comportamiento del experto de MetaTrader Exp_ATR_Normalize_Histogram dentro de StockSharp. El sistema observa la proporción normalizada entre la distancia suavizada de cierre a mínimo y el rango verdadero suavizado. Los cambios de color del histograma impulsan tanto las entradas como las salidas, emulando la lógica multi-buffer utilizada en la implementación MQL5 original.
Cálculo del indicador
Para cada vela completada, la estrategia calcula:
diff = Close − Low.
range = max(High, cierre anterior) − min(Low, cierre anterior).
Cada serie se suaviza independientemente con los métodos y longitudes seleccionados. Hay cinco métodos disponibles: Simple, Exponencial, Suavizado (RMA), Ponderado y Jurik. Los métodos MQL no compatibles (JurX, Parabolic, T3, VIDYA, AMA) utilizan la media móvil simple como alternativa.
El valor normalizado del histograma se calcula como
Los umbrales dividen el histograma en cinco bandas. El cruce entre bandas refleja el buffer de color producido por el indicador MQL.
Lógica de señales
Filtro de entrada – SignalBar selecciona qué barra histórica debe evaluarse (predeterminado 1, la última barra cerrada). La estrategia compara el color de esa barra con el de la anterior:
Una transición desde el extremo alcista (color 0) a cualquier otro color abre una posición larga cuando las operaciones largas están habilitadas.
Una transición desde el extremo bajista (color 4) a cualquier otro color abre una posición corta cuando las operaciones cortas están habilitadas.
Filtro de salida – el color de la barra anterior es suficiente por sí solo para cerrar posiciones:
El color 0 cierra posiciones cortas si las salidas cortas están habilitadas.
El color 4 cierra posiciones largas si las salidas largas están habilitadas.
Las salidas se procesan antes de cualquier nueva entrada para que la estrategia nunca mantenga operaciones superpuestas.
Gestión de riesgo
La estrategia lleva un registro del último precio de ejecución y opcionalmente aplica stops de protección y objetivos medidos en puntos del instrumento. La conversión utiliza Security.PriceStep, coincidiendo con el concepto de "puntos" del experto original. Cuando se alcanza cualquier límite dentro de la barra, la posición se cierra inmediatamente y la dirección de la operación puede cambiar en la siguiente señal.
Parámetros
CandleType – marco temporal utilizado para el cálculo.
FirstSmoothingMethod / SecondSmoothingMethod – tipo de suavizado para los flujos diff y range.
FirstLength / SecondLength – períodos para los suavizadores.
HighLevel, MiddleLevel, LowLevel – umbrales del histograma (predeterminado 60/50/40).
SignalBar – desplazamiento para la evaluación del buffer (mínimo 1).
EnableBuyEntries, EnableSellEntries, EnableBuyExits, EnableSellExits – interruptores para gestionar las cuatro direcciones de operación.
TradeVolume – tamaño de orden base. La estrategia compensa automáticamente la exposición existente al cambiar de dirección.
StopLossPoints, TakeProfitPoints – distancias de protección opcionales en puntos; establecer en cero para deshabilitar.
Notas y diferencias con la versión MQL
Ambas etapas de suavizado son configurables de forma independiente, pero solo están disponibles las cinco implementaciones de media móvil de StockSharp. Cuando se selecciona otro método MQL, la estrategia usa la media móvil simple manteniendo la longitud.
La lógica de SignalBar sigue el desplazamiento del buffer usado en CopyBuffer, por lo que desplazamientos mayores aún comparan la barra elegida con su predecesor inmediato.
Los parámetros de gestión de dinero del experto original (MM, MMMode, Deviation) se simplifican a un único parámetro TradeVolume. La ejecución de órdenes se realiza a mercado con monitoreo opcional de stop/objetivo.
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;
/// <summary>
/// Strategy based on normalized ATR histogram transitions (simplified).
/// Uses ATR to measure volatility and trades on regime changes.
/// </summary>
public class AtrNormalizeHistogramStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _emaLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public AtrNormalizeHistogramStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period for trend", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
decimal prevAtr = 0;
bool hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, (ICandleMessage candle, decimal atrValue, decimal emaValue) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevAtr = atrValue;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevAtr = atrValue;
return;
}
var price = candle.ClosePrice;
// ATR expanding + price above EMA = bullish breakout
if (atrValue > prevAtr * 1.1m && price > emaValue && Position <= 0)
{
BuyMarket();
}
// ATR expanding + price below EMA = bearish breakout
else if (atrValue > prevAtr * 1.1m && price < emaValue && Position >= 0)
{
SellMarket();
}
prevAtr = atrValue;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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 AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class atr_normalize_histogram_strategy(Strategy):
def __init__(self):
super(atr_normalize_histogram_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period for trend", "Indicators")
self._prev_atr = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnReseted(self):
super(atr_normalize_histogram_strategy, self).OnReseted()
self._prev_atr = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(atr_normalize_histogram_strategy, self).OnStarted2(time)
self._prev_atr = 0.0
self._has_prev = False
atr = AverageTrueRange()
atr.Length = self.AtrLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(atr, ema, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, atr_value, ema_value):
if candle.State != CandleStates.Finished:
return
av = float(atr_value)
ev = float(ema_value)
if not self._has_prev:
self._prev_atr = av
self._has_prev = True
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_atr = av
return
close = float(candle.ClosePrice)
if av > self._prev_atr * 1.1 and close > ev and self.Position <= 0:
self.BuyMarket()
elif av > self._prev_atr * 1.1 and close < ev and self.Position >= 0:
self.SellMarket()
self._prev_atr = av
def CreateClone(self):
return atr_normalize_histogram_strategy()