Ver en GitHub

Estrategia Bollinger Squeeze

Estrategia basada en la compresión de las Bandas de Bollinger

Las pruebas indican un retorno anual promedio de aproximadamente 100%. Funciona mejor en el mercado de forex.

Bollinger Squeeze espera un ancho de banda estrecho que indique baja volatilidad. Una ruptura fuera de las bandas inicia una operación en esa dirección y sale cuando el momentum falla o aparece una ruptura opuesta.

La condición de compresión indica una próxima expansión de volatilidad. Una vez activada, la operación monta el movimiento de ruptura y depende de un stop ATR o cruce de banda para salir.

Detalles

  • Criterios de entrada: Señales basadas en Bollinger.
  • Largo/Corto: Ambas direcciones.
  • Criterios de salida: Señal opuesta.
  • Stops: No.
  • Valores predeterminados:
    • BollingerPeriod = 20
    • BollingerDeviation = 2m
    • SqueezeThreshold = 0.1m
    • CandleType = TimeSpan.FromMinutes(5)
  • Filtros:
    • Categoría: Tendencia
    • Dirección: Ambos
    • Indicadores: Bollinger
    • Stops: No
    • Complejidad: Básico
    • Marco temporal: Intradía (5m)
    • Estacionalidad: No
    • Neural Networks: No
    • Divergencia: No
    • Nivel de riesgo: Medio
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on Bollinger Bands squeeze.
/// Detects when bands narrow (squeeze) and trades the breakout direction.
/// </summary>
public class BollingerSqueezeStrategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevBandWidth;
	private bool _hasPrevValues;
	private int _cooldown;

	/// <summary>
	/// Bollinger Bands period.
	/// </summary>
	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands deviation multiplier.
	/// </summary>
	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="BollingerSqueezeStrategy"/>.
	/// </summary>
	public BollingerSqueezeStrategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators")
			.SetOptimize(15, 30, 5);

		_bollingerDeviation = Param(nameof(BollingerDeviation), 1.8m)
			.SetDisplay("Bollinger Deviation", "Standard deviation multiplier", "Indicators");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevBandWidth = default;
		_hasPrevValues = default;
		_cooldown = default;
	}

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

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

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bb, ProcessCandle)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var bb = (IBollingerBandsValue)bbValue;

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

		if (middle == 0)
			return;

		var bandWidth = (upper - lower) / middle;

		if (!_hasPrevValues)
		{
			_hasPrevValues = true;
			_prevBandWidth = bandWidth;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevBandWidth = bandWidth;
			return;
		}

		var price = candle.ClosePrice;

		// Price crosses above upper band = buy (breakout)
		if (price > upper && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
			_cooldown = 10;
		}
		// Price crosses below lower band = sell (breakout)
		else if (price < lower && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
			_cooldown = 10;
		}

		_prevBandWidth = bandWidth;
	}
}