Auf GitHub ansehen

Bullish & Bearish Engulfing-Strategie

Übersicht

Diese Strategie repliziert das klassische bullische und bärische Engulfing-Kerzenmuster, das ursprünglich in MetaTrader für den Expert Advisor "Bullish and Bearish Engulfing" implementiert wurde. Der StockSharp-Port bewertet abgeschlossene Kerzen in einem konfigurierbaren Zeitrahmen, überspringt optional eine Anzahl aktueller Bars und reagiert, wenn ein Engulfing-Muster einen minimalen Abstandsfilter erfüllt. Die Logik ist für diskretionäre Trader konzipiert, die ein bewährtes Price-Action-Muster automatisieren möchten, während sie die Kontrolle über Richtung, Volumen und die Behandlung bestehender Positionen behalten.

Musterdefinition

Ein Engulfing-Signal wird bestätigt, wenn zwei aufeinanderfolgende abgeschlossene Kerzen die folgenden Regeln erfüllen (nach Anwendung des konfigurierten Versatzes):

  • Bullisches Engulfing
    • Die zuletzt bewertete Kerze schließt über ihrem Eröffnungskurs (bullischer Körper).
    • Die vorhergehende Kerze schließt unter ihrem Eröffnungskurs (bärischer Körper).
    • Die bullische Kerze hat ein höheres Hoch und ein niedrigeres Tief als die vorherige Kerze, und zwar mindestens um den Abstandsfilter.
    • Der bullische Schluss liegt über dem vorherigen Eröffnungskurs und sein Eröffnungskurs liegt unter dem vorherigen Schluss, wiederum unter Einhaltung des Abstandsfilters.
  • Bärisches Engulfing
    • Die bewertete Kerze schließt unter ihrem Eröffnungskurs (bärischer Körper).
    • Die vorhergehende Kerze schließt über ihrem Eröffnungskurs (bullischer Körper).
    • Die bärische Kerze druckt noch ein höheres Hoch, schließt aber deutlich unter dem vorherigen Eröffnungskurs, und ihr Eröffnungskurs übersteigt den vorherigen Schluss, jeweils um den Abstandsfilter.
    • Das Tief des bärischen Bars liegt unter dem vorherigen Tief um den Abstandsfilter.

Diese Bedingungen reproduzieren die ursprüngliche MetaTrader-Implementierung, die forderte, dass die Engulfing-Kerze den vorherigen Körper vollständig abdeckt und über beide Extreme hinausgeht. Der Abstandsfilter wird in Pips gemessen und mit dem Preisschritt und den Dezimalstellen des Instruments in einen Preis umgerechnet (5-stellige und 3-stellige Forex-Kurse werden automatisch auf 10-Punkt-Pips skaliert).

Handelslogik

  1. Den ausgewählten Kerzentyp über die High-Level-API abonnieren und nur abgeschlossene Kerzen verarbeiten.
  2. Einen kleinen rollierenden Puffer pflegen, der die OHLC-Werte speichert, die für den aktuellen Versatzzwert erforderlich sind.
  3. Wenn mindestens zwei historische Kerzen zur Auswertung verfügbar sind, die oben beschriebenen bullischen und bärischen Engulfing-Bedingungen prüfen.
  4. Bei einem bullischen Signal eine Marktorder auf der durch BullishSide definierten Seite senden. Bei einem bärischen Signal die über BearishSide konfigurierte Seite verwenden.
  5. Wenn CloseOppositePositions aktiviert ist und ein gegenläufiges Exposure besteht, erhöht die Strategie das Ordervolumen um die absolute aktuelle Position, sodass der resultierende Trade das gegenläufige Bein schließt und ein neues in der gewünschten Richtung eröffnet. Wenn das Flag deaktiviert ist, werden Signale ignoriert, solange eine gegenläufige Position offen ist.
  6. Die Positionsgröße wird durch den Strategieparameter Volume gesteuert (Standard: 1 Kontrakt/Lot). Standardmäßig wird kein automatischer Stop-Loss oder Take-Profit angehängt; das Risikomanagement bleibt dem Endbenutzer oder Schutzmodulen überlassen (kann mit den integrierten Schutzfunktionen von StockSharp kombiniert werden).

