Auf GitHub ansehen

Trade Channel

Overview

Trade Channel is a channel-reversion strategy converted from the MetaTrader "TradeChannel" expert advisor. The system draws a price channel from the highest high and lowest low over a configurable number of completed candles. When the channel stops expanding and price retests one of its borders, the strategy opens a position in the opposite direction, expecting a reversion back inside the range.

Core ideas

  • Use the Highest and Lowest indicators to form a Donchian-like channel.
  • Require the channel to be flat (no new highs or lows) before opening a trade.
  • Fade the touch of resistance with short positions and the touch of support with long positions.
  • Place the initial protective stop one Average True Range (ATR) away from the breakout point.
  • Optionally trail the stop once the trade moves in favour of the position.

Parameters

Name Description Default Optimization
Volume Trade volume in lots/contracts. 1 Enabled (0.1 → 2.0, step 0.1)
ChannelLength Number of finished candles used to compute channel boundaries. 20 Enabled (10 → 60, step 5)
AtrPeriod Period of the ATR indicator for stop placement. 4 Enabled (2 → 20, step 2)
TrailingPoints Trailing stop offset measured in instrument price steps. Set to 0 to disable trailing. 30 Enabled (0 → 100, step 10)
CandleType Candle type and timeframe used for calculations. 30-minute time frame

Trading logic

  1. Subscribe to the configured candle series and feed three indicators: Highest, Lowest and ATR.
  2. Wait until all indicators are fully formed. The first completed values initialise the channel state and no trades are taken on that candle.
  3. For every new finished candle:
    • Update the channel boundaries and calculate the pivot (resistance + support + close) / 3.
    • Check whether the resistance (or support) is unchanged compared with the previous candle. A flat resistance allows short setups, a flat support allows long setups.
    • Short entry: if resistance is flat and the candle either touches the resistance high or closes between the pivot and the resistance, send a market sell order.
    • Long entry: if support is flat and the candle either touches the support low or closes between the support and the pivot, send a market buy order.
    • Only one position is allowed at a time. The strategy waits for the flat-channel signal while no trades are open.
  4. Upon entry:
    • Store the entry price.
    • Set the initial stop to resistance + ATR for shorts and support − ATR for longs.
  5. Manage open positions:
    • Exit conditions for longs:
      • Price touches the upper channel boundary while it remains flat.
      • Candle low crosses below the trailing/initial stop level.
    • Exit conditions for shorts:
      • Price touches the lower channel boundary while it remains flat.
      • Candle high crosses above the trailing/initial stop level.
  6. Trailing stop (if TrailingPoints > 0):
    • Convert the input into price units using the instrument's Security.Step (falls back to raw value if the step is unavailable).
    • For longs, once the close exceeds the entry price by the trailing offset, move the stop to close − offset.
    • For shorts, once the close drops below the entry price by the offset, move the stop to close + offset.
    • The trailing stop never moves backwards; it only tightens the protective level.

Notes

  • All decisions are made on finished candles to stay aligned with the original MQL logic that used High[1], Low[1] and Close[1].
  • The equality check between the current and previous channel boundary is tolerant to instrument price steps to avoid floating-point glitches.
  • Trailing stops rely on correct Security.Step metadata. If the exchange does not provide it, the raw point value is used instead.
  • The strategy does not send e-mails or adjust position sizing dynamically, because those features were platform-specific in the MQL implementation.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trade Channel Breakout strategy - Highest/Lowest channel breakout.
/// Buys when close crosses above channel midpoint.
/// Sells when close crosses below channel midpoint.
/// </summary>
public class TradeChannelBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;

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

	public TradeChannelBreakoutStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;

		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 (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
		}
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
		}

		_prevClose = close; _prevMid = mid;
	}
}