Ver no GitHub

Estratégia de Alcista e Bajista Engulfing

Visão geral

Esta estratégia replica a configuração clássica de velas de engulfing altista e baixista que foi originalmente implementada no MetaTrader para o assessor especializado "Bullish and Bearish Engulfing". O port do StockSharp avalia velas concluídas em um período configurável, opcionalmente ignora um número de barras recentes e reage quando um padrão engulfing atende a um filtro de distância mínima. A lógica foi projetada para traders discricionários que desejam automatizar um padrão de ação do preço bem estabelecido, mantendo o controle sobre direção, volume e como as posições existentes são tratadas.

Definição do padrão

Um sinal engulfing é confirmado quando duas velas concluídas consecutivas satisfazem as seguintes regras (após aplicar o deslocamento configurado):

  • Engulfing altista
    • A vela avaliada mais recente fecha acima de sua abertura (corpo altista).
    • A vela anterior fecha abaixo de sua abertura (corpo baixista).
    • A vela altista tem um máximo mais alto e um mínimo mais baixo do que a vela anterior pelo menos pelo filtro de distância.
    • O fechamento altista termina acima da abertura anterior e sua abertura está abaixo do fechamento anterior, respeitando também o filtro de distância.
  • Engulfing baixista
    • A vela avaliada fecha abaixo de sua abertura (corpo baixista).
    • A vela anterior fecha acima de sua abertura (corpo altista).
    • A vela baixista ainda registra um máximo mais alto, mas fecha bem abaixo da abertura anterior, e sua abertura excede o fechamento anterior, cada um pelo filtro de distância.
    • O mínimo da barra baixista está abaixo do mínimo anterior pelo filtro de distância.

Essas condições reproduzem a implementação original do MetaTrader, que exigia que a vela engulfing cobrisse completamente o corpo anterior e se estendesse além de ambos os extremos. O filtro de distância é medido em pips e convertido para preço usando o passo de preço e as casas decimais do instrumento (cotações forex de 5 e 3 dígitos são automaticamente dimensionadas para pips de 10 pontos).

Lógica de trading

  1. Subscrever ao tipo de vela selecionado por meio da API de alto nível e processar apenas velas concluídas.
  2. Manter um pequeno buffer rotativo que armazena os valores OHLC necessários para o valor de deslocamento atual.
  3. Quando pelo menos duas velas históricas estiverem disponíveis para avaliação, testar as condições de engulfing altista e baixista descritas acima.
  4. Mediante um sinal altista, enviar uma ordem de mercado no lado definido por BullishSide. Mediante um sinal baixista, usar o lado configurado via BearishSide.
  5. Se CloseOppositePositions estiver habilitado e existir uma exposição oposta, a estratégia aumenta o volume da ordem pela posição atual absoluta para que a operação resultante feche o lado oposto e abra um novo na direção desejada. Quando o sinalizador está desabilitado, os sinais são ignorados enquanto uma posição oposta estiver aberta.
  6. O dimensionamento de posições é controlado pelo parâmetro Volume da estratégia (padrão: 1 contrato/lote). Nenhum stop-loss ou take-profit automático é anexado por padrão; a gestão de risco fica a cargo do usuário final ou de módulos de proteção (pode ser combinado com as proteções integradas do StockSharp).

Parâmetros

Parâmetro Descrição Padrão Notas
CandleType Período (StockSharp DataType) usado para detecção de sinal. Período de 1 hora Ajustável para qualquer tipo de vela suportado.
Shift Número de velas concluídas a ignorar antes de avaliar o padrão. 1 Configurar 1 analisa a última barra fechada; valores mais altos olham mais para trás.
DistanceInPips Distância mínima em pips que a vela engulfing deve exceder em relação à anterior. 0 Convertido para preço usando o passo de preço do instrumento; útil para filtrar velas com corpos pequenos.
CloseOppositePositions Se deve fechar uma posição oposta existente quando um novo sinal é disparado. true Desabilitar ignora a operação se a exposição atual entrar em conflito com o sinal.
BullishSide Lado da ordem executado em um sinal de engulfing altista. Buy Pode ser invertido para Sell para comportamento contrário.
BearishSide Lado da ordem executado em um sinal de engulfing baixista. Sell Pode ser invertido para Buy para operar configurações contra a tendência.
Volume Tamanho base da ordem. 1 O volume da ordem é aumentado em abs(Position) ao fechar o lado oposto.

Gestão de posições e risco

  • Como as ordens são enviadas a mercado sem stops de proteção, combine a estratégia com módulos adicionais (p. ex., StartProtection) ou configure controles de risco externos.
  • O código original do MetaTrader dimensionava as operações por meio de um gerenciador de capital baseado em risco. Neste port o dimensionamento é simplificado para um parâmetro de volume direto para que o comportamento seja determinístico dentro do StockSharp; integre um bloco personalizado de gerenciamento de capital se o dimensionamento dinâmico for necessário.
  • Quando CloseOppositePositions é true, as reversões são imediatas: o volume da operação é igual ao volume base mais a posição aberta absoluta, garantindo uma transição de plano para nova direção.

Arquivos

  • CS/BullishBearishEngulfingStrategy.cs – implementação principal em C# construída sobre a API de estratégia de alto nível do StockSharp.

Nota: Nenhuma implementação Python é fornecida para este ID; apenas a versão C# está incluída conforme 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;
	}
}