Parameter

Parameter Beschreibung Standard Hinweise
CandleType Zeitrahmen (StockSharp DataType) zur Signalentdeckung. 1-Stunden-Zeitrahmen Anpassbar an jeden unterstützten Kerzentyp.
Shift Anzahl der abgeschlossenen Kerzen, die vor der Musterbewertung übersprungen werden sollen. 1 Wert 1 analysiert den zuletzt geschlossenen Bar; höhere Werte schauen weiter zurück.
DistanceInPips Mindestabstand in Pips, den die Engulfing-Kerze gegenüber der vorherigen überschreiten muss. 0 In Preis umgerechnet anhand des Instruments-Preisschritts; nützlich zum Herausfiltern von Kerzen mit kleinen Körpern.
CloseOppositePositions Ob eine bestehende gegenläufige Position geschlossen werden soll, wenn ein neues Signal ausgelöst wird. true Wenn deaktiviert, wird der Trade übersprungen, wenn das aktuelle Exposure mit dem Signal in Konflikt steht.
BullishSide Orderseite bei einem bullischen Engulfing-Signal. Buy Kann für konträres Verhalten auf Sell umgestellt werden.
BearishSide Orderseite bei einem bärischen Engulfing-Signal. Sell Kann auf Buy umgestellt werden, um gegen den Trend zu handeln.
Volume Basisordergröße. 1 Das Ordervolumen wird beim Schließen der Gegenseite um abs(Position) erhöht.

Positionsmanagement und Risiko

  • Da Orders ohne Schutz-Stops zum Marktpreis gesendet werden, die Strategie mit zusätzlichen Modulen (z. B. StartProtection) kombinieren oder externe Risikokontrollen konfigurieren.
  • Der originale MetaTrader-Code dimensionierte Trades über einen risikobasierten Money Manager. In diesem Port wird die Dimensionierung auf einen direkten Volumensparameter vereinfacht, damit das Verhalten innerhalb von StockSharp deterministisch ist; einen benutzerdefinierten Money-Management-Block integrieren, wenn dynamisches Sizing erforderlich ist.
  • Wenn CloseOppositePositions true ist, erfolgen Umkehrungen sofort: Das Tradevolumen entspricht dem Basisvolumen plus der absoluten offenen Position, was einen direkten Übergang von flach zur neuen Richtung garantiert.

Dateien

  • CS/BullishBearishEngulfingStrategy.cs – Haupt-C#-Implementierung, basierend auf der High-Level-StockSharp-Strategie-API.

