Ver en GitHub

Estrategia de Alcista y Bajista Engulfing

Descripción general

Esta estrategia replica la configuración clásica de velas alcistas y bajistas de tipo engulfing que originalmente se implementó en MetaTrader para el asesor experto "Bullish and Bearish Engulfing". El port de StockSharp evalúa velas completadas en un marco temporal configurable, opcionalmente omite un número de barras recientes y reacciona cuando un patrón engulfing cumple un filtro de distancia mínima. La lógica está diseñada para traders discrecionales que desean automatizar un patrón de acción del precio bien establecido manteniendo el control sobre la dirección, el volumen y la gestión de posiciones existentes.

Definición del patrón

Una señal engulfing se confirma cuando dos velas completadas consecutivas cumplen las siguientes reglas (después de aplicar el desplazamiento configurado):

  • Alcista engulfing
    • La vela evaluada más reciente cierra por encima de su apertura (cuerpo alcista).
    • La vela anterior cierra por debajo de su apertura (cuerpo bajista).
    • La vela alcista tiene un máximo más alto y un mínimo más bajo que la vela anterior al menos por la distancia del filtro.
    • El cierre alcista termina por encima de la apertura anterior y su apertura está por debajo del cierre previo, respetando también el filtro de distancia.
  • Bajista engulfing
    • La vela evaluada cierra por debajo de su apertura (cuerpo bajista).
    • La vela anterior cierra por encima de su apertura (cuerpo alcista).
    • La vela bajista aún registra un máximo más alto pero cierra bien por debajo de la apertura anterior, y su apertura supera el cierre previo, cada uno respetando el filtro de distancia.
    • El mínimo de la barra bajista está por debajo del mínimo anterior en el filtro de distancia.

Estas condiciones reproducen la implementación original de MetaTrader, que exigía que la vela engulfing cubriera completamente el cuerpo anterior y se extendiera más allá de ambos extremos. El filtro de distancia se mide en pips y se convierte a precio usando el paso de precio e índice de decimales del instrumento (las cotizaciones forex de 5 y 3 dígitos se escalan automáticamente a pips de 10 puntos).

Lógica de trading

  1. Suscribirse al tipo de vela seleccionado a través de la API de alto nivel y procesar solo las velas terminadas.
  2. Mantener un pequeño buffer rodante que almacena los valores OHLC requeridos para el valor de desplazamiento actual.
  3. Cuando al menos dos velas históricas están disponibles para evaluación, probar las condiciones de engulfing alcista y bajista descritas anteriormente.
  4. Al recibir una señal alcista, enviar una orden de mercado en el lado definido por BullishSide. Al recibir una señal bajista, usar el lado configurado mediante BearishSide.
  5. Si CloseOppositePositions está habilitado y existe una exposición opuesta, la estrategia aumenta el volumen de la orden en la posición actual absoluta para que la operación resultante cierre el lado contrario y abra uno nuevo en la dirección deseada. Cuando el indicador está deshabilitado, las señales se ignoran mientras hay una posición opuesta abierta.
  6. El dimensionamiento de posiciones está controlado por el parámetro Volume de la estrategia (predeterminado 1 contrato/lote). No se adjunta ningún stop-loss ni take-profit automático por defecto; la gestión del riesgo queda a cargo del usuario final o de módulos de protección (puede combinarse con las protecciones integradas de StockSharp).

Parámetros

Parámetro Descripción Predeterminado Notas
CandleType Marco temporal (StockSharp DataType) usado para la detección de señales. Marco temporal de 1 hora Ajustable a cualquier tipo de vela soportado.
Shift Número de velas completadas a omitir antes de evaluar el patrón. 1 Configurar 1 analiza la última barra cerrada; valores más altos miran más atrás.
DistanceInPips Distancia mínima en pips que la vela engulfing debe superar respecto a la anterior. 0 Convertido a precio usando el paso de precio del instrumento; útil para filtrar velas con cuerpos pequeños.
CloseOppositePositions Si se debe cerrar una posición opuesta existente cuando se dispara una nueva señal. true Deshabilitarlo omite la operación si la exposición actual entra en conflicto con la señal.
BullishSide Lado de la orden ejecutado en una señal alcista engulfing. Buy Puede invertirse a Sell para comportamiento contrario.
BearishSide Lado de la orden ejecutado en una señal bajista engulfing. Sell Puede invertirse a Buy para operar configuraciones contra tendencia.
Volume Tamaño base de la orden. 1 El volumen de la orden se incrementa en abs(Position) al cerrar el lado opuesto.

Gestión de posiciones y riesgo

  • Dado que las órdenes se envían a mercado sin stops de protección, combine la estrategia con módulos adicionales (p. ej., StartProtection) o configure controles de riesgo externos.
  • El código original de MetaTrader dimensionaba las operaciones mediante un gestor de dinero basado en riesgo. En este port el dimensionamiento se simplifica a un parámetro de volumen directo para que el comportamiento sea determinístico dentro de StockSharp; integre un bloque personalizado de gestión del capital si se requiere dimensionamiento dinámico.
  • Cuando CloseOppositePositions es true, las reversiones son inmediatas: el volumen de la operación es igual al volumen base más la posición abierta absoluta, garantizando una transición de plano a nueva dirección.

Archivos

  • CS/BullishBearishEngulfingStrategy.cs – implementación principal en C# construida sobre la API de estrategia de alto nivel de StockSharp.

Nota: No se proporciona implementación en Python para este ID; solo se incluye la versión en C# según lo solicitado.

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;
	}
}