Ver no GitHub

Estratégia Bollinger Winner Pro

Bollinger Winner Pro expande a versão Lite adicionando filtros modulares e controles de risco. Ainda busca preços fechando fora das Bollinger Bands, mas as operações são executadas apenas quando as confirmações opcionais concordam.

Os traders podem habilitar filtros de RSI, Aroon e média móvel para confirmar o impulso e a direção da tendência. Um stop-loss integrado também pode ser ativado para limitar o risco. Essa flexibilidade permite que a estratégia se adapte a diferentes mercados ou necessidades de teste.

A abordagem visa a reversão à média: uma vez que o preço reentra nas bandas ou toca o lado oposto, a posição é fechada ou o stop é acionado. Como múltiplos filtros podem ser empilhados, os sinais são menos frequentes, mas de maior qualidade.

Detalhes

  • Dados: Velas de preço.
  • Critérios de entrada: Vela fecha fora de uma banda e todos os filtros habilitados concordam.
  • Critérios de saída: Retorno à banda central/oposta ou stop-loss se UseSL for verdadeiro.
  • Stops: Stop-loss opcional controlado por UseSL.
  • Valores padrão:
    • UseRSI = True
    • UseAroon = False
    • UseMA = True
    • UseSL = True
  • Filtros:
    • Categoria: Reversão à média com confirmações
    • Direção: Comprado/Vendido
    • Indicadores: Bollinger Bands, RSI, Aroon, Moving Average
    • Complexidade: Avançado
    • Nível de risco: Médio
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>
/// Bollinger Winner Pro strategy.
/// Goes long when a candle closes below the lower band and short when it closes above the upper band, provided every
/// enabled filter agrees: RSI beyond its oversold/overbought level, Aroon up/down dominance in the trade direction and
/// the middle band on the trade side of the moving average, which confirms the trend direction. A position closes when price returns to the middle band (or reaches
/// the opposite one), and an optional percent stop-loss caps the risk.
/// </summary>
public class BollingerWinnerProStrategy : Strategy
{
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<decimal> _bbMultiplier;
	private readonly StrategyParam<bool> _useRsi;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<decimal> _rsiOversold;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<bool> _useAroon;
	private readonly StrategyParam<int> _aroonLength;
	private readonly StrategyParam<bool> _useMa;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<bool> _useSl;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;

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

	/// <summary>
	/// Bollinger Bands standard deviation multiplier.
	/// </summary>
	public decimal BBMultiplier
	{
		get => _bbMultiplier.Value;
		set => _bbMultiplier.Value = value;
	}

	/// <summary>
	/// Enable the RSI filter.
	/// </summary>
	public bool UseRSI
	{
		get => _useRsi.Value;
		set => _useRsi.Value = value;
	}

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RSILength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// RSI level below which longs are allowed.
	/// </summary>
	public decimal RSIOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

	/// <summary>
	/// RSI level above which shorts are allowed.
	/// </summary>
	public decimal RSIOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	/// <summary>
	/// Enable the Aroon filter.
	/// </summary>
	public bool UseAroon
	{
		get => _useAroon.Value;
		set => _useAroon.Value = value;
	}

	/// <summary>
	/// Aroon period.
	/// </summary>
	public int AroonLength
	{
		get => _aroonLength.Value;
		set => _aroonLength.Value = value;
	}

	/// <summary>
	/// Enable the moving average filter.
	/// </summary>
	public bool UseMA
	{
		get => _useMa.Value;
		set => _useMa.Value = value;
	}

	/// <summary>
	/// Moving average period.
	/// </summary>
	public int MALength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Enable the stop-loss.
	/// </summary>
	public bool UseSL
	{
		get => _useSl.Value;
		set => _useSl.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage from the entry price.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public BollingerWinnerProStrategy()
	{
		_bbLength = Param(nameof(BBLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands");

		_bbMultiplier = Param(nameof(BBMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("BB StdDev", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");

		_useRsi = Param(nameof(UseRSI), true)
			.SetDisplay("Use RSI", "Require RSI confirmation", "RSI Filter");

		_rsiLength = Param(nameof(RSILength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI period", "RSI Filter");

		_rsiOversold = Param(nameof(RSIOversold), 40m)
			.SetDisplay("RSI Oversold", "RSI level below which longs are allowed", "RSI Filter");

		_rsiOverbought = Param(nameof(RSIOverbought), 60m)
			.SetDisplay("RSI Overbought", "RSI level above which shorts are allowed", "RSI Filter");

		_useAroon = Param(nameof(UseAroon), false)
			.SetDisplay("Use Aroon", "Require Aroon confirmation", "Aroon Filter");

		_aroonLength = Param(nameof(AroonLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("Aroon Length", "Aroon period", "Aroon Filter");

		_useMa = Param(nameof(UseMA), true)
			.SetDisplay("Use MA", "Require the middle band on the trade side of the moving average", "Moving Average");

		_maLength = Param(nameof(MALength), 50)
			.SetGreaterThanZero()
			.SetDisplay("MA Length", "Moving average period", "Moving Average");

		_useSl = Param(nameof(UseSL), true)
			.SetDisplay("Use Stop Loss", "Enable the percent stop-loss", "Risk");

		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
	}

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

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

		var bollinger = new BollingerBands
		{
			Length = BBLength,
			Width = BBMultiplier
		};

		var rsi = new RelativeStrengthIndex { Length = RSILength };
		var aroon = new Aroon { Length = AroonLength };
		var ma = new ExponentialMovingAverage { Length = MALength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bollinger, rsi, aroon, ma, ProcessCandle)
			.Start();

		if (UseSL)
			StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true);

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

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue rsiValue, IIndicatorValue aroonValue, IIndicatorValue maValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!bollingerValue.IsFormed || !rsiValue.IsFormed || !aroonValue.IsFormed || !maValue.IsFormed)
			return;

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

		var aroon = (AroonValue)aroonValue;
		if (aroon.Up is not decimal aroonUp || aroon.Down is not decimal aroonDown)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var rsi = rsiValue.ToDecimal();
		var ma = maValue.ToDecimal();
		var close = candle.ClosePrice;

		var longSignal = close < lower
			&& (!UseRSI || rsi < RSIOversold)
			&& (!UseAroon || aroonUp > aroonDown)
			&& (!UseMA || middle > ma);

		var shortSignal = close > upper
			&& (!UseRSI || rsi > RSIOverbought)
			&& (!UseAroon || aroonDown > aroonUp)
			&& (!UseMA || middle < ma);

		if (longSignal && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));
		else if (shortSignal && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
		else if (Position > 0 && close >= middle)
			SellMarket(Position);
		else if (Position < 0 && close <= middle)
			BuyMarket(-Position);
	}
}