The ATR Normalize Histogram strategy reproduces the behavior of the MetaTrader expert Exp_ATR_Normalize_Histogram inside StockSharp. The system observes the normalized ratio between the smoothed close-to-low distance and the smoothed true range. Color changes of the histogram drive both entries and exits, emulating the multi-buffer logic used in the original MQL5 implementation.
Indicator Calculation
For every finished candle the strategy calculates:
diff = Close − Low.
range = max(High, previous Close) − min(Low, previous Close).
Each series is smoothed independently with the selected methods and lengths. Five methods are available: Simple, Exponential, Smoothed (RMA), Weighted and Jurik. Unsupported MQL methods (JurX, Parabolic, T3, VIDYA, AMA) fall back to the simple moving average.
Thresholds split the histogram into five bands. Crossing between bands mirrors the color buffer produced by the MQL indicator.
Signal Logic
Entry filter – SignalBar selects which historical bar should be evaluated (default 1, the last closed bar). The strategy compares the color of that bar with the previous one:
A transition from the bullish extreme (color 0) to any other color opens a long position when long trades are enabled.
A transition from the bearish extreme (color 4) to any other color opens a short position when short trades are enabled.
Exit filter – the color of the previous bar alone is sufficient to close positions:
Color 0 closes short positions if short exits are enabled.
Color 4 closes long positions if long exits are enabled.
Exits are processed before any new entries so that the strategy never keeps overlapping trades.
Risk Management
The strategy keeps track of the last fill price and optionally enforces protective stops and targets measured in instrument points. The conversion uses Security.PriceStep, matching the “points” concept from the original expert. When either limit is hit intrabar, the position is closed immediately and trade direction can change on the following signal.
Parameters
CandleType – timeframe used for the calculation.
FirstSmoothingMethod / SecondSmoothingMethod – smoothing type for diff and range streams.
FirstLength / SecondLength – periods for the smoothers.
SignalBar – offset for buffer evaluation (minimum 1).
EnableBuyEntries, EnableSellEntries, EnableBuyExits, EnableSellExits – toggles for managing the four trade directions.
TradeVolume – base order size. The strategy automatically offsets existing exposure when flipping direction.
StopLossPoints, TakeProfitPoints – optional protective distances in points; set to zero to disable.
Notes and Differences vs. MQL Version
Both smoothing stages are configurable independently, but only the five StockSharp moving average implementations are available. When another MQL method is selected the strategy defaults to the simple moving average while keeping the length.
The SignalBar logic follows the buffer shift used in CopyBuffer, so larger offsets still compare the chosen bar with its immediate predecessor.
Money management parameters from the original expert (MM, MMMode, Deviation) are simplified to a single TradeVolume parameter. Order execution happens at market with optional stop/target monitoring.
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()