Ver en GitHub

Estrategia de Ruptura BollTrade

Esta estrategia replica el asesor experto original BollTrade operando rupturas de las Bandas de Bollinger con un búfer de pips configurable y dimensionamiento de posición opcional basado en el balance. Las órdenes se abren solo en velas completadas y se gestionan con niveles fijos de stop-loss y toma de ganancias.

Concepto

  • Se suscribe al marco temporal primario configurable y calcula un envelope de Bandas de Bollinger con el período y desviación especificados.
  • Añade un desplazamiento adicional (Band Offset) medido en pips por encima de la banda superior y por debajo de la banda inferior para reducir entradas prematuras.
  • Abre una posición larga cuando el cierre de la vela termina por debajo de la banda inferior menos el desplazamiento.
  • Abre una posición corta cuando el cierre de la vela termina por encima de la banda superior más el desplazamiento.
  • Solo puede estar activa una posición en cualquier momento. La estrategia espera a que la operación actual termine antes de evaluar nuevas entradas.

Gestión de Operaciones

  • Los niveles de stop-loss y toma de ganancias se establecen inmediatamente después de una entrada. Se expresan en múltiplos de pips y se evalúan en cada vela completada. Si el precio toca cualquiera de los niveles, la posición se cierra a mercado.
  • Si Scale Volume está habilitado, el volumen negociado crece (o se reduce) con el balance de la cuenta. La línea base de escalado es el valor inicial del portafolio dividido por el tamaño base del lote, imitando la implementación MQL original. El volumen está limitado a 500 lotes para mantener el riesgo bajo control, igual que en el código fuente.
  • El tamaño del pip se deriva del paso de precio del instrumento. Para pasos muy pequeños (símbolos tipo forex), el código multiplica el paso por 10 para convertir pasos de pip fraccionarios en pips estándar, coincidiendo con el comportamiento de la versión MetaTrader.

Parámetros

Nombre Descripción Valor predeterminado
Candle Type Marco temporal usado para las velas de señal. Marco temporal de 15 minutos
Bollinger Period Número de barras en el cálculo de las Bandas de Bollinger. 4
Bollinger Deviation Multiplicador de ancho para las Bandas de Bollinger. 2
Band Offset Desplazamiento adicional en pips añadido fuera de ambas bandas antes de activar señales. 3
Take Profit (pips) Distancia al objetivo de ganancia en unidades de pips. 3
Stop Loss (pips) Distancia al stop de protección en unidades de pips. 20
Base Volume Volumen predeterminado en lotes cuando el escalado está deshabilitado. 1
Scale Volume Cuando está habilitado, escala el tamaño de posición con el balance de la cuenta. Habilitado

Notas de uso

  • Funciona mejor en símbolos forex o CFD donde los desplazamientos basados en pips proporcionan niveles de ruptura claros, pero también puede ejecutarse en futuros o acciones siempre que su PriceStep esté configurado.
  • La estrategia procesa solo velas terminadas, por lo que los picos intrabarra que revierten antes del cierre de la barra no activarán entradas.
  • Dado que las salidas se manejan con stops y objetivos fijos, asegúrese de que esas distancias sean apropiadas para el marco temporal seleccionado y la volatilidad del instrumento.
  • El EA original dependía de stops del lado del broker. Este puerto monitorea los extremos de las velas para emular el mismo comportamiento de protección dentro de StockSharp.

