Открыть на GitHub

Стратегия AH HM MFI

Краткое описание

Стратегия AH HM MFI торгует свечными моделями «молот» и «повешенный», подтверждёнными индикатором Money Flow Index (MFI). Если после локального нисходящего движения появляется «молот», а MFI остаётся ниже порога перепроданности, открывается длинная позиция. Когда в восходящем тренде формируется «повешенный» и MFI превышает порог перекупленности, стратегия открывает короткую позицию. Выходы выполняются при пересечении MFI заранее заданных границ.

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

  1. Подписка на свечи выбранного таймфрейма и расчёт двух индикаторов:
    • Money Flow Index с настраиваемым периодом (по умолчанию 47).
    • Простая скользящая средняя по ценам закрытия для оценки тренда (период по умолчанию 5).
  2. Поиск моделей молот и повешенный:
    • Тело свечи расположено в верхней трети диапазона.
    • Длинная нижняя тень относительно тела.
    • Разрыв в сторону тренда относительно предыдущей свечи.
    • Подтверждение тренда через сравнение средней точки предыдущей свечи и значения скользящей средней.
  3. Подтверждение входов через уровни MFI:
    • Покупка, если найден «молот» и MFI не превышает порог перепроданности (по умолчанию 40).
    • Продажа, если обнаружен «повешенный» и MFI не опускается ниже порога перекупленности (по умолчанию 60).
  4. Управление выходами по пересечениям MFI:
    • Короткие позиции закрываются при пробое MFI вверх выше нижней или верхней границ (по умолчанию 30 и 70).
    • Длинные позиции закрываются при пробое MFI вверх выше верхней границы или при падении ниже нижней.
  5. Запуск встроенной защиты StartProtection для обработки аварийных ситуаций.

Параметры

Имя Описание Значение по умолчанию
CandleType Тип и таймфрейм свечей для анализа паттернов. 30-минутные свечи
MfiPeriod Период расчёта индикатора MFI. 47
MaPeriod Период SMA по ценам закрытия для фильтра тренда. 5
HammerEntryThreshold Максимальное значение MFI для входа по сигналу «молот». 40
HangingEntryThreshold Минимальное значение MFI для входа по сигналу «повешенный». 60
MfiUpperExitLevel Верхняя граница MFI, пробой которой закрывает позицию. 70
MfiLowerExitLevel Нижняя граница MFI: пробой вниз закрывает лонг, пробой вверх — шорт. 30

Примечания

  • В расчёт принимаются только завершённые свечи, чтобы не реагировать на незавершённые данные.
  • Проверка паттернов требует и длинной тени, и расположения тела в верхней части свечи, что снижает количество ложных сигналов.
  • Скользящая средняя заменяет фильтр CloseAvg из оригинального советника MetaTrader 5 и обеспечивает согласованность сигналов с направлением тренда.
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Hammer/Hanging Man + MFI strategy.
/// Buys on hammer with low MFI (oversold), sells on hanging man with high MFI (overbought).
/// </summary>
public class AhHmMfiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiLow;
	private readonly StrategyParam<decimal> _mfiHigh;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
	public decimal MfiLow { get => _mfiLow.Value; set => _mfiLow.Value = value; }
	public decimal MfiHigh { get => _mfiHigh.Value; set => _mfiHigh.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AhHmMfiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "MFI period", "Indicators");
		_mfiLow = Param(nameof(MfiLow), 35m)
			.SetDisplay("MFI Low", "MFI oversold threshold for buy", "Signals");
		_mfiHigh = Param(nameof(MfiHigh), 65m)
			.SetDisplay("MFI High", "MFI overbought threshold for sell", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 8)
			.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 rsi = new RelativeStrengthIndex { Length = MfiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
	{
		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 && mfiValue < MfiLow && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (isHangingMan && mfiValue > MfiHigh && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}