Открыть на GitHub

Trade Channel

Обзор

Trade Channel — это стратегия возврата в диапазон, портированная с советника MetaTrader «TradeChannel». Она строит ценовой канал по максимумам и минимумам последних завершённых свечей. Когда границы канала перестают обновляться, а цена повторно тестирует одну из них, стратегия открывает позицию в противоположную сторону, ожидая возврата внутрь диапазона.

Ключевые идеи

  • Используются индикаторы Highest и Lowest для построения канала, похожего на Donchian Channel.
  • Торговля разрешена только при «плоском» канале, то есть когда не появилось новых максимумов или минимумов.
  • При касании сопротивления открывается короткая позиция, при касании поддержки — длинная.
  • Стоп-лосс устанавливается на расстоянии одного ATR от точки пробоя.
  • При желании стоп можно сопровождать, когда сделка переходит в прибыль.

Параметры

Имя Описание Значение по умолчанию Оптимизация
Volume Объём сделки в лотах/контрактах. 1 Включена (0.1 → 2.0, шаг 0.1)
ChannelLength Количество завершённых свечей для расчёта границ канала. 20 Включена (10 → 60, шаг 5)
AtrPeriod Период ATR для расчёта защитного стопа. 4 Включена (2 → 20, шаг 2)
TrailingPoints Отступ трейлинг-стопа в шагах цены инструмента. 0 — отключить сопровождение. 30 Включена (0 → 100, шаг 10)
CandleType Тип и таймфрейм свечей для расчётов. 30-минутные свечи

Логика торговли

  1. Подписаться на выбранную свечную серию и передавать данные индикаторам Highest, Lowest и ATR.
  2. Дождаться формирования всех индикаторов. Первые доступные значения лишь инициализируют канал, сделки на этой свече не совершаются.
  3. Для каждой новой завершённой свечи:
    • Обновить границы канала и рассчитать пивот (сопротивление + поддержка + закрытие) / 3.
    • Проверить, изменилась ли граница канала по сравнению с прошлой свечой. Плоское сопротивление даёт право искать шорты, плоская поддержка — лонги.
    • Шорт: сопротивление плоское, а свеча либо касается верхней границы, либо закрывается между пивотом и сопротивлением.
    • Лонг: поддержка плоская, а свеча либо касается нижней границы, либо закрывается между поддержкой и пивотом.
    • Одновременно может быть только одна позиция; пока она открыта, новые сигналы игнорируются.
  4. После входа:
    • Сохранить цену входа.
    • Установить стоп: для шорта сопротивление + ATR, для лонга поддержка − ATR.
  5. Управление позицией:
    • Выход из лонга:
      • Цена касается верхней границы при неизменном сопротивлении;
      • Минимум свечи пробивает текущий (первоначальный или трейлинг) стоп.
    • Выход из шорта:
      • Цена касается нижней границы при неизменной поддержке;
      • Максимум свечи пробивает текущий стоп.
  6. Трейлинг-стоп (если TrailingPoints > 0):
    • Перевести параметр в денежные единицы через Security.Step; при отсутствии данных используется сам параметр.
    • Для лонга, когда закрытие превышает цену входа на величину отступа, перенести стоп на закрытие − отступ.
    • Для шорта, когда закрытие падает ниже цены входа на величину отступа, опустить стоп на закрытие + отступ.
    • Стоп двигается только в сторону уменьшения риска и никогда не откатывается назад.

Примечания

  • Все решения принимаются по завершённым свечам, что соответствует оригинальному MQL-коду, использовавшему High[1], Low[1] и Close[1].
  • Сравнение текущей и предыдущей границы выполняется с допуском, зависящим от шага цены, чтобы избежать проблем с точностью чисел с плавающей запятой.
  • Если Security.Step неизвестен, для трейлинга используется исходное значение TrailingPoints.
  • Отправка писем и динамический расчёт объёма из оригинальной версии не перенесены, так как зависят от инфраструктуры MetaTrader.
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;
	}
}