Открыть на GitHub

Стратегия Engulfing MFI Confirmation

Стратегия воспроизводит логику советника MetaTrader "Expert_ABE_BE_MFI". Торговые сигналы формируются при появлении свечного паттерна поглощения, подтверждённого индикатором Money Flow Index (MFI). Покупка открывается, когда бычье поглощение сопровождается нахождением MFI в зоне перепроданности. Продажа открывается, когда медвежье поглощение возникает при MFI в зоне перекупленности. Закрытие позиций осуществляется при пересечении ключевых уровней MFI, что указывает на смену импульса.

Основная идея

  1. Обнаружение паттерна – тело текущей завершённой свечи должно полностью поглощать тело предыдущей свечи в направлении сделки.
  2. Подтверждение объёмом – индикатор MFI (период настраивается, по умолчанию 37) должен быть ниже уровня перепроданности (40) для покупок или выше уровня перекупленности (60) для продаж.
  3. Выход по импульсу – открытые позиции закрываются при пересечении уровня 30 или 70 индикатором MFI в противоположную сторону, что имитирует «систему голосов» оригинального советника.

Индикаторы

  • Money Flow Index (MFI) – вычисляет объёмный импульс. Стратегия хранит два последних значения MFI для определения пересечений уровней.
  • Анализ свечных тел – дополнительные индикаторы не используются; проверка поглощения выполняется по двум последним свечам.

Правила торговли

Вход в лонг

  • Предыдущая свеча – медвежья, текущая – бычья.
  • Тело текущей свечи открывается не выше закрытия предыдущей и закрывается не ниже открытия предыдущей свечи (строгое поглощение).
  • Текущее значение MFI ниже параметра OversoldLevel (по умолчанию 40).

Вход в шорт

  • Предыдущая свеча – бычья, текущая – медвежья.
  • Тело текущей свечи открывается не ниже закрытия предыдущей и закрывается не выше её открытия.
  • Текущее значение MFI выше параметра OverboughtLevel (по умолчанию 60).

Условия выхода

  • Закрыть шорт, если MFI пересёк снизу вверх уровень ExitLongLevel (30) или ExitShortLevel (70).
  • Закрыть лонг, если MFI пересёк сверху вниз уровень ExitShortLevel (70) или ExitLongLevel (30).

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

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

  • Для входа и выхода используются рыночные ордера (BuyMarket / SellMarket).
  • Стоп-лоссы и тейк-профиты не применяются; управление риском возлагается на сигналы разворота MFI.

Параметры

Имя Описание Значение по умолчанию Диапазон / примечания
CandleType Тип/таймфрейм свечей для расчётов. 1 минута Любой поддерживаемый тип свечей.
MfiPeriod Период индикатора MFI. 37 Должен быть > 0; соответствует настройке в MQL.
OversoldLevel Уровень MFI для подтверждения бычьего сигнала. 40 При необходимости включите оптимизацию.
OverboughtLevel Уровень MFI для подтверждения медвежьего сигнала. 60 При необходимости включите оптимизацию.
ExitLongLevel Нижний уровень MFI для фиксации прибыли. 30 Используется при закрытии лонга и подтверждении шорта.
ExitShortLevel Верхний уровень MFI для фиксации прибыли. 70 Используется при закрытии шорта и подтверждении лонга.

Особенности конверсии

  • Оригинальный советник суммировал «голоса» от паттернов и фильтров. В C# логика преобразована в явные условия входа и выхода.
  • Модули мани-менеджмента и трейлинг-стопа из MQL не перенесены; объём задаётся параметром Volume стратегии StockSharp.
  • Используется высокоуровневый API (SubscribeCandles().Bind(...)) в соответствии с требованиями.

Рекомендации по применению

  • Оптимизируйте MfiPeriod, OversoldLevel и OverboughtLevel для конкретного инструмента.
  • При необходимости дополните стратегию защитными стопами через StartProtection в хост-приложении.
  • Перед запуском убедитесь, что истории достаточно для формирования MFI.
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Engulfing MFI Confirmation strategy: Engulfing pattern with MFI filter.
/// Bullish engulfing + oversold MFI for long, bearish engulfing + overbought MFI for short.
/// </summary>
public class EngulfingMfiConfirmationStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevMfi;
	private bool _hasPrevMfi;
	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 Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public EngulfingMfiConfirmationStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "Money Flow Index period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "MFI oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "MFI overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_prevMfi = 0m;
		_hasPrevMfi = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevMfi = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var mfi = new MoneyFlowIndex { Length = MfiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(mfi, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		_candles.Add(candle);
		if (_candles.Count > 5)
			_candles.RemoveAt(0);

		if (_candles.Count >= 2)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			if (curr is null || prev is null)
				return;

			var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice <= prev.ClosePrice
				&& curr.ClosePrice >= prev.OpenPrice;

			var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice >= prev.ClosePrice
				&& curr.ClosePrice <= prev.OpenPrice;

			if (bullishEngulfing && mfiValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulfing && mfiValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		// Exit on MFI crossing
		if (_hasPrevMfi)
		{
			if (Position > 0 && _prevMfi >= Overbought && mfiValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevMfi <= Oversold && mfiValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMfi = mfiValue;
		_hasPrevMfi = true;
	}
}