GitHub で見る
ATR 正規化ヒストグラム戦略
概要
ATR 正規化ヒストグラム戦略は、MetaTrader のエキスパート Exp_ATR_Normalize_Histogram の動作を StockSharp 内で再現します。このシステムは、平滑化された終値から安値までの距離と平滑化された真のレンジの正規化された比率を観察します。ヒストグラムの色の変化がエントリーとエグジットの両方を駆動し、元の MQL5 実装で使用されたマルチバッファロジックをエミュレートします。
インジケーター計算
完成した各ローソク足について、戦略は以下を計算します:
diff = Close − Low。
range = max(High, 前の Close) − min(Low, 前の Close)。
各系列は選択したメソッドと長さで独立して平滑化されます。5 つのメソッドが利用可能です:Simple、Exponential、Smoothed (RMA)、Weighted、Jurik。サポートされていない MQL メソッド(JurX、Parabolic、T3、VIDYA、AMA)は単純移動平均にフォールバックします。
正規化されたヒストグラム値は以下のように計算されます:
normalized = 100 × smoothedDiff / max(|smoothedRange|, PriceStep)。
閾値がヒストグラムを 5 つのバンドに分割します。バンド間の遷移は MQL インジケーターが生成するカラーバッファを反映します。
シグナルロジック
- エントリーフィルター –
SignalBar はどの過去のバーを評価するかを選択します(デフォルト 1、最後に閉じたバー)。戦略はそのバーの色を前のバーと比較します:
- 強気の極値(色
0)から別の色への遷移は、ロングトレードが有効な場合にロングポジションを開きます。
- 弱気の極値(色
4)から別の色への遷移は、ショートトレードが有効な場合にショートポジションを開きます。
- エグジットフィルター – 前のバーの色だけでポジションを閉じるのに十分です:
- 色
0 はショートエグジットが有効な場合にショートポジションを閉じます。
- 色
4 はロングエグジットが有効な場合にロングポジションを閉じます。
- 戦略が重複するトレードを保持しないよう、エグジットは新しいエントリーの前に処理されます。
リスク管理
戦略は最後の約定価格を追跡し、オプションで商品ポイント単位の保護ストップと目標を適用します。変換は Security.PriceStep を使用し、元のエキスパートの「ポイント」概念に対応しています。いずれかの制限がバー内でヒットすると、ポジションはすぐに閉じられ、次のシグナルでトレード方向が変わる可能性があります。
パラメーター
CandleType – 計算に使用するタイムフレーム。
FirstSmoothingMethod / SecondSmoothingMethod – diff および range ストリームの平滑化タイプ。
FirstLength / SecondLength – スムーザーの期間。
HighLevel、MiddleLevel、LowLevel – ヒストグラムの閾値(デフォルト 60/50/40)。
SignalBar – バッファ評価のオフセット(最小 1)。
EnableBuyEntries、EnableSellEntries、EnableBuyExits、EnableSellExits – 4 つのトレード方向を管理するトグル。
TradeVolume – 基本注文サイズ。方向転換時に戦略が既存のエクスポージャーを自動的に相殺します。
StopLossPoints、TakeProfitPoints – ポイント単位のオプションの保護距離;無効にするにはゼロに設定。
MQL バージョンとの注意点と相違点
- 両方の平滑化段階は独立して設定可能ですが、利用できるのは 5 つの StockSharp 移動平均実装のみです。別の MQL メソッドを選択した場合、戦略は長さを維持しながら単純移動平均をデフォルトとします。
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()