Supertrend Distance Mean Reversion
The Supertrend Distance Mean Reversion strategy focuses on extreme readings of the Supertrend to exploit reversion. Wide departures from the normal level rarely last.
Trades trigger when the indicator swings far from its mean and then begins to reverse. Both long and short setups include a protective stop.
Suited for swing traders expecting oscillations, the strategy closes out once the Supertrend returns toward balance. Starting parameter AtrPeriod = 10.
Details
- Entry Criteria: Indicator crosses back toward mean.
- Long/Short: Both directions.
- Exit Criteria: Indicator reverts to average.
- Stops: Yes.
- Default Values:
AtrPeriod= 10Multiplier= 3.0mLookbackPeriod= 20DeviationMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Mean Reversion
- Direction: Both
- Indicators: Supertrend
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Short-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Supertrend distance mean reversion strategy.
/// Trades large deviations of price from Supertrend and exits when the distance returns to its recent average.
/// </summary>
public class SupertrendDistanceMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private SuperTrend _supertrend;
private decimal[] _distanceHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
/// <summary>
/// ATR period for Supertrend calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier for Supertrend calculation.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Lookback period for distance statistics.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Deviation multiplier for mean reversion detection.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Cooldown bars 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="SupertrendDistanceMeanReversionStrategy"/>.
/// </summary>
public SupertrendDistanceMeanReversionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for Supertrend calculation", "Supertrend")
.SetOptimize(5, 20, 1);
_multiplier = Param(nameof(Multiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Multiplier for Supertrend calculation", "Supertrend")
.SetOptimize(1m, 5m, 0.5m);
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Lookback period for distance statistics", "Strategy Parameters")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Deviation multiplier for mean reversion detection", "Strategy Parameters")
.SetOptimize(1m, 3m, 0.5m);
_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", "Candle type for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_supertrend = null;
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_distanceHistory = new decimal[LookbackPeriod];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_supertrend = new SuperTrend { Length = AtrPeriod, Multiplier = Multiplier };
_distanceHistory = new decimal[LookbackPeriod];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_supertrend, ProcessSupertrend)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _supertrend);
DrawOwnTrades(area);
}
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
}
private void ProcessSupertrend(ICandleMessage candle, decimal supertrendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_supertrend.IsFormed)
return;
var distance = Math.Abs(candle.ClosePrice - supertrendValue);
_distanceHistory[_currentIndex] = distance;
_currentIndex = (_currentIndex + 1) % LookbackPeriod;
if (_filledCount < LookbackPeriod)
_filledCount++;
if (_filledCount < LookbackPeriod)
return;
var avgDistance = 0m;
var sumSq = 0m;
for (var i = 0; i < LookbackPeriod; i++)
avgDistance += _distanceHistory[i];
avgDistance /= LookbackPeriod;
for (var i = 0; i < LookbackPeriod; i++)
{
var diff = _distanceHistory[i] - avgDistance;
sumSq += diff * diff;
}
var stdDistance = (decimal)Math.Sqrt((double)(sumSq / LookbackPeriod));
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var extendedThreshold = avgDistance + stdDistance * DeviationMultiplier;
var priceAboveSupertrend = candle.ClosePrice > supertrendValue;
var priceBelowSupertrend = candle.ClosePrice < supertrendValue;
if (Position == 0)
{
if (distance > extendedThreshold)
{
if (priceAboveSupertrend)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (priceBelowSupertrend)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
else if (Position > 0 && (distance <= avgDistance || priceAboveSupertrend))
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && (distance <= avgDistance || priceBelowSupertrend))
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
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 SuperTrend
from StockSharp.Algo.Strategies import Strategy
class supertrend_distance_mean_reversion_strategy(Strategy):
"""
Supertrend distance mean reversion strategy.
Trades large deviations of price from Supertrend and exits when the distance returns to its recent average.
"""
def __init__(self):
super(supertrend_distance_mean_reversion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "ATR period for Supertrend calculation", "Supertrend")
self._multiplier = self.Param("Multiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Multiplier", "Multiplier for Supertrend calculation", "Supertrend")
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Lookback period for distance statistics", "Strategy Parameters")
self._deviation_multiplier = self.Param("DeviationMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Multiplier", "Deviation multiplier for mean reversion detection", "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", "Candle type for strategy", "General")
self._supertrend = None
self._distance_history = None
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(supertrend_distance_mean_reversion_strategy, self).OnReseted()
self._supertrend = None
lb = int(self._lookback_period.Value)
self._distance_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
def OnStarted2(self, time):
super(supertrend_distance_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._lookback_period.Value)
self._distance_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._supertrend = SuperTrend()
self._supertrend.Length = int(self._atr_period.Value)
self._supertrend.Multiplier = self._multiplier.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._supertrend, self._process_supertrend).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._supertrend)
self.DrawOwnTrades(area)
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
def _process_supertrend(self, candle, supertrend_value):
if candle.State != CandleStates.Finished:
return
if not self._supertrend.IsFormed:
return
close_price = float(candle.ClosePrice)
st_val = float(supertrend_value)
distance = abs(close_price - st_val)
lb = int(self._lookback_period.Value)
self._distance_history[self._current_index] = distance
self._current_index = (self._current_index + 1) % lb
if self._filled_count < lb:
self._filled_count += 1
if self._filled_count < lb:
return
avg_distance = 0.0
for i in range(lb):
avg_distance += self._distance_history[i]
avg_distance /= float(lb)
sum_sq = 0.0
for i in range(lb):
diff = self._distance_history[i] - avg_distance
sum_sq += diff * diff
std_distance = math.sqrt(sum_sq / float(lb))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
dm = float(self._deviation_multiplier.Value)
extended_threshold = avg_distance + std_distance * dm
price_above_supertrend = close_price > st_val
price_below_supertrend = close_price < st_val
if self.Position == 0:
if distance > extended_threshold:
if price_above_supertrend:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif price_below_supertrend:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0 and (distance <= avg_distance or price_above_supertrend):
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0 and (distance <= avg_distance or price_below_supertrend):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return supertrend_distance_mean_reversion_strategy()