Прорыв по ширине канала Дончиана
Стратегия Donchian Channel Width Breakout наблюдает за резким расширением канала Дончиана. Когда значения выходят за пределы среднего диапазона, цена часто начинает новое движение.
Тестирование показывает среднегодичную доходность около 61%. Стратегию лучше запускать на крипторынке.
Позиция открывается, как только индикатор пробивает полосу, построенную по последним данным и множителю отклонения. Возможны сделки в обе стороны со стопом.
Система подходит трейдерам импульсных стратегий, желающим поймать ранний прорыв. Сделки закрываются, когда индикатор возвращается к среднему. По умолчанию используется DonchianPeriod = 20.
Подробности
- Условия входа: индикатор превышает среднее на величину множителя отклонения.
- Длинные/короткие: оба направления.
- Условия выхода: индикатор возвращается к среднему.
- Стопы: да.
- Значения по умолчанию:
DonchianPeriod= 20AvgPeriod= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)StopLoss= 2.0m
- Фильтры:
- Категория: Breakout
- Направление: оба
- Индикаторы: Donchian
- Стопы: да
- Сложность: средняя
- Таймфрейм: краткосрочный
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
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>
/// Donchian Channel width breakout.
/// Enters when the channel width exceeds its average by a standard deviation multiplier,
/// in the direction of the close relative to the channel middle.
/// Exits when the width falls back below its average.
/// </summary>
public class DonchianWidthBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _avgPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private SimpleMovingAverage _widthAverage;
private StandardDeviation _widthStdDev;
/// <summary>
/// Donchian Channel period.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Period for the width average and standard deviation.
/// </summary>
public int AvgPeriod
{
get => _avgPeriod.Value;
set => _avgPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for the width breakout.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Initialize <see cref="DonchianWidthBreakoutStrategy"/>.
/// </summary>
public DonchianWidthBreakoutStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Period of the Donchian Channel", "Indicators");
_avgPeriod = Param(nameof(AvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for width average and deviation", "Strategy");
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLoss = Param(nameof(StopLoss), 2.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_widthAverage = null;
_widthStdDev = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highest = new Highest { Length = DonchianPeriod };
var lowest = new Lowest { Length = DonchianPeriod };
_widthAverage = new SimpleMovingAverage { Length = AvgPeriod };
_widthStdDev = new StandardDeviation { Length = AvgPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
StartProtection(
takeProfit: null,
stopLoss: StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Percent) : null
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
var width = highestValue - lowestValue;
var avgWidth = _widthAverage.Process(width, candle.ServerTime, true).ToDecimal();
var stdWidth = _widthStdDev.Process(width, candle.ServerTime, true).ToDecimal();
if (!_widthAverage.IsFormed || !_widthStdDev.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var middle = (highestValue + lowestValue) / 2m;
if (width > avgWidth + Multiplier * stdWidth)
{
if (close > middle && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
return;
}
if (close < middle && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
return;
}
}
if (width < avgWidth)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import Highest, Lowest, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class donchian_width_breakout_strategy(Strategy):
"""
Donchian Channel width breakout.
Enters when the channel width exceeds its average by a standard deviation multiplier,
in the direction of the close relative to the channel middle.
Exits when the width falls back below its average.
"""
def __init__(self):
super(donchian_width_breakout_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Donchian Period", "Period of the Donchian Channel", "Indicators")
self._avg_period = self.Param("AvgPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Average Period", "Period for width average and deviation", "Strategy")
self._multiplier = self.Param("Multiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
self._width_average = None
self._width_std_dev = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(donchian_width_breakout_strategy, self).OnReseted()
self._width_average = None
self._width_std_dev = None
def OnStarted2(self, time):
super(donchian_width_breakout_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self._donchian_period.Value
lowest = Lowest()
lowest.Length = self._donchian_period.Value
self._width_average = SimpleMovingAverage()
self._width_average.Length = self._avg_period.Value
self._width_std_dev = StandardDeviation()
self._width_std_dev.Length = self._avg_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._process_candle).Start()
stop = float(self._stop_loss.Value)
self.StartProtection(None, Unit(stop, UnitTypes.Percent) if stop > 0 else None)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _process_candle(self, candle, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
highest_value = float(highest_value)
lowest_value = float(lowest_value)
width = highest_value - lowest_value
avg_width = float(process_float(self._width_average, width, candle.ServerTime, True))
std_width = float(process_float(self._width_std_dev, width, candle.ServerTime, True))
if not self._width_average.IsFormed or not self._width_std_dev.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
middle = (highest_value + lowest_value) / 2.0
if width > avg_width + float(self._multiplier.Value) * std_width:
if close > middle and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
return
if close < middle and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
return
if width < avg_width:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
def CreateClone(self):
return donchian_width_breakout_strategy()