Открыть на GitHub

Стратегия Hammer & Hanging Man с фильтром CCI

Стратегия переносит советник MetaTrader «AH HM CCI» на StockSharp. Она отслеживает свечные паттерны «молот» и «висячий человек», а вход подтверждает индикатор Commodity Channel Index (CCI). Дополнительная проверка отсекает слабые паттерны и помогает отрабатывать только те развороты, где импульс действительно меняется.

Обработка ведётся только по завершённым свечам. Короткая простая скользящая средняя (SMA) задаёт тренд: предыдущая свеча должна быть молотом в нисходящем тренде с перепроданным CCI для входа в лонг либо висячим человеком в восходящем тренде с перекупленным CCI для входа в шорт. Закрытия позиций происходят при пересечении CCI настроенных уровней, что повторяет «систему голосов» оригинального эксперта.

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

  1. Трендовый фильтр. Средняя точка предыдущей свечи должна располагаться ниже (для лонга) или выше (для шорта) SMA по ценам закрытия.
  2. Распознавание паттерна. Анализируется предыдущая свеча:
    • Тело полностью находится в верхней трети диапазона свечи.
    • Есть ценовой разрыв между открытиями/закрытиями двух последних свечей.
    • Контекст соответствует типу паттерна (молот внизу тренда, висячий человек вверху).
  3. Подтверждение CCI. Значение CCI на предыдущей свече должно быть ниже порога для лонга или выше порога для шорта. По умолчанию используются значения 40 и 60 как в шаблоне MetaTrader.
  4. Выход из позиций. Текущие позиции закрываются, когда CCI пересекает один из уровней выхода. Пересечение снизу вверх закрывает лонг, сверху вниз — шорт.

Параметры

Имя Описание Значение по умолчанию
CandleType Тип и таймфрейм свечей для анализа. TimeSpan.FromMinutes(15)
CciPeriod Количество баров в расчёте CCI. 11
MaPeriod Период SMA для трендового фильтра. 5
LongConfirmationThreshold Максимальное значение CCI для подтверждения молота. 40
ShortConfirmationThreshold Минимальное значение CCI для подтверждения висячего человека. 60
ExitUpperThreshold Уровень CCI, при пересечении снизу которого закрывается позиция. 70
ExitLowerThreshold Дополнительный уровень CCI для раннего выхода. 30

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

Управление заявками

  • Входы выполняются рыночными ордерами объёмом Volume + |Position|, чтобы переворот выполнялся одной сделкой.
  • Выходы основаны только на пересечениях CCI — так сохраняется близость к оригинальному советнику. При необходимости можно добавить StartProtection со стопами и тейк-профитами.

Рекомендации

  • Используйте стратегию на ликвидных инструментах с чёткими хвостами свечей и ценовыми разрывами.
  • Для старших таймфреймов увеличивайте CciPeriod и MaPeriod, чтобы сгладить шум.
  • Уменьшение LongConfirmationThreshold или увеличение ShortConfirmationThreshold сократит число входов, но повысит их избирательность.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Hammer/Hanging Man + CCI strategy.
/// Buys on hammer with negative CCI, sells on hanging man with positive CCI.
/// </summary>
public class HammerHangingManCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public HammerHangingManCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_cciLevel = Param(nameof(CciLevel), 100m)
			.SetDisplay("CCI Level", "CCI threshold", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candlesSinceTrade = SignalCooldownCandles;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal cciValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var range = candle.HighPrice - candle.LowPrice;
		if (range <= 0 || body <= 0) return;

		var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
		var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;

		var isHammer = lowerShadow > body * 2.5m && upperShadow < body * 0.5m;
		var isHangingMan = upperShadow > body * 2.5m && lowerShadow < body * 0.5m;

		if (isHammer && cciValue < -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (isHangingMan && cciValue > CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}