Ver en GitHub

Ruptura de Bollinger DC2008

Reimplementación del asesor experto de ruptura de Bollinger de Sergey Pavlov (DC2008) de MetaTrader para la API de estrategia de alto nivel de StockSharp. La estrategia observa velas completadas, evalúa rupturas de las Bandas de Bollinger en la fuente de precio seleccionada y abre o revierte posiciones solo cuando la operación actual no está perdiendo.

Descripción general

  • Calcula una envolvente de Bandas de Bollinger en el marco temporal configurado y el precio aplicado (cierre, apertura, máximo, mínimo, mediana, típico, ponderado o promedio).
  • Genera configuraciones largo cuando el mínimo de la vela cierra por debajo de la banda inferior mientras el máximo permanece bajo la banda media (fuerte extensión bajista que debería revertir).
  • Genera configuraciones corto cuando el máximo de la vela supera la banda superior mientras el mínimo se mantiene por encima de la banda media (fuerte extensión alcista que se espera revierta).
  • El experto MQL original operaba en ticks; en este port los señales se procesan una vez por vela terminada para mayor estabilidad y coherencia del indicador.
  • Las posiciones solo se abren o revierten si la posición existente muestra un beneficio no realizado no negativo, replicando el filtro de riesgo original.

Lógica de trading

Pipeline del indicador

  1. Suscribirse a velas del CandleType elegido (por defecto: marco temporal de 1 hora).
  2. Alimentar el precio aplicado seleccionado en el indicador de Bandas de Bollinger (Length = BandsPeriod, Width = BandsDeviation).
  3. Ignorar velas hasta que el indicador produzca valores válidos de banda superior, media e inferior.

Criterios de entrada

  • Comprar: Low < LowerBand y High < MiddleBand. Indica que toda la vela operó por debajo de la línea media tras perforar la banda inferior.
  • Vender: High > UpperBand y Low > MiddleBand. Indica que toda la vela operó por encima de la línea media tras perforar la banda superior.

Filtro de posición y gestión

  • Si no hay posición, la estrategia abre una orden de mercado con el Volume configurado cuando aparece una señal.
  • Si ya existe una posición:
    • Cuando la señal es opuesta a la dirección actual, calcular el beneficio no realizado como Position * (Close - PositionPrice) usando el cierre de la vela.
    • Si el beneficio no realizado es negativo, omitir todas las acciones para esta vela (idéntico al return temprano del original).
    • Si el beneficio no realizado es no negativo y la señal es opuesta, enviar una orden de mercado de reversión de tamaño Volume + |Position| para tanto aplanar la posición actual como establecer una nueva en la dirección de la señal.
    • Las señales que coinciden con la dirección actual no añaden a la posición (igual que la versión MQL).
  • No hay órdenes de stop-loss o take-profit explícitas; las salidas de operaciones ocurren solo mediante señales opuestas que satisfacen el filtro de beneficio.

Parámetros

Nombre Valor predeterminado Descripción
BandsPeriod 80 Número de velas utilizadas para calcular la media móvil y las desviaciones de Bollinger. Debe ser positivo y está disponible para optimización.
BandsDeviation 3.0 Multiplicador de desviación estándar aplicado al ancho de las Bandas de Bollinger. Positivo, optimizable.
AppliedPrice Close Fuente de precio para el indicador: Close, Open, High, Low, Median, Typical, Weighted o Average (OHLC/4). Refleja ENUM_APPLIED_PRICE de MetaTrader.
CandleType Marco temporal de 1 hora Tipo de vela (marco temporal) usado para análisis y órdenes. Se puede cambiar a cualquier otro tipo de datos compatible con StockSharp.
Volume (heredado) dependiente del broker Tamaño de orden para nuevas entradas. Las reversiones añaden automáticamente el tamaño absoluto de la posición existente.

Diferencias con el experto MQL original

  • El EA MetaTrader evaluaba condiciones en cada tick; este port C# espera a que las velas estén terminadas para evitar actuar sobre datos incompletos.
  • El desplazamiento del indicador estaba fijado en cero en el EA fuente y aquí permanece implícito; no se exponen desplazamientos adicionales.
  • MetaTrader reportaba el beneficio flotante directamente; el port lo aproxima vía cierre de vela y PositionPrice, lo que es suficiente para la comparación de signo usada por el filtro.
  • La gestión de operaciones, mensajes de cadena y comentarios de órdenes de la versión MQL se omiten, centrándose puramente en la generación de señales.

