Donchian Stochastic Strategy
Donchian Channel + Stochastic strategy. Strategy enters the market when the price breaks out of Donchian Channel with Stochastic confirming oversold/overbought conditions.
Testing indicates an average annual return of about 85%. It performs best in the crypto market.
Breakouts beyond the Donchian channel are confirmed with Stochastic momentum. Trades start as soon as price escapes the range and the oscillator agrees.
Useful for traders expecting immediate follow-through. An ATR multiple sets the stop.
Details
- Entry Criteria:
- Long:
Close > DonchianHigh && StochK < 20 - Short:
Close < DonchianLow && StochK > 80
- Long:
- Long/Short: Both
- Exit Criteria: Breakout failure or opposite signal
- Stops: Percent-based using
StopLossPercent - Default Values:
DonchianPeriod= 20StochPeriod= 14StochK= 3StochD= 3CandleType= TimeSpan.FromMinutes(5).TimeFrame()StopLossPercent= 2m
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: Donchian Channel, Stochastic Oscillator
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-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>
/// Donchian Channel + Stochastic strategy.
/// Strategy enters the market when the price breaks out of Donchian Channel with Stochastic confirming oversold/overbought conditions.
/// </summary>
public class DonchianStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<int> _stochD;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
// Indicators
private DonchianChannels _donchian;
private StochasticOscillator _stochastic;
private int _cooldown;
/// <summary>
/// Donchian Channel period.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Stochastic period.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Stochastic %K period.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// Stochastic %D period.
/// </summary>
public int StochD
{
get => _stochD.Value;
set => _stochD.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DonchianStochasticStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Donchian Channel lookback period", "Indicators")
.SetOptimize(10, 50, 5);
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Stochastic oscillator period", "Indicators")
.SetOptimize(5, 30, 5);
_stochK = Param(nameof(StochK), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Stochastic %K period", "Indicators")
.SetOptimize(1, 10, 1);
_stochD = Param(nameof(StochD), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "Stochastic %D period", "Indicators")
.SetOptimize(1, 10, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetRange(5, 500)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
.SetOptimize(1m, 5m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_donchian = null;
_stochastic = null;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_donchian = new DonchianChannels
{
Length = DonchianPeriod
};
_stochastic = new StochasticOscillator
{
K = { Length = StochK },
D = { Length = StochD },
};
// Enable position protection
var takeProfitUnit = new Unit(0, UnitTypes.Absolute); // No take profit - we'll exit based on strategy rules
var stopLossUnit = new Unit(StopLossPercent, UnitTypes.Percent);
StartProtection(takeProfitUnit, stopLossUnit);
// Subscribe to candles and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_donchian, _stochastic, ProcessCandle)
.Start();
// Setup chart
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _donchian);
var secondArea = CreateChartArea();
if (secondArea != null)
{
DrawIndicator(secondArea, _stochastic);
}
DrawOwnTrades(area);
}
}
private void ProcessCandle(
ICandleMessage candle,
IIndicatorValue donchianValue,
IIndicatorValue stochValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var donchianTyped = (DonchianChannelsValue)donchianValue;
if (donchianTyped.UpperBand is not decimal upperBand ||
donchianTyped.LowerBand is not decimal lowerBand ||
donchianTyped.Middle is not decimal middleBand)
{
return;
}
var stochTyped = (StochasticOscillatorValue)stochValue;
if (stochTyped.K is not decimal stochK || stochTyped.D is not decimal stochD)
{
return;
}
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Trading logic:
// Enter in trend direction with stochastic confirmation.
if (candle.ClosePrice >= middleBand && stochK > 55 && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Long entry: Price={candle.ClosePrice}, Middle Band={middleBand}, Stochastic %K={stochK}");
}
else if (candle.ClosePrice <= middleBand && stochK < 45 && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Short entry: Price={candle.ClosePrice}, Middle Band={middleBand}, Stochastic %K={stochK}");
}
// Exit long position when price falls below middle band
else if (Position > 0 && candle.ClosePrice < middleBand)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Long exit: Price={candle.ClosePrice}, Middle Band={middleBand}");
}
// Exit short position when price rises above middle band
else if (Position < 0 && candle.ClosePrice > middleBand)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Short exit: Price={candle.ClosePrice}, Middle Band={middleBand}");
}
}
}
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 DonchianChannels, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class donchian_stochastic_strategy(Strategy):
"""
Donchian Channel + Stochastic strategy.
Enters when price breaks Donchian Channel with Stochastic confirmation.
"""
def __init__(self):
super(donchian_stochastic_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 20) \
.SetDisplay("Donchian Period", "Donchian Channel lookback period", "Indicators")
self._stoch_k = self.Param("StochK", 3) \
.SetDisplay("Stochastic %K", "Stochastic %K period", "Indicators")
self._stoch_d = self.Param("StochD", 3) \
.SetDisplay("Stochastic %D", "Stochastic %D period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(donchian_stochastic_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(donchian_stochastic_strategy, self).OnStarted2(time)
donchian = DonchianChannels()
donchian.Length = self._donchian_period.Value
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_k.Value
stochastic.D.Length = self._stoch_d.Value
sl_pct = self._stop_loss_percent.Value
self.StartProtection(
Unit(0.0, UnitTypes.Absolute),
Unit(float(sl_pct), UnitTypes.Percent)
)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(donchian, stochastic, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
def _process_candle(self, candle, donchian_value, stoch_value):
if candle.State != CandleStates.Finished:
return
upper = donchian_value.UpperBand
lower = donchian_value.LowerBand
middle = donchian_value.Middle
if upper is None or lower is None or middle is None:
return
upper = float(upper)
lower = float(lower)
middle = float(middle)
stoch_k = stoch_value.K
if stoch_k is None:
return
stoch_k = float(stoch_k)
if self._cooldown > 0:
self._cooldown -= 1
return
price = float(candle.ClosePrice)
if price >= middle and stoch_k > 55 and self.Position == 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif price <= middle and stoch_k < 45 and self.Position == 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position > 0 and price < middle:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0 and price > middle:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
def CreateClone(self):
return donchian_stochastic_strategy()