Ver no GitHub

Estratégia de Rompimento BollTrade

Esta estratégia replica o assessor especialista original BollTrade operando rompimentos das Bandas de Bollinger com um buffer de pips configurável e dimensionamento de posição opcional baseado no saldo. As ordens são abertas apenas em velas completadas e são gerenciadas com níveis fixos de stop-loss e take profit.

Conceito

  • Subscreve o período primário configurável e calcula um envelope das Bandas de Bollinger com o período e desvio especificados.
  • Adiciona um deslocamento adicional (Band Offset) medido em unidades de pip acima da banda superior e abaixo da banda inferior para reduzir entradas prematuras.
  • Abre uma posição comprada quando o fechamento da vela termina abaixo da banda inferior menos o deslocamento.
  • Abre uma posição vendida quando o fechamento da vela termina acima da banda superior mais o deslocamento.
  • Apenas uma posição pode estar ativa a qualquer momento. A estratégia aguarda o término da negociação atual antes de avaliar novas entradas.

Gestão de Negociações

  • Os níveis de stop-loss e take profit são definidos imediatamente após uma entrada. São expressos em múltiplos de pip e avaliados em cada vela completada. Se o preço tocar qualquer nível, a posição é fechada a mercado.
  • Se Scale Volume estiver habilitado, o volume negociado cresce (ou diminui) com o saldo da conta. A linha de base de escala é o valor inicial do portfólio dividido pelo tamanho base do lote, imitando a implementação MQL original. O volume é limitado a 500 lotes para manter o risco sob controle, assim como no código fonte.
  • O tamanho do pip é derivado do passo de preço do instrumento. Para passos muito pequenos (símbolos no estilo forex), o código multiplica o passo por 10 para converter passos de pip fracionários em pips padrão, correspondendo ao comportamento da versão MetaTrader.

Parâmetros

Nome Descrição Valor padrão
Candle Type Período usado para velas de sinal. Período de 15 minutos
Bollinger Period Número de barras no cálculo das Bandas de Bollinger. 4
Bollinger Deviation Multiplicador de largura para as Bandas de Bollinger. 2
Band Offset Deslocamento adicional em pip adicionado fora de ambas as bandas antes de acionar sinais. 3
Take Profit (pips) Distância ao alvo de lucro em unidades de pip. 3
Stop Loss (pips) Distância ao stop de proteção em unidades de pip. 20
Base Volume Volume padrão em lotes quando o dimensionamento está desabilitado. 1
Scale Volume Quando habilitado, escala o tamanho da posição com o saldo da conta. Habilitado

Notas de uso

  • Funciona melhor em símbolos forex ou CFD onde deslocamentos baseados em pip fornecem níveis de rompimento claros, mas também pode ser executado em futuros ou ações desde que seu PriceStep esteja configurado.
  • A estratégia processa apenas velas terminadas, portanto picos intrabarra que revertem antes do fechamento da barra não acionarão entradas.
  • Como as saídas são tratadas com stops e alvos fixos, certifique-se de que essas distâncias sejam apropriadas para o período selecionado e a volatilidade do instrumento.
  • O EA original dependia de stops do lado do broker. Este port monitora os extremos das velas para emular o mesmo comportamento de proteção dentro do StockSharp.

Arquivos

  • CS/BollTradeStrategy.cs – implementação em C# da estratégia.
  • README.md – documentação em inglês (este arquivo).
  • README_ru.md – documentação em russo.
  • README_zh.md – documentação em chinês.
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;
	}
}