Открыть на GitHub

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

Стратегия переносит советник MetaTrader Expert_ABE_BE_Stoch на высокоуровневый API StockSharp. Она сочетает анализ японских свечей и осциллятора Стохастик, чтобы ловить развороты в зонах перепроданности и перекупленности. Основной сигнал формируется, когда бычье поглощение подтверждается глубоким значением %D ниже порога перепроданности, либо медвежье поглощение поддерживается %D выше порога перекупленности. Управление позицией осуществляется через пересечения уровней 20 и 80 Стохастика, что воспроизводит систему «голосов» оригинального эксперта.

Алгоритм может открывать как длинные, так и короткие позиции. Он анализирует только завершённые свечи, избегая шума внутри бара. Размер позиции задаётся свойством Volume, а опциональные стоп-лосс и тейк-профит преобразуют исходные настройки в пунктах в объекты Unit с типом UnitTypes.Price.

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

  1. Подписка на данные – создаётся подписка на выбранный таймфрейм и инициализируется StochasticOscillator с параметрами %K, %D и замедлением.
  2. Распознавание паттерна – на каждой закрытой свече проверяется, поглощает ли её тело тело предыдущей свечи. Вспомогательные методы воспроизводят определения бычьего и медвежьего поглощения из MetaTrader.
  3. Подтверждение импульса – линия %D служит фильтром подтверждения: значения ниже порога перепроданности (по умолчанию 30) обязательны для бычьего сигнала, а значения выше порога перекупленности (по умолчанию 70) – для медвежьего.
  4. Управление позицией – кэшируется предыдущее значение %D. Если новая точка пересекает вверх уровень 20 или 80, все короткие позиции закрываются. При пересечении вниз тех же уровней закрываются длинные позиции. Это повторяет добавочные «голоса» на закрытие из MQL-реализации.
  5. Контроль риска – при ненулевых значениях стопа или тейка (в шагах цены) они переводятся в Unit и передаются в StartProtection. Если расстояния равны нулю, вызывается StartProtection() без параметров.

Торговые правила

  • Вход в лонг: предыдущая свеча медвежья, текущая бычья, её тело полностью покрывает тело предыдущей свечи, а %D ниже EntryOversoldLevel (30 по умолчанию). Любой шорт закрывается и открывается новая длинная позиция через BuyMarket.
  • Вход в шорт: предыдущая свеча бычья, текущая медвежья, её тело поглощает предыдущее, а %D выше EntryOverboughtLevel (70 по умолчанию). Любая длинная позиция закрывается и открывается новый шорт через SellMarket.
  • Выход из лонга: при открытом лонге пересечение %D вниз через ExitUpperLevel (80 по умолчанию) или ExitLowerLevel (20) приводит к продаже всей позиции.
  • Выход из шорта: при открытом шорте пересечение %D вверх через ExitLowerLevel или ExitUpperLevel закрывает позицию покупкой.
  • Стопы/тейки: параметры StopLossPoints и TakeProfitPoints задают расстояние в шагах цены. Значение 0 отключает соответствующую защиту.

Параметры

Имя Тип Значение по умолчанию Описание
CandleType DataType TimeSpan.FromHours(1).TimeFrame() Источник свечей для анализа паттернов.
StochasticPeriodK int 47 Период расчёта быстрой линии %K.
StochasticPeriodD int 9 Период сглаживания линии %D.
StochasticPeriodSlow int 13 Дополнительное сглаживание %K перед построением %D.
EntryOversoldLevel decimal 30 Верхний предел %D для допуска бычьего поглощения.
EntryOverboughtLevel decimal 70 Нижний предел %D для допуска медвежьего поглощения.
ExitLowerLevel decimal 20 Уровень, пересечение вверх которого закрывает шорты, а вниз — лонги.
ExitUpperLevel decimal 80 Уровень перекупленности, используемый аналогично ExitLowerLevel.
TakeProfitPoints decimal 0 Дистанция до тейк-профита в шагах цены (0 отключает).
StopLossPoints decimal 0 Дистанция до стоп-лосса в шагах цены (0 отключает).

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

  • По умолчанию используется часовой таймфрейм, но стратегия применима к любым OHLC-данным.
  • Расчёты выполняются только на закрытых свечах для соответствия логике оригинального эксперта.
  • Размер позиции задаётся пользователем через Volume или внешние механизмы управления капиталом.
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 Stoch strategy: Engulfing pattern with Stochastic confirmation.
/// Bullish engulfing + oversold stochastic for long, bearish engulfing + overbought for short.
/// </summary>
public class AbeBeStochStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevK;
	private bool _hasPrevK;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.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 AbeBeStochStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "Stochastic 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();
		_prevK = 0m;
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(stoch, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var stochTyped = stochValue as StochasticOscillatorValue;
		if (stochTyped?.K is not decimal kValue) return;

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

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

			// Bullish engulfing: prev bearish, curr bullish, curr body engulfs prev body
			var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice <= prev.ClosePrice
				&& curr.ClosePrice >= prev.OpenPrice;

			// Bearish engulfing: prev bullish, curr bearish, curr body engulfs prev body
			var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice >= prev.ClosePrice
				&& curr.ClosePrice <= prev.OpenPrice;

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

		// Exit on stochastic cross
		if (_hasPrevK)
		{
			if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevK = kValue;
		_hasPrevK = true;
	}
}