Открыть на GitHub

Стратегия «Head and Shoulders»

Общее описание

Head and Shoulders Strategy — адаптация советника MetaTrader «HEAD AND SHOULDERS» (MQL ID 26066). Исходный робот определял фигуру «голова и плечи», фильтровал сигналы с помощью индикаторов Momentum, MA и MACD, а также управлял позициями посредством трейлинг-стопа, контроля по капиталу и переноса стопа в безубыток. Версия на StockSharp реализует основной торговый алгоритм на высокоуровневом API, используя привязку индикаторов и сервис StartProtection для автоматического управления рисками.

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

  1. Поиск паттерна
    • Пятисвечное окно формирует массивы фрактальных максимумов и минимумов — аналогично исходному MQL-алгоритму.
    • Медвежий паттерн фиксируется при появлении трёх последовательных фрактальных максимумов, когда средний максимум (голова) выше обоих плеч на заданный процент.
    • Бычий (перевёрнутый) паттерн выявляется по трём фрактальным минимумам, где центральное значение заметно ниже плеч.
    • Линия шеи рассчитывается как среднее ближайших фрактальных минимумов (для продажи) или максимумов (для покупки) между плечами и головой.
  2. Фильтры тренда и импульса
    • Быстрая и медленная простые скользящие средние должны подтверждать направление сделки.
    • Абсолютное значение Momentum обязано превышать порог и совпадать по знаку с предполагаемым входом.
    • Значение MACD также должно поддерживать пробой, чтобы отсеять контртрендовые сигналы.
  3. Исполнение пробоя
    • Покупка открывается, когда закрытие свечи пробивает вверх линию шеи перевёрнутого паттерна и все фильтры согласованы.
    • Продажа выполняется при пробое вниз линии шеи классического паттерна при выполнении фильтров.
  4. Сопровождение позиции
    • Выход инициируется, если цена возвращается за линию шеи либо если фильтры тренда и MACD теряют согласованность.
    • Встроенный StartProtection позволяет задать стоп-лосс, тейк-профит и трейлинг-стоп в шагах цены; нулевое значение отключает компонент.

Параметры

Параметр Значение по умолчанию Описание
CandleType таймфрейм 1 час Основная серия свечей для анализа паттернов.
OrderVolume 1 Базовый объём заявки.
FastMaLength / SlowMaLength 6 / 85 Периоды быстрых и медленных SMA.
MomentumPeriod 14 Длина окна для Momentum.
MomentumThreshold 0.3 Минимальный модуль значения Momentum.
MacdFastLength, MacdSlowLength, MacdSignalLength 12, 26, 9 Настройки MACD.
ShoulderTolerancePercent 5 Допустимая асимметрия плеч в процентах.
HeadDominancePercent 2 Минимальный перевес головы над плечами.
StopLossSteps, TakeProfitSteps, TrailingStopSteps 100, 200, 0 Размеры защитных приказов в шагах цены (0 — выключено).

Все параметры создаются через Param(), снабжены метаданными для интерфейса и доступны для оптимизации в StockSharp.

Отличия от оригинального советника

  • Отказ от платформенных функций MQL (equity stop, ручные модификации заявок) в пользу стандартного StartProtection.
  • Работа исключительно с рыночными ордерами и высокоуровневыми методами BuyMarket/SellMarket.
  • Исключены графические объекты, уведомления и другая инфраструктурная логика — события выводятся через LogInfo.
  • Фрактальный алгоритм полностью переписан под C#, но сохраняет концепцию «три вершины/впадины + линия шеи».

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

  • Стратегия обрабатывает только завершённые свечи (CandleStates.Finished), поэтому убедитесь, что подписка поставляет закрытые бары.
  • При использовании трейлинг-стопа и защитных ордеров убедитесь, что Security.PriceStep соответствует минимальному тик-сайзу инструмента.
  • Для экономии памяти хранятся только последние фрактальные точки, что делает стратегию пригодной для длительной работы.
  • При необходимости добавить фильтрацию по старшим таймфреймам можно подключить дополнительные подписки и индикаторы по аналогии с текущей реализацией.

Источники

using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Head and Shoulders strategy: EMA crossover + RSI confirmation.
/// Buys when fast EMA crosses above slow EMA and RSI &lt; 55.
/// Sells when fast EMA crosses below slow EMA and RSI &gt; 45.
/// </summary>
public class HeadAndShouldersStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public HeadAndShouldersStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
	}

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

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		decimal? prevFast = null;
		decimal? prevSlow = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, rsi, (candle, fastVal, slowVal, rsiVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				if (prevFast.HasValue && prevSlow.HasValue)
				{
					var crossUp = prevFast.Value <= prevSlow.Value && fastVal > slowVal;
					var crossDown = prevFast.Value >= prevSlow.Value && fastVal < slowVal;

					if (crossUp && rsiVal < 55m && Position <= 0)
						BuyMarket();
					else if (crossDown && rsiVal > 45m && Position >= 0)
						SellMarket();
				}

				prevFast = fastVal;
				prevSlow = slowVal;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}
}