Стратегия ATR Normalize Histogram переносит логику советника MetaTrader Exp_ATR_Normalize_Histogram в StockSharp. Система анализирует отношение сглаженной разницы «закрытие − минимум» к сглаженному истинному диапазону. Смена цвета гистограммы формирует сигналы входа и выхода, полностью повторяя многобуферный подход оригинала.
Расчёт индикатора
Для каждой завершённой свечи рассчитываются:
diff = Close − Low;
range = max(High, предыдущий Close) − min(Low, предыдущий Close).
Оба ряда сглаживаются выбранными методами и периодами. Доступны пять вариантов: Simple, Exponential, Smoothed (RMA), Weighted, Jurik. Если в параметрах выбран другой MQL-метод (JurX, Parabolic, T3, VIDYA, AMA), используется простое скользящее среднее с заданной длиной.
Нормализованная гистограмма вычисляется по формуле
Пороговые уровни делят гистограмму на пять зон, что позволяет воспроизвести буфер цветов индикатора.
Логика торговли
Входы. Параметр SignalBar задаёт, какую историческую свечу нужно анализировать (по умолчанию 1 — последняя закрытая). Стратегия сравнивает её цвет с предыдущей:
переход из бычьей экстремальной зоны (цвет 0) в любую другую зону открывает длинную позицию, если разрешена работа в лонг;
переход из медвежьей экстремальной зоны (цвет 4) в любую другую зону открывает короткую позицию, если разрешена работа в шорт.
Выходы. Для закрытия позиции достаточно цвета предыдущей свечи:
цвет 0 закрывает короткие позиции при включённом флаге EnableSellExits;
цвет 4 закрывает длинные позиции при включённом EnableBuyExits.
Сначала выполняются выходы, затем рассматриваются возможные входы, что исключает наложение встречных сделок.
Управление рисками
Стратегия запоминает цену входа и может применять стоп-лосс и тейк-профит в пунктах инструмента. Пересчёт выполняется через Security.PriceStep, то есть полностью соответствует понятию «points» в оригинале. При достижении стопа или цели внутри бара позиция закрывается немедленно, и следующая свеча уже может сформировать новый сигнал.
Параметры
CandleType — таймфрейм расчётов.
FirstSmoothingMethod / SecondSmoothingMethod — методы сглаживания рядов diff и range.
FirstLength / SecondLength — длины сглаживания.
HighLevel, MiddleLevel, LowLevel — пороговые уровни (по умолчанию 60/50/40).
SignalBar — смещение по буферу (минимум 1).
EnableBuyEntries, EnableSellEntries, EnableBuyExits, EnableSellExits — отдельные переключатели входов/выходов для каждой стороны.
TradeVolume — базовый объём заявки; при развороте стратегия автоматически добавляет объём для закрытия текущей позиции.
StopLossPoints, TakeProfitPoints — стоп и цель в пунктах, ноль отключает защиту.
Отличия от версии MQL
Оба этапа сглаживания настраиваются отдельно, но доступны только пять реализаций StockSharp. При выборе нестандартного метода используется простое скользящее среднее с прежним периодом.
Реализация SignalBar соответствует смещению CopyBuffer: независимо от величины смещения сравниваются выбранная свеча и её непосредственный предшественник.
Параметры управления капиталом оригинального советника (MM, MMMode, Deviation) заменены единым параметром TradeVolume. Заявки отправляются по рынку, стоп/тейк выполняются силами стратегии.
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()