Открыть на GitHub

Стратегия ABE BE CCI Engulfing

Данный проект переносит эксперта MetaTrader 5 Expert_ABE_BE_CCI (папка MQL/306) в экосистему StockSharp. Исходный робот использует свечные модели «Bullish/Bearish Engulfing», подтверждённые индикатором Commodity Channel Index (CCI), и торгует фиксированным объёмом. C# версия полностью воспроизводит логику принятия решений, но реализована через высокоуровневые подписки и индикаторы StockSharp, поэтому код становится компактнее и прозрачнее.

Алгоритм обрабатывает только завершённые свечи выбранного таймфрейма. Для каждой новой свечи рассчитывается скользящее среднее величины тела (BodyAveragePeriod) и среднее закрытия, а также CCI с периодом CciPeriod. Паттерн поглощения считается валидным, если тело текущей свечи превышает средний размер, её закрытие выходит за пределы открытия предыдущей свечи, а средняя точка поглощённой свечи расположена по правильную сторону от среднего закрытия — это повторяет проверки класса CCandlePattern из MQL. В лонг входим при бычьем поглощении и CCI ниже уровня перепроданности EntryOversoldLevel, в шорт — при медвежьем поглощении и CCI выше EntryOverboughtLevel. Выходы из позиций соответствуют 40-балльным «голосам» оригинального фильтра: любое пересечение CCI через ±ExitLevel закрывает открытую позицию.

Как работает стратегия

  1. Подписка на свечи CandleType и одновременный расчёт:
    • среднего размера тела по BodyAveragePeriod последним барам;
    • среднего значения цены закрытия за тот же период;
    • индикатора CCI длиной CciPeriod.
  2. При формировании новой завершённой свечи:
    • проверяется, что предыдущая свеча противоположного цвета и её тело полностью «поглощено» текущей свечой;
    • убедиться, что тело поглощения больше среднего и закрытие выше (ниже) открытия предыдущей свечи;
    • оценить контекст тренда по положению средней точки предыдущей свечи относительно среднего закрытия;
    • подтвердить импульс по CCI и соответствующим порогам.
  3. Управление сделками:
    • при бычьем наборе условий закрываются шорты и открывается лонг указанного объёма;
    • при медвежьем наборе закрываются лонги и открывается шорт;
    • CCI контролирует выход: пересечение сверху вниз +ExitLevel или опускание ниже -ExitLevel закрывает лонги, пересечение снизу вверх -ExitLevel или падение ниже +ExitLevel — шорты.

Параметры по умолчанию

Параметр Значение Описание
CciPeriod 49 Период индикатора Commodity Channel Index.
BodyAveragePeriod 11 Длина окна для среднего размера свечного тела и среднего закрытия.
EntryOversoldLevel -50 Порог перепроданности для подтверждения бычьего поглощения.
EntryOverboughtLevel 50 Порог перекупленности для подтверждения медвежьего поглощения.
ExitLevel 80 Абсолютное значение CCI, при пересечении которого позиция закрывается.
CandleType 1 час Таймфрейм свечей, используемый в стратегии.

Дополнительно

  • Управление объёмом идентично оригиналу: базовый размер задаётся параметром Volume, а при развороте противоположная позиция закрывается автоматически.
  • Компоненты MQL (TrailingNone, MoneyFixedLot) не переносились — их функции покрывает стандартная логика StockSharp.
  • Комментарии в коде написаны на английском, отступы выполнены табуляцией, индикаторные значения берутся из Bind, без вызовов GetValue, как требует репозиторий.
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>
/// ABE BE CCI strategy: Engulfing pattern with CCI confirmation.
/// Bullish engulfing + negative CCI for long, bearish engulfing + positive CCI for short.
/// </summary>
public class AbeBeCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _entryLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevCci;
	private bool _hasPrevCci;
	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 EntryLevel { get => _entryLevel.Value; set => _entryLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AbeBeCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_entryLevel = Param(nameof(EntryLevel), 100m)
			.SetDisplay("Entry Level", "CCI threshold for entry", "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();
		_prevCci = 0m;
		_hasPrevCci = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevCci = false;
		_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++;

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

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

			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 && cciValue < -EntryLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulfing && cciValue > EntryLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		if (_hasPrevCci)
		{
			if (Position > 0 && _prevCci > EntryLevel && cciValue < EntryLevel && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevCci < -EntryLevel && cciValue > -EntryLevel && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevCci = cciValue;
		_hasPrevCci = true;
	}
}