Прорыв с расширением ATR
Эта стратегия следует за всплесками волатильности, используя средний истинный диапазон. Когда ATR растёт по сравнению с предыдущей свечой, а цена находится относительно скользящей средней, система пытается поймать прорыв.
Тестирование показывает среднегодичную доходность около 145%. Стратегию лучше запускать на крипторынке.
Рост ATR подразумевает сильное движение. Входы совершаются по направлению цены относительно средней, тогда как сокращение волатильности служит сигналом выхода.
Стопы устанавливаются по множителю ATR, чтобы дать сделке пространство в периоды высокой волатильности.
Подробности
- Условия входа: ATR увеличивается, а цена выше или ниже MA.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: ATR сокращается или срабатывает стоп.
- Стопы: да.
- Значения по умолчанию:
AtrPeriod= 14MAPeriod= 20AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Breakout
- Направление: обе стороны
- Индикаторы: 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>
/// Strategy that trades on volatility expansion as measured by ATR.
/// Enters when ATR expands above threshold and price is above/below MA,
/// exits when volatility contracts.
/// </summary>
public class AtrExpansionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _atrExpansionRatio;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<int> _lookback;
private decimal _prevAtr;
private bool _hasPrev;
private int _cooldown;
/// <summary>
/// Period for ATR calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Period for Moving Average calculation.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Ratio of current ATR to previous ATR needed for expansion signal.
/// </summary>
public decimal AtrExpansionRatio
{
get => _atrExpansionRatio.Value;
set => _atrExpansionRatio.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Lookback period for ATR comparison.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Initialize the ATR Expansion strategy.
/// </summary>
public AtrExpansionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 21, 7);
_maPeriod = Param(nameof(MAPeriod), 20)
.SetDisplay("MA Period", "Period for MA calculation", "Indicators")
.SetOptimize(10, 50, 5);
_atrExpansionRatio = Param(nameof(AtrExpansionRatio), 1.05m)
.SetDisplay("Expansion Ratio", "ATR expansion ratio for entry signal", "Entry")
.SetOptimize(1.1m, 2.0m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 100)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
_lookback = Param(nameof(Lookback), 5)
.SetRange(1, 50)
.SetDisplay("Lookback", "Bars to look back for ATR comparison", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = 0;
_hasPrev = false;
_cooldown = 0;
var atr = new AverageTrueRange { Length = AtrPeriod };
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_hasPrev)
{
_prevAtr = atrValue;
_hasPrev = true;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevAtr = atrValue;
return;
}
var isExpanding = _prevAtr > 0 && atrValue / _prevAtr >= AtrExpansionRatio;
var isContracting = _prevAtr > 0 && atrValue / _prevAtr < 1m / AtrExpansionRatio;
if (Position == 0 && isExpanding)
{
// ATR expanding - enter in direction of price vs MA
if (candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && isContracting)
{
// Volatility contracting - exit long
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && isContracting)
{
// Volatility contracting - exit short
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 AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class atr_expansion_strategy(Strategy):
"""
Strategy that trades on volatility expansion as measured by ATR.
Enters when ATR expands above threshold and price is above/below MA,
exits when volatility contracts.
"""
def __init__(self):
super(atr_expansion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for MA calculation", "Indicators")
self._atr_expansion_ratio = self.Param("AtrExpansionRatio", 1.05).SetDisplay("Expansion Ratio", "ATR expansion ratio for entry signal", "Entry")
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._lookback = self.Param("Lookback", 5).SetDisplay("Lookback", "Bars to look back for ATR comparison", "General")
self._prev_atr = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_expansion_strategy, self).OnReseted()
self._prev_atr = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(atr_expansion_strategy, self).OnStarted2(time)
self._prev_atr = 0.0
self._has_prev = False
self._cooldown = 0
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_val, sma_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
if not self._has_prev:
self._prev_atr = av
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_atr = av
return
ratio = float(self._atr_expansion_ratio.Value)
is_expanding = self._prev_atr > 0 and av / self._prev_atr >= ratio
is_contracting = self._prev_atr > 0 and av / self._prev_atr < 1.0 / ratio
close = float(candle.ClosePrice)
sv = float(sma_val)
cd = self._cooldown_bars.Value
if self.Position == 0 and is_expanding:
if close > sv:
self.BuyMarket()
self._cooldown = cd
elif close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and is_contracting:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and is_contracting:
self.BuyMarket()
self._cooldown = cd
self._prev_atr = av
def CreateClone(self):
return atr_expansion_strategy()