Ver en GitHub

Trade Channel ATR Strategy

The Trade Channel strategy replicates the original MetaTrader expert advisor that traded price channels with ATR-based stops. It waits for channel boundaries to remain unchanged and for the latest candle to touch or reject those levels. When the setup appears, the strategy opens a position in the opposite direction of the touch and applies an adaptive trailing stop measured in points.

The approach seeks to exploit mean-reversion around a stable price channel. It filters signals so that the channel must be flat (no new highs or lows) before it enters. Protective stops are placed beyond the channel using the Average True Range, and an optional trailing stop locks in profits once the move develops.

Details

  • Entry Criteria:
    • Short: Channel high equals the previous channel high and the latest candle either breaks that high or closes between the high and pivot (high + low + close) / 3.
    • Long: Channel low equals the previous channel low and the latest candle either breaks that low or closes between the low and pivot.
  • Long/Short: Both directions, but only one position at a time.
  • Exit Criteria:
    • Long: Price touches the channel high while the high stayed unchanged.
    • Short: Price touches the channel low while the low stayed unchanged.
    • Optional trailing stop tightens behind the market once profit exceeds TrailingDistance points.
  • Stops: Initial stop loss at channel boundary ± ATR. Trailing stop replaces it when activated.
  • Default Values:
    • Volume = 0.1m
    • ChannelPeriod = 20
    • AtrPeriod = 4
    • TrailingDistance = 30
    • CandleType = 30-minute candles
  • Filters:
    • Category: Mean Reversion
    • Direction: Both
    • Indicators: Highest, Lowest, Average True Range
    • Stops: ATR stop, Trailing
    • Complexity: Intermediate
    • Timeframe: Intraday (30 minutes)
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium

Notes

  • Volume controls the order size; only one position can exist at a time.
  • TrailingDistance is specified in points (price steps). Set to zero to disable the trailing stop.
  • The strategy requires historical candles to warm up the Highest/Lowest and ATR indicators before trading.
  • Stop orders are automatically canceled when the position closes or the strategy resets.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class TradeChannelAtrStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TradeChannelAtrStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 48).SetDisplay("Channel Period", "Channel lookback", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14).SetDisplay("ATR Period", "ATR lookback", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 150).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevMid = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevMid = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;
		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevMid = mid;
			return;
		}

		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevMid = mid;
	}
}