Supertrend Distance Breakout
The Supertrend Distance Breakout strategy watches the Supertrend for sharp expansions. When readings jump beyond their average range, price often starts a new move.
Testing indicates an average annual return of about 115%. It performs best in the stocks market.
A position opens once the indicator pierces a band derived from recent data and a deviation multiplier. Long and short trades are possible with a stop attached.
This system fits momentum traders seeking early breakouts. Trades close as the Supertrend falls back toward the mean. Defaults start with SupertrendPeriod = 10.
Details
- Entry Criteria: Indicator exceeds average by deviation multiplier.
- Long/Short: Both directions.
- Exit Criteria: Indicator reverts to average.
- Stops: Yes.
- Default Values:
SupertrendPeriod= 10SupertrendMultiplier= 3mLookbackPeriod= 20DeviationMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Breakout
- 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.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that enters positions when the distance between price and Supertrend
/// exceeds the average distance plus a multiple of standard deviation
/// </summary>
public class SupertrendDistanceBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _supertrendPeriod;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<DataType> _candleType;
private SuperTrend _supertrend;
private AverageTrueRange _atr;
private decimal _avgDistanceLong;
private decimal _stdDevDistanceLong;
private decimal _avgDistanceShort;
private decimal _stdDevDistanceShort;
private decimal _lastLongDistance;
private decimal _lastShortDistance;
private int _samplesCount;
/// <summary>
/// Supertrend period
/// </summary>
public int SupertrendPeriod
{
get => _supertrendPeriod.Value;
set => _supertrendPeriod.Value = value;
}
/// <summary>
/// Supertrend multiplier
/// </summary>
public decimal SupertrendMultiplier
{
get => _supertrendMultiplier.Value;
set => _supertrendMultiplier.Value = value;
}
/// <summary>
/// Lookback period for distance statistics calculation
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for breakout detection
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Candle type
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public SupertrendDistanceBreakoutStrategy()
{
_supertrendPeriod = Param(nameof(SupertrendPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Supertrend Period", "Period for Supertrend indicator", "Indicator Parameters")
.SetOptimize(5, 20, 1);
_supertrendMultiplier = Param(nameof(SupertrendMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Supertrend Multiplier", "Multiplier for Supertrend indicator", "Indicator Parameters")
.SetOptimize(1m, 5m, 0.5m);
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Period for statistical calculations", "Strategy Parameters")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for breakout detection", "Strategy Parameters")
.SetOptimize(1m, 3m, 0.5m);
_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();
_avgDistanceLong = 0;
_stdDevDistanceLong = 0;
_avgDistanceShort = 0;
_stdDevDistanceShort = 0;
_lastLongDistance = 0;
_lastShortDistance = 0;
_samplesCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_atr = new AverageTrueRange { Length = SupertrendPeriod };
_supertrend = new SuperTrend { Length = SupertrendPeriod, Multiplier = SupertrendMultiplier };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_supertrend, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _supertrend);
DrawOwnTrades(area);
}
// Set up position protection with dynamic stop-loss
StartProtection(
takeProfit: null, // We'll handle exits via our strategy logic
stopLoss: new Unit(2, UnitTypes.Percent) // 2% stop-loss
);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal supertrendPrice)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready for trading
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Calculate distances
decimal longDistance = 0;
decimal shortDistance = 0;
// If price is above Supertrend, calculate distance for long case
if (candle.ClosePrice > supertrendPrice)
longDistance = candle.ClosePrice - supertrendPrice;
// If price is below Supertrend, calculate distance for short case
else if (candle.ClosePrice < supertrendPrice)
shortDistance = supertrendPrice - candle.ClosePrice;
// Update statistics
UpdateDistanceStatistics(longDistance, shortDistance);
// Trading logic
if (_samplesCount >= LookbackPeriod)
{
// Long signal: distance exceeds average + k*stddev and we don't have a long position
if (longDistance > 0 &&
longDistance > _avgDistanceLong + DeviationMultiplier * _stdDevDistanceLong &&
Position <= 0)
{
// Cancel existing orders
CancelActiveOrders();
// Enter long position
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
LogInfo($"Long signal: Distance {longDistance} > Avg {_avgDistanceLong} + {DeviationMultiplier}*StdDev {_stdDevDistanceLong}");
}
// Short signal: distance exceeds average + k*stddev and we don't have a short position
else if (shortDistance > 0 &&
shortDistance > _avgDistanceShort + DeviationMultiplier * _stdDevDistanceShort &&
Position >= 0)
{
// Cancel existing orders
CancelActiveOrders();
// Enter short position
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
LogInfo($"Short signal: Distance {shortDistance} > Avg {_avgDistanceShort} + {DeviationMultiplier}*StdDev {_stdDevDistanceShort}");
}
// Exit conditions - when distance returns to average
if (Position > 0 && longDistance < _avgDistanceLong)
{
// Exit long position
SellMarket(Math.Abs(Position));
LogInfo($"Exit long: Distance {longDistance} < Avg {_avgDistanceLong}");
}
else if (Position < 0 && shortDistance < _avgDistanceShort)
{
// Exit short position
BuyMarket(Math.Abs(Position));
LogInfo($"Exit short: Distance {shortDistance} < Avg {_avgDistanceShort}");
}
}
// Store current distances for next update
_lastLongDistance = longDistance;
_lastShortDistance = shortDistance;
}
private void UpdateDistanceStatistics(decimal longDistance, decimal shortDistance)
{
_samplesCount++;
// Simple calculation of running average and standard deviation
if (_samplesCount == 1)
{
// Initialize with first values
_avgDistanceLong = longDistance;
_avgDistanceShort = shortDistance;
_stdDevDistanceLong = 0;
_stdDevDistanceShort = 0;
}
else
{
// Update running average
decimal oldAvgLong = _avgDistanceLong;
decimal oldAvgShort = _avgDistanceShort;
_avgDistanceLong = oldAvgLong + (longDistance - oldAvgLong) / _samplesCount;
_avgDistanceShort = oldAvgShort + (shortDistance - oldAvgShort) / _samplesCount;
// Update running standard deviation using Welford's algorithm
if (_samplesCount > 1)
{
_stdDevDistanceLong = (1 - 1.0m / (_samplesCount - 1)) * _stdDevDistanceLong +
_samplesCount * ((_avgDistanceLong - oldAvgLong) * (_avgDistanceLong - oldAvgLong));
_stdDevDistanceShort = (1 - 1.0m / (_samplesCount - 1)) * _stdDevDistanceShort +
_samplesCount * ((_avgDistanceShort - oldAvgShort) * (_avgDistanceShort - oldAvgShort));
}
// We only need last LookbackPeriod samples
if (_samplesCount > LookbackPeriod)
{
_samplesCount = LookbackPeriod;
}
}
// Calculate square root for final standard deviation
_stdDevDistanceLong = (decimal)Math.Sqrt((double)_stdDevDistanceLong / _samplesCount);
_stdDevDistanceShort = (decimal)Math.Sqrt((double)_stdDevDistanceShort / _samplesCount);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SuperTrend, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class supertrend_distance_breakout_strategy(Strategy):
"""
Strategy that enters positions when the distance between price and Supertrend
exceeds the average distance plus a multiple of standard deviation
"""
def __init__(self):
"""Constructor"""
super(supertrend_distance_breakout_strategy, self).__init__()
self._supertrendPeriod = self.Param("SupertrendPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Supertrend Period", "Period for Supertrend indicator", "Indicator Parameters") \
.SetCanOptimize(True) \
.SetOptimize(5, 20, 1)
self._supertrendMultiplier = self.Param("SupertrendMultiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Supertrend Multiplier", "Multiplier for Supertrend indicator", "Indicator Parameters") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 5.0, 0.5)
self._lookbackPeriod = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Period for statistical calculations", "Strategy Parameters") \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 5)
self._deviationMultiplier = self.Param("DeviationMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for breakout detection", "Strategy Parameters") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
self._candleType = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._supertrend = None
self._atr = None
self._avgDistanceLong = 0
self._stdDevDistanceLong = 0
self._avgDistanceShort = 0
self._stdDevDistanceShort = 0
self._lastLongDistance = 0
self._lastShortDistance = 0
self._samplesCount = 0
@property
def SupertrendPeriod(self):
"""Supertrend period"""
return self._supertrendPeriod.Value
@SupertrendPeriod.setter
def SupertrendPeriod(self, value):
self._supertrendPeriod.Value = value
@property
def SupertrendMultiplier(self):
"""Supertrend multiplier"""
return self._supertrendMultiplier.Value
@SupertrendMultiplier.setter
def SupertrendMultiplier(self, value):
self._supertrendMultiplier.Value = value
@property
def LookbackPeriod(self):
"""Lookback period for distance statistics calculation"""
return self._lookbackPeriod.Value
@LookbackPeriod.setter
def LookbackPeriod(self, value):
self._lookbackPeriod.Value = value
@property
def DeviationMultiplier(self):
"""Standard deviation multiplier for breakout detection"""
return self._deviationMultiplier.Value
@DeviationMultiplier.setter
def DeviationMultiplier(self, value):
self._deviationMultiplier.Value = value
@property
def CandleType(self):
"""Candle type"""
return self._candleType.Value
@CandleType.setter
def CandleType(self, value):
self._candleType.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
"""
Resets internal state when strategy is reset.
"""
super(supertrend_distance_breakout_strategy, self).OnReseted()
self._avgDistanceLong = 0
self._stdDevDistanceLong = 0
self._avgDistanceShort = 0
self._stdDevDistanceShort = 0
self._lastLongDistance = 0
self._lastShortDistance = 0
self._samplesCount = 0
def OnStarted2(self, time):
super(supertrend_distance_breakout_strategy, self).OnStarted2(time)
self._atr = AverageTrueRange()
self._atr.Length = self.SupertrendPeriod
self._supertrend = SuperTrend()
self._supertrend.Length = self.SupertrendPeriod
self._supertrend.Multiplier = self.SupertrendMultiplier
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._supertrend, self.ProcessCandle).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._supertrend)
self.DrawOwnTrades(area)
# Set up position protection with dynamic stop-loss
self.StartProtection(
takeProfit=None,
stopLoss=Unit(2, UnitTypes.Percent)
)
def ProcessCandle(self, candle, supertrendPrice):
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
# Check if strategy is ready for trading
# Calculate distances
longDistance = 0
shortDistance = 0
# If price is above Supertrend, calculate distance for long case
if candle.ClosePrice > supertrendPrice:
longDistance = float(candle.ClosePrice - supertrendPrice)
# If price is below Supertrend, calculate distance for short case
elif candle.ClosePrice < supertrendPrice:
shortDistance = float(supertrendPrice - candle.ClosePrice)
# Update statistics
self.UpdateDistanceStatistics(longDistance, shortDistance)
# Trading logic
if self._samplesCount >= self.LookbackPeriod:
# Long signal: distance exceeds average + k*stddev and we don't have a long position
if longDistance > 0 and \
longDistance > self._avgDistanceLong + self.DeviationMultiplier * self._stdDevDistanceLong and \
self.Position <= 0:
# Cancel existing orders
self.CancelActiveOrders()
# Enter long position
volume = self.Volume + Math.Abs(self.Position)
self.BuyMarket(volume)
self.LogInfo(f"Long signal: Distance {longDistance} > Avg {self._avgDistanceLong} + {self.DeviationMultiplier}*StdDev {self._stdDevDistanceLong}")
# Short signal: distance exceeds average + k*stddev and we don't have a short position
elif shortDistance > 0 and \
shortDistance > self._avgDistanceShort + self.DeviationMultiplier * self._stdDevDistanceShort and \
self.Position >= 0:
# Cancel existing orders
self.CancelActiveOrders()
# Enter short position
volume = self.Volume + Math.Abs(self.Position)
self.SellMarket(volume)
self.LogInfo(f"Short signal: Distance {shortDistance} > Avg {self._avgDistanceShort} + {self.DeviationMultiplier}*StdDev {self._stdDevDistanceShort}")
# Exit conditions - when distance returns to average
if self.Position > 0 and longDistance < self._avgDistanceLong:
# Exit long position
self.SellMarket(Math.Abs(self.Position))
self.LogInfo(f"Exit long: Distance {longDistance} < Avg {self._avgDistanceLong}")
elif self.Position < 0 and shortDistance < self._avgDistanceShort:
# Exit short position
self.BuyMarket(Math.Abs(self.Position))
self.LogInfo(f"Exit short: Distance {shortDistance} < Avg {self._avgDistanceShort}")
# Store current distances for next update
self._lastLongDistance = longDistance
self._lastShortDistance = shortDistance
def UpdateDistanceStatistics(self, longDistance, shortDistance):
# Simple calculation of running average and standard deviation
self._samplesCount += 1
if self._samplesCount == 1:
# Initialize with first values
self._avgDistanceLong = longDistance
self._avgDistanceShort = shortDistance
self._stdDevDistanceLong = 0
self._stdDevDistanceShort = 0
else:
# Update running average
oldAvgLong = self._avgDistanceLong
oldAvgShort = self._avgDistanceShort
self._avgDistanceLong = oldAvgLong + (longDistance - oldAvgLong) / self._samplesCount
self._avgDistanceShort = oldAvgShort + (shortDistance - oldAvgShort) / self._samplesCount
# Update running standard deviation using Welford's algorithm
if self._samplesCount > 1:
self._stdDevDistanceLong = (1 - 1.0 / (self._samplesCount - 1)) * self._stdDevDistanceLong + \
self._samplesCount * ((self._avgDistanceLong - oldAvgLong) * (self._avgDistanceLong - oldAvgLong))
self._stdDevDistanceShort = (1 - 1.0 / (self._samplesCount - 1)) * self._stdDevDistanceShort + \
self._samplesCount * ((self._avgDistanceShort - oldAvgShort) * (self._avgDistanceShort - oldAvgShort))
# We only need last LookbackPeriod samples
if self._samplesCount > self.LookbackPeriod:
self._samplesCount = self.LookbackPeriod
# Calculate square root for final standard deviation
self._stdDevDistanceLong = Math.Sqrt(float(self._stdDevDistanceLong) / self._samplesCount)
self._stdDevDistanceShort = Math.Sqrt(float(self._stdDevDistanceShort) / self._samplesCount)
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return supertrend_distance_breakout_strategy()