Archivos

  • CS/BollTradeStrategy.cs – implementación en C# de la estrategia.
  • README.md – documentación en inglés (este archivo).
  • README_ru.md – documentación en ruso.
  • README_zh.md – documentación en chino.
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 Bands breakout strategy with optional balance-based position sizing.
/// </summary>
public class BollTradeStrategy : Strategy
{
	private readonly StrategyParam<decimal> _maxVolume;

	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _bandOffset;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<decimal> _lots;
	private readonly StrategyParam<bool> _lotIncrease;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lotBaseline;
	private decimal _pipSize;
	private BollingerBands _bollinger;
	private decimal? _longStop;
	private decimal? _longTarget;
	private decimal? _shortStop;
	private decimal? _shortTarget;

	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public decimal BollingerDistance
	{
		get => _bandOffset.Value;
		set => _bandOffset.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

	public decimal Lots
	{
		get => _lots.Value;
		set => _lots.Value = value;
	}

	public bool LotIncrease
	{
		get => _lotIncrease.Value;
		set => _lotIncrease.Value = value;
	}

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

	public decimal MaxVolume
	{
		get => _maxVolume.Value;
		set => _maxVolume.Value = value;
	}

	public BollTradeStrategy()
	{
		_maxVolume = Param(nameof(MaxVolume), 500m)
			.SetGreaterThanZero()
			.SetDisplay("Max Volume", "Upper bound for scaled volume", "Money Management");

		_takeProfit = Param(nameof(TakeProfit), 3m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Distance to take profit expressed in pip units.", "Orders")
		;

		_stopLoss = Param(nameof(StopLoss), 20m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Distance to stop loss expressed in pip units.", "Orders")
		;

		_bandOffset = Param(nameof(BollingerDistance), 0m)
		.SetNotNegative()
		.SetDisplay("Band Offset", "Extra pip offset beyond Bollinger Bands.", "Signals")
		;

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("Bollinger Period", "Length of the Bollinger Bands.", "Signals")
		;

		_bollingerDeviation = Param(nameof(BollingerDeviation), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Bollinger Deviation", "Width multiplier of the Bollinger Bands.", "Signals")
		;

		_lots = Param(nameof(Lots), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Base Volume", "Default trade volume in lots.", "Money Management");

		_lotIncrease = Param(nameof(LotIncrease), true)
		.SetDisplay("Scale Volume", "Increase volume with balance growth.", "Money Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe for signals.", "General");
	}

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

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

		_lotBaseline = 0m;
		_pipSize = 0m;
		_bollinger = null!;
		ResetStops();
	}

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

		Volume = Lots;

		_pipSize = CalculatePipSize();
		_lotBaseline = 0m;

		if (LotIncrease && Lots > 0m)
		{
			var balance = Portfolio?.CurrentValue ?? 0m;

			if (balance > 0m)
			_lotBaseline = balance / Lots;
		}

		var bollinger = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerDeviation
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
		.BindEx(bollinger, ProcessCandle)
		.Start();

		_bollinger = bollinger;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;

		if (step <= 0m)
		step = 1m;

		if (step < 0.01m)
		step *= 10m;

		return step;
	}

	private decimal CalculateVolume()
	{
		var baseVolume = Lots;

		if (!LotIncrease || _lotBaseline <= 0m)
		return baseVolume;

		var balance = Portfolio?.CurrentValue ?? 0m;

		if (balance <= 0m)
		return baseVolume;

		var scaled = baseVolume * (balance / _lotBaseline);

		return Math.Min(scaled, MaxVolume);
	}

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

		if (value is not BollingerBandsValue bollingerValue)
			return;

		if (bollingerValue.MovingAverage is not decimal middleBand ||
			bollingerValue.UpBand is not decimal upperBand ||
			bollingerValue.LowBand is not decimal lowerBand)
			return;

		var offset = _pipSize * BollingerDistance;
		var upperThreshold = upperBand + offset;
		var lowerThreshold = lowerBand - offset;

		var shouldBuy = candle.ClosePrice < lowerThreshold;
		var shouldSell = candle.ClosePrice > upperThreshold;

		if (Position == 0)
		{
			if (shouldBuy)
			{
				EnterLong(candle);
			}
			else if (shouldSell)
			{
				EnterShort(candle);
			}

			return;
		}

		if (Position > 0)
		{
			// Close long positions when stop loss or take profit levels are hit.
			if ((_longStop.HasValue && candle.LowPrice <= _longStop.Value) ||
				(_longTarget.HasValue && candle.HighPrice >= _longTarget.Value))
			{
				SellMarket();
				ResetStops();
			}
		}
		else if (Position < 0)
		{
			// Close short positions when stop loss or take profit levels are hit.
			if ((_shortStop.HasValue && candle.HighPrice >= _shortStop.Value) ||
				(_shortTarget.HasValue && candle.LowPrice <= _shortTarget.Value))
			{
				BuyMarket();
				ResetStops();
			}
		}
	}

	private void EnterLong(ICandleMessage candle)
	{
		var volume = CalculateVolume();

		if (volume <= 0m)
			return;

		BuyMarket(volume);

		// Store exit targets for the newly opened long trade.
		_longStop = StopLoss > 0m ? candle.ClosePrice - _pipSize * StopLoss : null;
		_longTarget = TakeProfit > 0m ? candle.ClosePrice + _pipSize * TakeProfit : null;
		_shortStop = null;
		_shortTarget = null;
	}

	private void EnterShort(ICandleMessage candle)
	{
		var volume = CalculateVolume();

		if (volume <= 0m)
			return;

		SellMarket(volume);

		// Store exit targets for the newly opened short trade.
		_shortStop = StopLoss > 0m ? candle.ClosePrice + _pipSize * StopLoss : null;
		_shortTarget = TakeProfit > 0m ? candle.ClosePrice - _pipSize * TakeProfit : null;
		_longStop = null;
		_longTarget = null;
	}

	private void ResetStops()
	{
		// Clear cached exit levels after a position is closed.
		_longStop = null;
		_longTarget = null;
		_shortStop = null;
		_shortTarget = null;
	}
}