Ver no GitHub

Estratégia BARS Alligator

A estratégia BARS Alligator é um port direto do consultor especialista do MetaTrader com o mesmo nome. Ela depende do indicador Alligator de Bill Williams para detectar tendências emergentes: quando a linha verde dos lábios (lips) cruza acima da linha azul da mandíbula (jaw), o sistema trata isso como um rompimento altista, enquanto um cruzamento para baixo sinaliza impulso baixista. As saídas dependem do lips cruzando a linha vermelha dos dentes (teeth) para que as posições sejam fechadas assim que o impulso desaparece. As distâncias de stop-loss de proteção, take-profit e trailing stop são configuradas em pips e automaticamente convertidas em unidades de preço com base no passo de preço do instrumento e na precisão decimal.

Lógica de trading

  1. Construção do indicador
    • Três médias móveis com comprimentos, deslocamentos e tipo configuráveis (simples, exponencial, suavizada ou ponderada) formam o Alligator.
    • O preço aplicado pode ser o fechamento, abertura, máxima, mínima, mediano, típico ou preço ponderado de cada vela.
    • Os deslocamentos são respeitados armazenando um pequeno buffer rotativo para cada linha para que os cruzamentos usem os mesmos valores que apareceriam em um gráfico do MetaTrader.
  2. Condições de entrada
    • Comprado: a linha lips na barra anterior está acima da jaw e estava abaixo duas barras atrás (cruzamento altista para cima).
    • Vendido: a linha lips na barra anterior está abaixo da jaw e estava acima duas barras atrás (cruzamento baixista para baixo).
    • Novas entradas são permitidas apenas se a posição atual está plana ou já alinhada com a direção do sinal e o tamanho agregado da posição permanece abaixo de MaxPositions × OrderVolume (ou o equivalente dimensionado por risco).
  3. Condições de saída
    • Saída comprada: a linha lips cruza abaixo da linha teeth e a posição é lucrativa em relação ao preço médio de entrada.
    • Saída vendida: a linha lips cruza acima da linha teeth e a posição é lucrativa.
    • As saídas também ocorrem quando os níveis estáticos de stop-loss ou take-profit são violados.
  4. Trailing stop
    • Quando habilitado, um trailing stop reposiciona o stop de proteção assim que o preço se move além de TrailingStopPips + TrailingStepPips na direção do trade. O stop então segue o preço a uma distância de TrailingStopPips pips, mas apenas avança se o preço fizer novo progresso de pelo menos TrailingStepPips pips.
  5. Gestão monetária
    • Com MoneyMode = FixedVolume, as ordens usam o tamanho de OrderVolume diretamente.
    • Com MoneyMode = RiskPercent, a estratégia aloca volume de forma que o percentual configurado MoneyValue do capital do portfólio seria perdido se o stop-loss fosse atingido. O risco por unidade equivale à distância do stop-loss expressa em unidades de preço. O resultado é arredondado para baixo até o VolumeStep mais próximo (ou para 1 quando informações de step estão faltando).

Parâmetros

Parâmetro Tipo Padrão Descrição
CandleType DataType TimeSpan.FromHours(1).TimeFrame() Período usado para cálculos do Alligator.
OrderVolume decimal 0.1 Volume de trade fixo quando MoneyMode é FixedVolume.
MoneyMode MoneyManagementMode FixedVolume Escolhe entre volume fixo e dimensionamento por percentual de risco.
MoneyValue decimal 1 Percentual de risco aplicado quando MoneyMode é RiskPercent; ignorado caso contrário.
MaxPositions int 1 Número máximo de entradas aditivas por direção (expresso como múltiplos do volume de ordem calculado).
StopLossPips int 150 Distância de stop-loss em pips. Zero desabilita o stop de proteção.
TakeProfitPips int 150 Distância de take-profit em pips. Zero desabilita o alvo de lucro.
TrailingStopPips int 5 Distância do trailing stop em pips. Zero desabilita o trailing.
TrailingStepPips int 5 Distância extra que o preço deve percorrer antes de o trailing stop avançar. Deve ser positivo quando o trailing está habilitado.
JawPeriod int 13 Comprimento da média móvil jaw.
JawShift int 8 Deslocamento para frente (em barras) aplicado à série jaw.
TeethPeriod int 8 Comprimento da média móvil teeth.
TeethShift int 5 Deslocamento para frente aplicado à série teeth.
LipsPeriod int 5 Comprimento da média móvil lips.
LipsShift int 3 Deslocamento para frente aplicado à série lips.
MaType MovingAverageType Smoothed Algoritmo de média móvil usado para as três linhas do Alligator.
AppliedPrice AppliedPriceType Median Preço da vela fornecido às médias móveis (fechamento, abertura, máxima, mínima, mediano, típico ou ponderado).

