Открыть на GitHub

Стратегия Tunnel Method

Стратегия Tunnel Method — это порт оригинального советника MetaTrader 5 на платформу StockSharp. Она использует три смещённых простых скользящих средних (SMA), которые формируют «туннель» вокруг цены. Быстрая средняя должна пробить этот туннель с заданным отступом, чтобы сформировать сигнал на вход. В стратегии воспроизведены все элементы управления позицией из исходной версии: стоп-лосс и тейк-профит в пунктах, ступенчатое трейлинг-сопровождение и задержка между повторными проверками условий входа.

Логика работы

  • Индикаторы: три SMA на одном инструменте и таймфрейме.
    • Первая SMA (медленная): длинный период без смещения. Определяет нижнюю границу бычьего туннеля и верхнюю границу медвежьего туннеля.
    • Вторая SMA (средняя): средний период с положительным смещением. Создаёт опережающий барьер для коротких сигналов.
    • Третья SMA (быстрая): короткий период с максимальным смещением. Именно её прорыв через туннель даёт сигнал.
  • Отступ: линии должны быть разнесены минимум на IndentPips (переводится в цену), чтобы отсечь шум. Для покупки быстрая SMA должна подняться выше медленной на половину отступа, а для продажи — опуститься ниже средней на ту же величину.
  • Пауза между проверками: после каждой оценки условий стратегия ждёт PauseSeconds, прежде чем снова искать вход. Это повторяет ограничение частоты вызовов OnTick в MQL-версии.
  • Одна позиция: одновременно допускается только одна позиция. Пока она открыта, новые сигналы игнорируются.

Управление рисками

  • Стоп-лосс: фиксированное расстояние в пунктах ниже (для лонга) или выше (для шорта) цены входа. Настраивается параметром StopLossPips.
  • Тейк-профит: фиксированная цель в пунктах через параметр TakeProfitPips.
  • Трейлинг-стоп: активируется, если TrailingStopPips > 0 и TrailingStepPips > 0. Когда цена проходит путь TrailingStopPips + TrailingStepPips, стоп подтягивается на TrailingStopPips от текущей цены закрытия и далее двигается только при каждом новом приросте на шаг трейлинга.
  • Выход из позиции: стратегия закрывает позицию рыночными ордерами при достижении стопа, тейка или трейлинг-уровня, имитируя срабатывание защитных заявок у брокера.

Параметры

Параметр Значение по умолчанию Описание
TradeVolume 1 Объём ордера на вход.
StopLossPips 50 Расстояние стоп-лосса в пунктах. 0 — отключить.
TakeProfitPips 50 Расстояние тейк-профита в пунктах. 0 — отключить.
TrailingStopPips 5 Базовое расстояние трейлинг-стопа. Требует TrailingStepPips > 0.
TrailingStepPips 5 Минимальный прирост прибыли перед пересчётом трейлинга.
FirstMaPeriod 160 Период медленной SMA.
FirstMaShift 0 Смещение медленной SMA вперёд.
SecondMaPeriod 80 Период средней SMA.
SecondMaShift 1 Смещение средней SMA.
ThirdMaPeriod 20 Период быстрой SMA.
ThirdMaShift 2 Смещение быстрой SMA.
IndentPips 1 Минимальный зазор между линиями для подтверждения сигнала.
PauseSeconds 45 Пауза между проверками условий входа.
CandleType Таймфрейм 5 минут Свечи, используемые в расчётах индикаторов.

Все параметры в пунктах автоматически переводятся в цену с учётом PriceStep и количества знаков инструмента, включая специальную обработку трёх- и пятизначных валютных котировок, как в оригинальном советнике.

Практические рекомендации

  1. Корректные параметры инструмента. Проверьте, что у Security заполнены PriceStep и Decimals, иначе перевод пунктов в цену будет некорректным.
  2. Выбор таймфрейма. По умолчанию используется серия 5-минутных свечей, но при необходимости можно задать иной таймфрейм (например, M1), чтобы повторить конфигурацию MT5.
  3. Учёт объёма. TradeVolume задаёт полный объём позиции. Выходы выполняются симметричными рыночными заявками, поэтому объём позиции сохраняется.
  4. Требования к трейлингу. Конструктор генерирует ошибку, если включён трейлинг (TrailingStopPips > 0), но шаг (TrailingStepPips) равен нулю — это соответствует проверке из MQL.
  5. Оптимизация. Параметры оформлены через Param, поэтому их можно оптимизировать или выводить в интерфейс Designer для тонкой настройки периодов, отступа и трейлинга.

Состав набора

  • CS/TunnelMethodStrategy.cs — реализация стратегии на C#.
  • README.md — документация на английском языке.
  • README_ru.md — русская документация (этот файл).
  • README_zh.md — документация на китайском языке.

Python-версия намеренно не создавалась — по требованию задачи предоставлена только реализация на C#.

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

public class TunnelMethodStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private decimal? _prevHigh, _prevLow;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }

	public TunnelMethodStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_period = Param(nameof(Period), 18).SetGreaterThanZero().SetDisplay("Channel Period", "Lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevHigh = null;
		_prevLow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevHigh = null; _prevLow = null;
		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevHigh = high; _prevLow = low; return; }
		if (_prevHigh == null || _prevLow == null) { _prevHigh = high; _prevLow = low; return; }
		var close = candle.ClosePrice;
		if (close > _prevHigh.Value && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (close < _prevLow.Value && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevHigh = high; _prevLow = low;
	}
}