ATR Slope 平均回帰戦略
ATR Slope 平均回帰戦略は、ATRの極端な値に着目して回帰を利用することに焦点を当てています。通常水準からの大きな乖離が長続きすることはほとんどありません。
指標が平均から大きく乖離した後、反転し始めたときにトレードが発動します。ロングとショートの両方のセットアップに保護的なストップが含まれています。
振動を期待するスウィングトレーダーに適しており、ATRが均衡に戻ったときにポジションを決済します。開始パラメータ AtrPeriod = 14。
詳細
- エントリー条件: 指標が平均に向かって戻るクロス。
- ロング/ショート: 両方向。
- エグジット条件: 指標が平均に回帰する。
- ストップ: はい。
- デフォルト値:
AtrPeriod= 14LookbackPeriod= 20DeviationMultiplier= 2.0mStopLossMultiplier= 2CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: ATR
- ストップ: はい
- 複雑さ: 中級
- 時間軸: 短期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// ATR slope mean reversion strategy.
/// Trades reversion of extreme ATR slope values with an EMA direction filter.
/// </summary>
public class AtrSlopeMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _slopeLookback;
private readonly StrategyParam<decimal> _thresholdMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private ExponentialMovingAverage _ema;
private decimal _previousAtrValue;
private decimal[] _slopeHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
private bool _isInitialized;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// EMA period.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Lookback used to estimate slope mean and standard deviation.
/// </summary>
public int SlopeLookback
{
get => _slopeLookback.Value;
set => _slopeLookback.Value = value;
}
/// <summary>
/// Standard deviation multiplier for entry threshold.
/// </summary>
public decimal ThresholdMultiplier
{
get => _thresholdMultiplier.Value;
set => _thresholdMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait between orders.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="AtrSlopeMeanReversionStrategy"/>.
/// </summary>
public AtrSlopeMeanReversionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicator Parameters");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period for EMA direction filter", "Indicator Parameters");
_slopeLookback = Param(nameof(SlopeLookback), 20)
.SetGreaterThanZero()
.SetDisplay("Slope Lookback", "Period for slope statistics", "Strategy Parameters");
_thresholdMultiplier = Param(nameof(ThresholdMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entries", "Strategy Parameters");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 1200)
.SetRange(1, 5000)
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr = null;
_ema = null;
_previousAtrValue = default;
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_isInitialized = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange { Length = AtrPeriod };
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atr, _ema, ProcessCandle)
.Start();
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_atr.IsFormed || !_ema.IsFormed)
return;
if (!_isInitialized)
{
_previousAtrValue = atrValue;
_isInitialized = true;
return;
}
var slope = atrValue - _previousAtrValue;
_previousAtrValue = atrValue;
_slopeHistory[_currentIndex] = slope;
_currentIndex = (_currentIndex + 1) % SlopeLookback;
if (_filledCount < SlopeLookback)
_filledCount++;
if (_filledCount < SlopeLookback)
return;
CalculateStatistics(out var averageSlope, out var slopeStdDev);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (slopeStdDev <= 0)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var lowerThreshold = averageSlope - ThresholdMultiplier * slopeStdDev;
var upperThreshold = averageSlope + ThresholdMultiplier * slopeStdDev;
var priceAboveEma = candle.ClosePrice >= emaValue;
var priceBelowEma = candle.ClosePrice <= emaValue;
if (Position == 0)
{
if (slope <= lowerThreshold && priceAboveEma)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (slope >= upperThreshold && priceBelowEma)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0)
{
if (slope >= averageSlope || priceBelowEma)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
if (slope <= averageSlope || priceAboveEma)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
private void CalculateStatistics(out decimal averageSlope, out decimal slopeStdDev)
{
averageSlope = 0m;
var sumSquaredDiffs = 0m;
for (var i = 0; i < SlopeLookback; i++)
averageSlope += _slopeHistory[i];
averageSlope /= SlopeLookback;
for (var i = 0; i < SlopeLookback; i++)
{
var diff = _slopeHistory[i] - averageSlope;
sumSquaredDiffs += diff * diff;
}
slopeStdDev = (decimal)Math.Sqrt((double)(sumSquaredDiffs / SlopeLookback));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class atr_slope_mean_reversion_strategy(Strategy):
"""
ATR slope mean reversion strategy.
Trades reversion of extreme ATR slope values with an EMA direction filter.
"""
def __init__(self):
super(atr_slope_mean_reversion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicator Parameters")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("EMA Period", "Period for EMA direction filter", "Indicator Parameters")
self._slope_lookback = self.Param("SlopeLookback", 20) \
.SetGreaterThanZero() \
.SetDisplay("Slope Lookback", "Period for slope statistics", "Strategy Parameters")
self._threshold_multiplier = self.Param("ThresholdMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entries", "Strategy Parameters")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1200) \
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._atr = None
self._ema = None
self._previous_atr_value = 0.0
self._slope_history = None
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_slope_mean_reversion_strategy, self).OnReseted()
self._atr = None
self._ema = None
self._previous_atr_value = 0.0
lb = int(self._slope_lookback.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
def OnStarted2(self, time):
super(atr_slope_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._slope_lookback.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_period.Value)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._atr, self._ema, self._process_candle).Start()
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_value, ema_value):
if candle.State != CandleStates.Finished:
return
if not self._atr.IsFormed or not self._ema.IsFormed:
return
av = float(atr_value)
ev = float(ema_value)
if not self._is_initialized:
self._previous_atr_value = av
self._is_initialized = True
return
slope = av - self._previous_atr_value
self._previous_atr_value = av
lb = int(self._slope_lookback.Value)
self._slope_history[self._current_index] = slope
self._current_index = (self._current_index + 1) % lb
if self._filled_count < lb:
self._filled_count += 1
if self._filled_count < lb:
return
avg_slope = 0.0
for i in range(lb):
avg_slope += self._slope_history[i]
avg_slope /= float(lb)
sum_sq = 0.0
for i in range(lb):
diff = self._slope_history[i] - avg_slope
sum_sq += diff * diff
std_slope = math.sqrt(sum_sq / float(lb))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if std_slope <= 0:
return
if self._cooldown > 0:
self._cooldown -= 1
return
tm = float(self._threshold_multiplier.Value)
lower_threshold = avg_slope - tm * std_slope
upper_threshold = avg_slope + tm * std_slope
close_price = float(candle.ClosePrice)
price_above_ema = close_price >= ev
price_below_ema = close_price <= ev
if self.Position == 0:
if slope <= lower_threshold and price_above_ema:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif slope >= upper_threshold and price_below_ema:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0:
if slope >= avg_slope or price_below_ema:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0:
if slope <= avg_slope or price_above_ema:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return atr_slope_mean_reversion_strategy()