Conversão de pips

A estratégia multiplica as configurações de pip pelo PriceStep do ativo. Quando o instrumento usa 3 ou 5 casas decimais, o valor é ajustado em ×10 para imitar a definição de pip do MetaTrader para cotações fracionárias. Se nenhum passo de preço estiver disponível, assume-se um valor de 1.

Notas de implementação

  • MaxPositions atua sobre o tamanho agregado da posição porque o StockSharp opera em modo netting. Entradas adicionais aumentam o preço médio em vez de criar tickets de posição separados.
  • O stop-loss e o take-profit são rastreados internamente e executados com ordens de mercado na primeira vela que viola os limites, correspondendo ao comportamento do especialista MQL original.
  • O dimensionamento baseado em risco requer uma distância de stop-loss diferente de zero; caso contrário, o sistema recorre ao OrderVolume fixo.
  • Todos os valores dos indicadores são atualizados apenas em velas concluídas (CandleStates.Finished) para evitar sinais prematuros.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// Bill Williams Alligator strategy: trades on lips/jaw crossover and exits on lips/teeth crossover.
/// </summary>
public class BarsAlligatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;

	private readonly SmoothedMovingAverage _jaw = new() { Length = 13 };
	private readonly SmoothedMovingAverage _teeth = new() { Length = 8 };
	private readonly SmoothedMovingAverage _lips = new() { Length = 5 };

	private decimal _previousJaw;
	private decimal _previousTeeth;
	private decimal _previousLips;
	private bool _hasPrevious;
	private decimal? _entryPrice;
	private int _cooldownLeft;

	public BarsAlligatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_cooldownBars = Param(nameof(CooldownBars), 6).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between completed trades", "Trading");
		_stopLossPercent = Param(nameof(StopLossPercent), 3m).SetDisplay("Stop Loss %", "Stop distance as percentage of entry price", "Risk");
		_takeProfitPercent = Param(nameof(TakeProfitPercent), 3m).SetDisplay("Take Profit %", "Take-profit distance as percentage of entry price", "Risk");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
	public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }

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

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

		_previousJaw = 0m;
		_previousTeeth = 0m;
		_previousLips = 0m;
		_hasPrevious = false;
		_entryPrice = null;
		_cooldownLeft = 0;
	}

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

		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_jaw, _teeth, _lips, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal jaw, decimal teeth, decimal lips)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_cooldownLeft > 0)
			_cooldownLeft--;

		if (Position != 0 && _entryPrice is null)
			_entryPrice = candle.ClosePrice;

		if (TryExitByRisk(candle))
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!_hasPrevious)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Exit conditions: lips crosses teeth against position
		var closeLong = lips < teeth && _previousLips >= _previousTeeth && Position > 0;
		var closeShort = lips > teeth && _previousLips <= _previousTeeth && Position < 0;

		if (closeLong)
		{
			SellMarket(Position);
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (closeShort)
		{
			BuyMarket(Math.Abs(Position));
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading() || _cooldownLeft > 0)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Entry: lips crosses jaw with proper Alligator ordering
		var buySignal = lips > jaw && _previousLips <= _previousJaw && lips > teeth;
		var sellSignal = lips < jaw && _previousLips >= _previousJaw && lips < teeth;

		if (buySignal && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}
		else if (sellSignal && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}

		UpdatePrevious(jaw, teeth, lips);
	}

	private bool TryExitByRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entryPrice || Position == 0 || entryPrice == 0)
			return false;

		var stopDistance = entryPrice * StopLossPercent / 100m;
		var takeDistance = entryPrice * TakeProfitPercent / 100m;

		if (Position > 0)
		{
			if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
				(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);

			if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
				(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
			{
				BuyMarket(volume);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}

		return false;
	}

	private void UpdatePrevious(decimal jaw, decimal teeth, decimal lips)
	{
		_previousJaw = jaw;
		_previousTeeth = teeth;
		_previousLips = lips;
		_hasPrevious = true;
	}
}