Trade Channel Strategy
The Trade Channel Strategy trades breakouts and pullbacks around a Donchian price channel. When the upper band remains unchanged and price hits it or closes below it but above the pivot, a long position is opened. The opposite logic is used for short entries. A stop loss is placed beyond the opposite band by the ATR value. An optional trailing stop can tighten the stop as the trade moves in profit.
Parameters
ChannelPeriod— length of the Donchian channel.AtrPeriod— ATR period for stop-loss calculation.Trailing— trailing stop distance in price units (0 disables trailing).CandleType— candle type used for calculations.
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>
/// Trade Channel breakout strategy.
/// Uses Highest/Lowest channel and ATR for stop management.
/// </summary>
public class TradeChannelStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _stopPrice;
private bool _hasPrev;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TradeChannelStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Donchian channel period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR length for stop calculation", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevUpper = 0;
_prevLower = 0;
_stopPrice = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var atr = new StandardDeviation { Length = AtrPeriod };
SubscribeCandles(CandleType)
.Bind(highest, lowest, atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal upper, decimal lower, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (!_hasPrev)
{
_prevUpper = upper;
_prevLower = lower;
_hasPrev = true;
return;
}
if (atrVal <= 0)
{
_prevUpper = upper;
_prevLower = lower;
return;
}
var close = candle.ClosePrice;
// Breakout above channel => long
if (close >= _prevUpper && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_stopPrice = lower - atrVal;
}
// Breakout below channel => short
else if (close <= _prevLower && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_stopPrice = upper + atrVal;
}
// Manage long
else if (Position > 0)
{
// Trailing stop
var newStop = close - atrVal * 2;
if (newStop > _stopPrice) _stopPrice = newStop;
if (candle.LowPrice <= _stopPrice)
{
SellMarket();
_stopPrice = 0;
}
}
// Manage short
else if (Position < 0)
{
var newStop = close + atrVal * 2;
if (newStop < _stopPrice) _stopPrice = newStop;
if (candle.HighPrice >= _stopPrice)
{
BuyMarket();
_stopPrice = 0;
}
}
_prevUpper = upper;
_prevLower = lower;
}
}
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
from StockSharp.Algo.Indicators import Highest, Lowest, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class trade_channel_strategy(Strategy):
def __init__(self):
super(trade_channel_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20) \
.SetDisplay("Channel Period", "Donchian channel period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR length for stop calculation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_upper = 0.0
self._prev_lower = 0.0
self._stop_price = 0.0
self._has_prev = False
@property
def channel_period(self):
return self._channel_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(trade_channel_strategy, self).OnReseted()
self._prev_upper = 0.0
self._prev_lower = 0.0
self._stop_price = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(trade_channel_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
atr = StandardDeviation()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, atr, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, upper, lower, atr_val):
if candle.State != CandleStates.Finished:
return
if not self._has_prev:
self._prev_upper = upper
self._prev_lower = lower
self._has_prev = True
return
if atr_val <= 0:
self._prev_upper = upper
self._prev_lower = lower
return
close = candle.ClosePrice
# Breakout above channel => long
if close >= self._prev_upper and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._stop_price = lower - atr_val
# Breakout below channel => short
elif close <= self._prev_lower and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._stop_price = upper + atr_val
# Manage long
elif self.Position > 0:
# Trailing stop
new_stop = close - atr_val * 2
if new_stop > self._stop_price:
self._stop_price = new_stop
if candle.LowPrice <= self._stop_price:
self.SellMarket()
self._stop_price = 0
# Manage short
elif self.Position < 0:
new_stop = close + atr_val * 2
if new_stop < self._stop_price:
self._stop_price = new_stop
if candle.HighPrice >= self._stop_price:
self.BuyMarket()
self._stop_price = 0
self._prev_upper = upper
self._prev_lower = lower
def CreateClone(self):
return trade_channel_strategy()