Прорыв по расстоянию Supertrend
Стратегия Supertrend Distance Breakout отслеживает резкие расширения индикатора Supertrend. Когда показания выходят за пределы среднего диапазона, цена часто начинает новое движение.
Тестирование показывает среднегодичную доходность около 115%. Стратегию лучше запускать на фондовом рынке.
Позиция открывается, как только индикатор пробивает полосу, построенную по последним данным и множителю отклонения. Возможны сделки в обе стороны со стопом.
Система подходит трейдерам импульсных стратегий, ищущим ранний прорыв. Сделки закрываются, когда Supertrend возвращается к среднему. По умолчанию используется SupertrendPeriod = 10.
Подробности
- Условия входа: индикатор превышает среднее на величину множителя отклонения.
- Длинные/короткие: оба направления.
- Условия выхода: индикатор возвращается к среднему.
- Стопы: да.
- Значения по умолчанию:
SupertrendPeriod= 10SupertrendMultiplier= 3mLookbackPeriod= 20DeviationMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Breakout
- Направление: оба
- Индикаторы: Supertrend
- Стопы: да
- Сложность: средняя
- Таймфрейм: краткосрочный
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
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()