Внезапный рост среднего истинного диапазона (ATR) указывает на расширение волатильности, которое может быстро угаснуть. Эта стратегия ищет значения ATR, превышающие свою среднюю на заданный множитель. При появлении разворотной свечи она стремится поймать последующее сжатие.
Тестирование показывает среднегодичную доходность около 139%. Стратегию лучше запускать на фондовом рынке.
Каждая свеча обновляет значение ATR и его среднее. Если ATR превышает среднее на указанный множитель и свеча закрывается в направлении, противоположном предыдущему движению, открывается сделка. Стоп‑лосс также основан на ATR, что привязывает риск к текущей волатильности.
Позиции обычно закрываются по стопу, рассчитывая на быстрый откат после всплеска волатильности.
Детали
Условия входа: всплеск ATR выше среднего и разворотная свеча.
Длинные/короткие: обе стороны.
Условия выхода: стоп‑лосс.
Стопы: да, на основе ATR.
Значения по умолчанию:
AtrPeriod = 14
AtrAvgPeriod = 20
AtrMultiplier = 1.5
MaPeriod = 20
StopLoss = 2%
CandleType = 5 минут
Фильтры:
Категория: разворот
Направление: оба
Индикаторы: ATR, MA
Стопы: да
Сложность: средняя
Таймфрейм: внутридневной
Сезонность: нет
Нейросети: нет
Дивергенция: нет
Уровень риска: средний
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 Exhaustion strategy.
/// Enters when ATR spikes (current ATR significantly higher than previous ATR).
/// ATR spike + bullish candle = buy.
/// ATR spike + bearish candle = sell.
/// Exits on SMA cross.
/// </summary>
public class AtrExhaustionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevAtr;
private int _cooldown;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AtrExhaustionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetRange(7, 21)
.SetDisplay("ATR Period", "Period for ATR", "Indicators");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = 0;
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevAtr = atrValue;
return;
}
if (_prevAtr == 0)
{
_prevAtr = atrValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevAtr = atrValue;
return;
}
// ATR spike: current ATR significantly higher than previous
var atrSpike = _prevAtr > 0 && atrValue > _prevAtr * 1.3m;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
if (Position == 0 && atrSpike && isBullish)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && atrSpike && isBearish)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevAtr = atrValue;
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class atr_exhaustion_strategy(Strategy):
"""
ATR Exhaustion strategy.
Enters when ATR spikes (current ATR significantly higher than previous ATR).
ATR spike + bullish candle = buy.
ATR spike + bearish candle = sell.
Exits on SMA cross.
"""
def __init__(self):
super(atr_exhaustion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "Period for ATR", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._prev_atr = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_exhaustion_strategy, self).OnReseted()
self._prev_atr = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(atr_exhaustion_strategy, self).OnStarted2(time)
self._prev_atr = 0.0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_atr = float(atr_val)
return
av = float(atr_val)
if self._prev_atr == 0:
self._prev_atr = av
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_atr = av
return
# ATR spike: current ATR significantly higher than previous
atr_spike = self._prev_atr > 0 and av > self._prev_atr * 1.3
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
sv = float(sma_val)
close = float(candle.ClosePrice)
cd = self._cooldown_bars.Value
if self.Position == 0 and atr_spike and is_bullish:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and atr_spike and is_bearish:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
self._prev_atr = av
def CreateClone(self):
return atr_exhaustion_strategy()