Hinweis: Für diese ID ist keine Python-Implementierung vorgesehen; nur die C#-Version ist wie angefordert enthalten.

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Engulfing pattern strategy that reacts to bullish and bearish engulfing candles.
/// </summary>
public class BullishBearishEngulfingStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _shift;
	private readonly StrategyParam<decimal> _distanceInPips;
	private readonly StrategyParam<bool> _closeOpposite;
	private readonly StrategyParam<Sides> _bullishSide;
	private readonly StrategyParam<Sides> _bearishSide;
	private readonly List<CandleSnapshot> _candles = new();

	/// <summary>
	/// Initializes a new instance of <see cref="BullishBearishEngulfingStrategy"/>.
	/// </summary>
	public BullishBearishEngulfingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");

		_shift = Param(nameof(Shift), 1)
			.SetGreaterThanZero()
			.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
			
			.SetOptimize(1, 5, 1);

		_distanceInPips = Param(nameof(DistanceInPips), 0m)
			.SetNotNegative()
			.SetDisplay("Distance (pips)", "Additional filter expressed in pips", "Pattern")
			
			.SetOptimize(0m, 10m, 1m);

		_closeOpposite = Param(nameof(CloseOppositePositions), true)
			.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk");

		_bullishSide = Param(nameof(BullishSide), Sides.Buy)
			.SetDisplay("Bullish Action", "Order side for bullish engulfing", "Pattern");

		_bearishSide = Param(nameof(BearishSide), Sides.Sell)
			.SetDisplay("Bearish Action", "Order side for bearish engulfing", "Pattern");
	}

	/// <summary>
	/// Candle type used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Number of fully completed candles to skip before pattern evaluation.
	/// </summary>
	public int Shift
	{
		get => _shift.Value;
		set => _shift.Value = value;
	}

	/// <summary>
	/// Additional price filter expressed in pips.
	/// </summary>
	public decimal DistanceInPips
	{
		get => _distanceInPips.Value;
		set => _distanceInPips.Value = value;
	}

	/// <summary>
	/// Indicates whether opposite positions should be closed before entering a new trade.
	/// </summary>
	public bool CloseOppositePositions
	{
		get => _closeOpposite.Value;
		set => _closeOpposite.Value = value;
	}

	/// <summary>
	/// Side used when a bullish engulfing pattern appears.
	/// </summary>
	public Sides BullishSide
	{
		get => _bullishSide.Value;
		set => _bullishSide.Value = value;
	}

	/// <summary>
	/// Side used when a bearish engulfing pattern appears.
	/// </summary>
	public Sides BearishSide
	{
		get => _bearishSide.Value;
		set => _bearishSide.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_candles.Clear();

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		// no protection needed
	}

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

		var snapshot = new CandleSnapshot
		{
			Open = candle.OpenPrice,
			High = candle.HighPrice,
			Low = candle.LowPrice,
			Close = candle.ClosePrice
		};

		_candles.Add(snapshot);

		var maxCount = Math.Max(Shift + 2, 3);
		while (_candles.Count > maxCount)
			try { _candles.RemoveAt(0); } catch { break; }

		if (_candles.Count < Shift + 1)
			return;

		var candles = _candles.ToArray();
		var currentIndex = candles.Length - Shift;
		if (currentIndex <= 0)
			return;

		var previousIndex = currentIndex - 1;
		if (previousIndex < 0)
			return;

		var current = candles[currentIndex];
		var previous = candles[previousIndex];
		var distance = CalculateDistanceInPrice();

		var isBullishEngulfing = current.Close > current.Open && previous.Open > previous.Close &&
			current.High > previous.High + distance &&
			current.Close > previous.Open + distance &&
			current.Open < previous.Close - distance &&
			current.Low < previous.Low - distance;

		if (isBullishEngulfing)
		{
			HandleSignal(BullishSide);
			return;
		}

		var isBearishEngulfing = current.Open > current.Close && previous.Open < previous.Close &&
			current.High > previous.High + distance &&
			current.Open > previous.Close + distance &&
			current.Close < previous.Open - distance &&
			current.Low < previous.Low - distance;

		if (isBearishEngulfing)
			HandleSignal(BearishSide);
	}

	private void HandleSignal(Sides side)
	{
		switch (side)
		{
			case Sides.Buy:
				EnterLong();
				break;
			case Sides.Sell:
				EnterShort();
				break;
		}
	}

	private void EnterLong()
	{
		if (Position > 0)
			return;

		if (Position < 0)
		{
			if (!CloseOppositePositions)
				return;

			// Close short first
			BuyMarket();
		}

		BuyMarket();
	}

	private void EnterShort()
	{
		if (Position < 0)
			return;

		if (Position > 0)
		{
			if (!CloseOppositePositions)
				return;

			// Close long first
			SellMarket();
		}

		SellMarket();
	}

	private decimal CalculateDistanceInPrice()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep == null)
			return 0m;

		var decimals = Security?.Decimals ?? 0;
		var multiplier = decimals is 3 or 5 ? 10m : 1m;
		return DistanceInPips * priceStep.Value * multiplier;
	}

	private struct CandleSnapshot
	{
		public decimal Open;
		public decimal High;
		public decimal Low;
		public decimal Close;
	}
}