Notas de implementación

  • Las velas, indicadores y llamadas de trading dependen de las APIs de alto nivel de StockSharp (SubscribeCandles().Bind(...), BuyMarket, SellMarket).
  • El indicador se dibuja automáticamente si hay un área de gráfico disponible en la UI; las operaciones también se representan para depuración.
  • La estrategia reinicia y reconstruye el indicador en cada inicio, por lo que los cambios de parámetros tienen efecto inmediato en la próxima ejecución.
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>
/// Bollinger breakout strategy inspired by DC2008 implementation.
/// </summary>
public class BollingerBreakoutDc2008Strategy : Strategy
{
	public enum AppliedPriceTypes
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted,
		Average
	}

	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _bandsDeviation;
	private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
	private readonly StrategyParam<DataType> _candleType;

	private BollingerBands _bollinger;
	private decimal _entryPrice;

	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}

	public decimal BandsDeviation
	{
		get => _bandsDeviation.Value;
		set => _bandsDeviation.Value = value;
	}

	public AppliedPriceTypes AppliedPrice
	{
		get => _appliedPrice.Value;
		set => _appliedPrice.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public BollingerBreakoutDc2008Strategy()
	{
		_bandsPeriod = Param(nameof(BandsPeriod), 80)
			.SetDisplay("Bands Period", "Number of candles for Bollinger Bands", "Indicators")
			.SetGreaterThanZero()
			
			.SetOptimize(10, 200, 10);

		_bandsDeviation = Param(nameof(BandsDeviation), 3m)
			.SetDisplay("Deviation", "Standard deviation multiplier", "Indicators")
			.SetGreaterThanZero()
			
			.SetOptimize(1m, 5m, 0.5m);

		_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Close)
			.SetDisplay("Applied Price", "Candle price source for Bollinger Bands", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to analyze", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_bollinger = null;
		_entryPrice = 0m;
	}

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

		// Create Bollinger Bands indicator with the configured parameters.
		_bollinger = new BollingerBands
		{
			Length = BandsPeriod,
			Width = BandsDeviation
		};

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _bollinger);
			DrawOwnTrades(area);
		}
	}

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

		// Calculate Bollinger Bands for the selected price source.
		var indicatorValue = _bollinger.Process(new DecimalIndicatorValue(_bollinger, GetAppliedPrice(candle), candle.OpenTime) { IsFinal = true });

		if (!indicatorValue.IsFinal)
			return;

		if (indicatorValue is not BollingerBandsValue bands)
			return;

		if (bands.UpBand is not decimal upper || bands.LowBand is not decimal lower || bands.MovingAverage is not decimal middle)
			return;

		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var close = candle.ClosePrice;

		// Determine breakout conditions based on Bollinger structure.
		var buySignal = low < lower && high < middle;
		var sellSignal = high > upper && low > middle;

		if (!buySignal && !sellSignal)
			return;

		// Compute unrealized profit to mimic original position filter.
		var unrealizedPnL = Position == 0 ? 0m : Position * (close - _entryPrice);

		if (buySignal)
		{
			if (Position == 0)
			{
				// No position open, start a new long.
				BuyMarket();
				_entryPrice = close;
			}
			else
			{
				if (unrealizedPnL < 0m)
					return;

				if (Position < 0)
				{
					// Reverse from short to long while preserving target volume.
					BuyMarket();
					_entryPrice = close;
				}
			}

			return;
		}

		if (sellSignal)
		{
			if (Position == 0)
			{
				// No position open, start a new short.
				SellMarket();
				_entryPrice = close;
			}
			else
			{
				if (unrealizedPnL < 0m)
					return;

				if (Position > 0)
				{
					// Reverse from long to short while preserving target volume.
					SellMarket();
					_entryPrice = close;
				}
			}
		}
	}

	private decimal GetAppliedPrice(ICandleMessage candle)
	{
		return AppliedPrice switch
		{
			AppliedPriceTypes.Close => candle.ClosePrice,
			AppliedPriceTypes.Open => candle.OpenPrice,
			AppliedPriceTypes.High => candle.HighPrice,
			AppliedPriceTypes.Low => candle.LowPrice,
			AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
			AppliedPriceTypes.Average => (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m,
			_ => candle.ClosePrice
		};
	}
}