Ver no GitHub

Estratégia Broadening Top

Visão geral

Broadening Top Strategy é um sistema seguidor de tendência inspirado no expert advisor original "Broadening top" do MetaTrader. A estratégia se concentra em capturar rompimentos que aparecem após uma formação de alargamento, combinando direção de tendência e confirmação de momentum. Duas médias móveis linearmente ponderadas, um oscilador de momentum e um filtro MACD trabalham juntos para detectar rompimentos altistas e baixistas.

Lógica de negociação

  1. Filtro de tendência: a estratégia compara uma média móvel linearmente ponderada (LWMA) rápida e uma lenta. Operações compradas exigem que a LWMA rápida esteja acima da lenta, enquanto operações vendidas esperam o oposto.
  2. Confirmação de momentum: o oscilador de momentum é observado nos três últimos candles concluídos. Uma operação só é permitida se qualquer um desses valores se desviar do nível neutro (100) pelo menos pelo limiar configurado (valores separados para compras e vendas).
  3. Alinhamento MACD: um filtro adicional verifica a linha MACD contra sua linha de sinal. Posições compradas são disparadas apenas quando a linha MACD está acima da linha de sinal; vendas, quando está abaixo.
  4. Tratamento de posição: antes de abrir uma operação na direção oposta, a estratégia fecha a posição atual, garantindo que apenas uma posição esteja ativa por vez.

Gestão de risco

A estratégia usa StartProtection para gerenciar ordens protetoras:

  • Distâncias opcionais de stop-loss e take-profit definidas em passos de preço (pips).
  • Um trailing stop opcional com passo de trailing configurável.

Parâmetros

Parâmetro Descrição Padrão
OrderVolume Tamanho da ordem em lotes/contratos. 1
FastMaLength Comprimento da média móvel linearmente ponderada rápida. 6
SlowMaLength Comprimento da média móvel linearmente ponderada lenta. 85
MomentumPeriod Período de retrospectiva do oscilador de momentum. 14
MomentumBuyThreshold Distância mínima do nível neutro de momentum (100) exigida para permitir entradas compradas. 0.3
MomentumSellThreshold Distância mínima do nível neutro de momentum (100) exigida para permitir entradas vendidas. 0.3
MacdFast Comprimento da EMA rápida dentro do MACD. 12
MacdSlow Comprimento da EMA lenta dentro do MACD. 26
MacdSignal EMA de sinal dentro do MACD. 9
TakeProfitPips Distância do take-profit medida em passos de preço. 50
StopLossPips Distância do stop-loss medida em passos de preço. 20
TrailingStopPips Distância do trailing-stop medida em passos de preço. 40
TrailingStepPips Distância adicional antes da atualização do trailing stop. 10
CandleType Tipo de candle/timeframe usado para cálculos. Timeframe de 15 minutos
EnableLongs Habilita ou desabilita operações compradas. true
EnableShorts Habilita ou desabilita operações vendidas. true

Indicadores

  • LinearWeightedMovingAverage: filtros de tendência rápido e lento.
  • Momentum: confirma aceleração do mercado para longe do nível neutro.
  • MovingAverageConvergenceDivergenceSignal: fornece confirmação direcional via linhas MACD e de sinal.

Notas de uso

  • Limiares de momentum são avaliados nos três candles concluídos mais recentes para emular o comportamento MQL original.
  • Ordens protetoras (stop-loss, take-profit, trailing stop) são opcionais e podem ser desabilitadas definindo a distância correspondente como zero.
  • A estratégia deve ser anexada a ativos que forneçam passo de preço e informações decimais para calcular corretamente o tamanho do pip.
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;

public class BroadeningTopStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BroadeningTopStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}