Стратегия разворота по каналу Дончиана
Каналы Дончиана отмечают недавние максимумы и минимумы за выбранный период. Когда цена пробивает эти границы и затем возвращается обратно, это может сигнализировать об истощении движения. Стратегия отслеживает закрытия, вернувшиеся внутрь канала после короткого пробоя.
Тестирование показывает среднегодичную доходность около 157%. Стратегию лучше запускать на крипторынке.
Если предыдущее закрытие было ниже нижней границы, а текущее вернулось выше неё, открывается длинная позиция. Если же прошлое закрытие было выше верхней границы и цена вернулась внутрь, открывается короткая позиция. В обоих случаях риск управляется процентным стопом.
Торгуя только после несостоявшегося пробоя, метод старается ловить ложные движения, которые быстро откатываются.
Детали
- Условия входа: цена закрывается обратно в канал Дончиана после выхода за верхнюю или нижнюю границу.
- Длинные/короткие: обе стороны.
- Условия выхода: стоп‑лосс.
- Стопы: да, процентные.
- Значения по умолчанию:
Period= 20StopLossPercent= 2CandleType= 15 минут
- Фильтры:
- Категория: разворот
- Направление: оба
- Индикаторы: канал Дончиана
- Стопы: да
- Сложность: базовая
- Таймфрейм: внутридневной
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Donchian Reversal strategy.
/// A breakout is a close beyond the channel of the Period candles before it. When the next close returns above the lower band
/// that was broken the position turns long, and when it returns below a broken upper band it turns short. A percent stop limits the loss.
/// </summary>
public class DonchianReversalStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal High, decimal Low)> _candles = [];
// The previous close and the channel it was compared with.
private (decimal Close, decimal Upper, decimal Lower)? _previous;
/// <summary>
/// Donchian Channel period.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DonchianReversalStrategy()
{
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Donchian Channel period", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_candles.Clear();
_previous = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_previous = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var previous = _previous;
// The channel of the candles before this one, for the next candle to compare with.
_previous = _candles.Count == Period
? (close, _candles.Max(c => c.High), _candles.Min(c => c.Low))
: null;
_candles.Add((candle.HighPrice, candle.LowPrice));
if (_candles.Count > Period)
_candles.RemoveAt(0);
if (previous is not { } prior || !IsFormedAndOnlineAndAllowTrading())
return;
if (prior.Close < prior.Lower && close > prior.Lower && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (prior.Close > prior.Upper && close < prior.Upper && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class donchian_reversal_strategy(Strategy):
"""
Donchian Reversal strategy.
A breakout is a close beyond the channel of the Period candles before it. When the next close returns above the lower band
that was broken the position turns long, and when it returns below a broken upper band it turns short. A percent stop limits the loss.
"""
def __init__(self):
super(donchian_reversal_strategy, self).__init__()
self._period = self.Param("Period", 20).SetGreaterThanZero().SetDisplay("Period", "Donchian Channel period", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._candles = []
# The previous close and the channel it was compared with.
self._previous = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(donchian_reversal_strategy, self).OnReseted()
self._candles = []
self._previous = None
def OnStarted2(self, time):
super(donchian_reversal_strategy, self).OnStarted2(time)
self._candles = []
self._previous = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
previous = self._previous
period = self._period.Value
# The channel of the candles before this one, for the next candle to compare with.
if len(self._candles) == period:
self._previous = (close, max(c[0] for c in self._candles), min(c[1] for c in self._candles))
else:
self._previous = None
self._candles.append((candle.HighPrice, candle.LowPrice))
if len(self._candles) > period:
self._candles.pop(0)
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
prior_close, prior_upper, prior_lower = previous
if prior_close < prior_lower and close > prior_lower and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif prior_close > prior_upper and close < prior_upper and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return donchian_reversal_strategy()