Donchian-Ausbruch-Strategie
Ein Ausbruchssystem mit Donchian-Kanälen und Volatilitäts- und Volumenfiltern.
Die Strategie kauft, wenn der Preis oberhalb des oberen Donchian-Kanals schließt und der Trend durch eine EMA und einen RSI über 50 bestätigt wird. Short-Positionen werden bei Brüchen unterhalb des unteren Kanals eröffnet. Positionen werden bei einem entgegengesetzten Donchian-Signal oder beim Auslösen eines ATR-basierten Stops geschlossen.
Details
- Einstiegskriterien: Donchian-Kanalausbruch mit EMA-, RSI-, Volatilitäts- und Volumenfiltern.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Entgegengesetzter Ausbruch oder ATR-Stop.
- Stops: ATR-basiert.
- Standardwerte:
EntryLength= 20ExitLength= 10AtrLength= 14AtrMultiplier= 1.5EmaLength= 50VolumeSmaLength= 20AtrSmaLength= 20CandleType= TimeSpan.FromMinutes(1)
- Filter:
- Kategorie: Trend
- Richtung: Beide
- Indikatoren: Donchian, ATR, EMA, RSI, Volumen
- Stops: ATR-Stop
- Komplexität: Mittel
- Zeitrahmen: Intraday (1m)
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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 breakout strategy with trend, volatility and volume filters.
/// A close above the EntryLength channel of the previous candles goes long when the close is above the EMA, RSI is above 50,
/// ATR is above its AtrSmaLength average and volume is above its VolumeSmaLength average; a close below the channel goes short
/// with the mirrored trend filters. An opposite entry breakout reverses the position.
/// A long also exits on a close below the ExitLength channel or at an ATR stop of AtrMultiplier times ATR from the entry; shorts mirror this.
/// </summary>
public class DonchianBreakoutStrategy : Strategy
{
private const int _rsiLength = 14;
private readonly StrategyParam<int> _entryLength;
private readonly StrategyParam<int> _exitLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _volumeSmaLength;
private readonly StrategyParam<int> _atrSmaLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private SimpleMovingAverage _atrSma;
private decimal? _prevEntryUpper;
private decimal? _prevEntryLower;
private decimal? _prevExitUpper;
private decimal? _prevExitLower;
private decimal? _stopPrice;
/// <summary>
/// Candles of the entry channel.
/// </summary>
public int EntryLength { get => _entryLength.Value; set => _entryLength.Value = value; }
/// <summary>
/// Candles of the exit channel.
/// </summary>
public int ExitLength { get => _exitLength.Value; set => _exitLength.Value = value; }
/// <summary>
/// ATR length.
/// </summary>
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <summary>
/// ATR multiplier of the stop.
/// </summary>
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
/// <summary>
/// Trend EMA length.
/// </summary>
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
/// <summary>
/// Volume average length.
/// </summary>
public int VolumeSmaLength { get => _volumeSmaLength.Value; set => _volumeSmaLength.Value = value; }
/// <summary>
/// ATR average length.
/// </summary>
public int AtrSmaLength { get => _atrSmaLength.Value; set => _atrSmaLength.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public DonchianBreakoutStrategy()
{
_entryLength = Param(nameof(EntryLength), 20)
.SetGreaterThanZero()
.SetDisplay("Entry Length", "Candles of the entry channel", "Donchian");
_exitLength = Param(nameof(ExitLength), 10)
.SetGreaterThanZero()
.SetDisplay("Exit Length", "Candles of the exit channel", "Donchian");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR length", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "ATR multiplier of the stop", "Risk");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Trend EMA length", "Filters");
_volumeSmaLength = Param(nameof(VolumeSmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume SMA Length", "Volume average length", "Filters");
_atrSmaLength = Param(nameof(AtrSmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("ATR SMA Length", "ATR average length", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_prevEntryUpper = null;
_prevEntryLower = null;
_prevExitUpper = null;
_prevExitLower = null;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var entryChannel = new DonchianChannels { Length = EntryLength };
var exitChannel = new DonchianChannels { Length = ExitLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var rsi = new RelativeStrengthIndex { Length = _rsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
_volumeSma = new SimpleMovingAverage { Length = VolumeSmaLength };
_atrSma = new SimpleMovingAverage { Length = AtrSmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(entryChannel, exitChannel, ema, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, entryChannel);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue entryValue, IIndicatorValue exitValue, IIndicatorValue emaValue, IIndicatorValue rsiValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
// Channels are measured on the candles before this one.
var entryUpper = _prevEntryUpper;
var entryLower = _prevEntryLower;
var exitUpper = _prevExitUpper;
var exitLower = _prevExitLower;
if (entryValue.IsFormed && entryValue is IDonchianChannelsValue { UpperBand: decimal eu, LowerBand: decimal el })
{
_prevEntryUpper = eu;
_prevEntryLower = el;
}
if (exitValue.IsFormed && exitValue is IDonchianChannelsValue { UpperBand: decimal xu, LowerBand: decimal xl })
{
_prevExitUpper = xu;
_prevExitLower = xl;
}
var volumeAvg = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true);
if (!atrValue.IsFormed)
return;
var atr = atrValue.GetValue<decimal>();
var atrAvg = _atrSma.Process(atr, candle.ServerTime, true);
if (!emaValue.IsFormed || !rsiValue.IsFormed || !volumeAvg.IsFormed || !atrAvg.IsFormed)
return;
if (entryUpper is not decimal upper || entryLower is not decimal lower || exitUpper is not decimal exitHigh || exitLower is not decimal exitLow)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var ema = emaValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var filtersOk = atr > atrAvg.GetValue<decimal>() && candle.TotalVolume > volumeAvg.GetValue<decimal>();
var longSignal = close > upper && close > ema && rsi > 50m && filtersOk;
var shortSignal = close < lower && close < ema && rsi < 50m && filtersOk;
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = AtrMultiplier > 0m ? close - atr * AtrMultiplier : null;
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = AtrMultiplier > 0m ? close + atr * AtrMultiplier : null;
}
else if (Position > 0 && (close < exitLow || (_stopPrice is decimal longStop && candle.LowPrice <= longStop)))
{
SellMarket(Position);
_stopPrice = null;
}
else if (Position < 0 && (close > exitHigh || (_stopPrice is decimal shortStop && candle.HighPrice >= shortStop)))
{
BuyMarket(-Position);
_stopPrice = null;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels, ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
RSI_LENGTH = 14
class donchian_breakout_strategy(Strategy):
"""
Donchian breakout strategy with trend, volatility and volume filters.
A close above the EntryLength channel of the previous candles goes long when the close is above the EMA, RSI is above 50,
ATR is above its AtrSmaLength average and volume is above its VolumeSmaLength average; a close below the channel goes short
with the mirrored trend filters. An opposite entry breakout reverses the position.
A long also exits on a close below the ExitLength channel or at an ATR stop of AtrMultiplier times ATR from the entry; shorts mirror this.
"""
def __init__(self):
super(donchian_breakout_strategy, self).__init__()
self._entry_length = self.Param("EntryLength", 20).SetGreaterThanZero().SetDisplay("Entry Length", "Candles of the entry channel", "Donchian")
self._exit_length = self.Param("ExitLength", 10).SetGreaterThanZero().SetDisplay("Exit Length", "Candles of the exit channel", "Donchian")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR length", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5).SetNotNegative().SetDisplay("ATR Multiplier", "ATR multiplier of the stop", "Risk")
self._ema_length = self.Param("EmaLength", 50).SetGreaterThanZero().SetDisplay("EMA Length", "Trend EMA length", "Filters")
self._volume_sma_length = self.Param("VolumeSmaLength", 20).SetGreaterThanZero().SetDisplay("Volume SMA Length", "Volume average length", "Filters")
self._atr_sma_length = self.Param("AtrSmaLength", 20).SetGreaterThanZero().SetDisplay("ATR SMA Length", "ATR average length", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
self._atr_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._prev_entry_upper = None
self._prev_entry_lower = None
self._prev_exit_upper = None
self._prev_exit_lower = None
self._stop_price = None
def OnReseted(self):
super(donchian_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(donchian_breakout_strategy, self).OnStarted2(time)
self._reset_state()
entry_channel = DonchianChannels()
entry_channel.Length = self._entry_length.Value
exit_channel = DonchianChannels()
exit_channel.Length = self._exit_length.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = RSI_LENGTH
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_sma_length.Value
self._atr_sma = SimpleMovingAverage()
self._atr_sma.Length = self._atr_sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(entry_channel, exit_channel, ema, rsi, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, entry_channel)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, entry_value, exit_value, ema_value, rsi_value, atr_value):
if candle.State != CandleStates.Finished:
return
# Channels are measured on the candles before this one.
entry_upper = self._prev_entry_upper
entry_lower = self._prev_entry_lower
exit_upper = self._prev_exit_upper
exit_lower = self._prev_exit_lower
if entry_value.IsFormed and entry_value.UpperBand is not None and entry_value.LowerBand is not None:
self._prev_entry_upper = float(entry_value.UpperBand)
self._prev_entry_lower = float(entry_value.LowerBand)
if exit_value.IsFormed and exit_value.UpperBand is not None and exit_value.LowerBand is not None:
self._prev_exit_upper = float(exit_value.UpperBand)
self._prev_exit_lower = float(exit_value.LowerBand)
volume_avg = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True)
if not atr_value.IsFormed:
return
atr_dec = atr_value.GetValue[Decimal](None)
atr_avg = process_float(self._atr_sma, atr_dec, candle.ServerTime, True)
if not ema_value.IsFormed or not rsi_value.IsFormed or not volume_avg.IsFormed or not atr_avg.IsFormed:
return
if entry_upper is None or entry_lower is None or exit_upper is None or exit_lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
atr = float(atr_dec)
ema = float(ema_value.GetValue[Decimal](None))
rsi = float(rsi_value.GetValue[Decimal](None))
atr_mult = float(self._atr_multiplier.Value)
filters_ok = atr > float(atr_avg.GetValue[Decimal](None)) and float(candle.TotalVolume) > float(volume_avg.GetValue[Decimal](None))
long_signal = close > entry_upper and close > ema and rsi > 50 and filters_ok
short_signal = close < entry_lower and close < ema and rsi < 50 and filters_ok
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - atr * atr_mult if atr_mult > 0 else None
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + atr * atr_mult if atr_mult > 0 else None
elif self.Position > 0 and (close < exit_lower or (self._stop_price is not None and float(candle.LowPrice) <= self._stop_price)):
self.SellMarket(self.Position)
self._stop_price = None
elif self.Position < 0 and (close > exit_upper or (self._stop_price is not None and float(candle.HighPrice) >= self._stop_price)):
self.BuyMarket(-self.Position)
self._stop_price = None
def CreateClone(self):
return donchian_breakout_strategy()