Ver no GitHub

Estratégia Exp XWPR Histograma Vol

Visão geral

Esta estratégia é uma conversão em C# do expert do MetaTrader Exp_XWPR_Histogram_Vol. Opera nas mudanças de cor do indicador personalizado XWPR Histograma Vol, que multiplica o oscilador Williams %R pelo volume da vela e suaviza o resultado. O port mantém o esquema original de gestão de dinheiro de dois slots (volume primário e secundário) e reproduz as mesmas regras de entrada e saída baseadas em cor usando a API de alto nível do StockSharp.

O algoritmo processa apenas velas finalizadas. Em cada nova barra, inspeciona a cor do histograma um número configurável de barras atrás no passado e reage quando as transições de cor cruzam os limites de alta ou baixa definidos pelo indicador.

Lógica do indicador

  1. Williams %R (WprPeriod) é deslocado em +50 e multiplicado pelo volume de vela selecionado (VolumeMode).
  2. Tanto o Williams %R ponderado quanto o volume bruto passam por filtros de suavização idênticos (SmoothingMethod, SmoothingLength, SmoothingPhase).
  3. Quatro níveis dinâmicos são derivados do volume suavizado: HighLevel2, HighLevel1, LowLevel1 e LowLevel2.
  4. As cores do histograma correspondem às zonas definidas por esses níveis:
    • 0 – histograma acima de HighLevel2 (alta forte).
    • 1 – histograma entre HighLevel1 e HighLevel2 (alta moderada).
    • 2 – histograma entre LowLevel1 e HighLevel1 (neutro).
    • 3 – histograma entre LowLevel2 e LowLevel1 (baixa moderada).
    • 4 – histograma abaixo de LowLevel2 (baixa forte).

Regras de sinal

A estratégia lê duas cores históricas por avaliação: barra SignalBar + 1 (mais antiga) e barra SignalBar (mais recente).

  • Abrir comprado primário (volume = PrimaryVolume) quando a cor da barra mais antiga é 1 e a cor da barra mais nova se move para 2, 3 ou 4. O movimento simultaneamente solicita o fechamento de posições vendidas.
  • Abrir comprado secundário (volume = SecondaryVolume) quando a cor da barra mais antiga é 0 e a cor da barra mais nova se torna qualquer coisa diferente de 0. O mesmo sinal também fecha vendidos.
  • Abrir vendido primário (volume = PrimaryVolume) quando a cor da barra mais antiga é 3 e a cor da barra mais nova sobe para 0, 1 ou 2, enquanto também fecha comprados.
  • Abrir vendido secundário (volume = SecondaryVolume) quando a cor da barra mais antiga é 4 e a cor da barra mais nova se torna 0, 1, 2 ou 3, novamente forçando saídas compradas.
  • Fechar comprados sempre que a cor mais antiga for 3 ou 4 (zona de baixa).
  • Fechar vendidos sempre que a cor mais antiga for 0 ou 1 (zona de alta).

Dois slots de posição independentes são mantidos para cada direção. Um sinal só aciona uma ordem se o slot correspondente estiver atualmente inativo e o indicador de entrada relevante (AllowLongEntry, AllowShortEntry) permitir.

Gestão de risco

  • StopLossSteps e TakeProfitSteps são traduzidos para ordens protetoras do StockSharp via StartProtection. Os valores são expressos em passos de preço do instrumento.
  • DeviationSteps é preservado para compatibilidade com a lista de entradas MQL. As ordens de mercado do StockSharp não o utilizam.

Parâmetros

Nome Descrição
CandleType Período usado para construir as velas fornecidas ao indicador.
PrimaryVolume, SecondaryVolume Volumes aplicados pelos slots de nível um e nível dois.
AllowLongEntry, AllowShortEntry Habilitar abertura de novas posições compradas ou vendidas.
AllowLongExit, AllowShortExit Habilitar fechamento de exposição comprada ou vendida quando aparecerem sinais de saída.
StopLossSteps, TakeProfitSteps Distâncias protetoras opcionais em passos de preço (0 desabilita a proteção respectiva).
DeviationSteps Reservado para compatibilidade; não tem efeito nas ordens do StockSharp.
SignalBar Número de velas fechadas para deslocar a avaliação de sinal (0 = última vela finalizada).
WprPeriod Período de retrospectiva para o cálculo do Williams %R.
VolumeMode Seleciona entre contagem de ticks (Tick) ou volume real (Real) no histograma.
HighLevel2, HighLevel1 Multiplicadores que definem os limiares de alta superiores.
LowLevel1, LowLevel2 Multiplicadores que definem os limiares de baixa inferiores.
SmoothingMethod Tipo de média móvel usada tanto para o histograma quanto para o volume de referência.
SmoothingLength Comprimento dos filtros de suavização.
SmoothingPhase Fase encaminhada para suavizadores baseados em Jurik (ignorada por outros métodos).

Notas de uso

  • A estratégia opera em um único ativo retornado por GetWorkingSecurities() e usa ordens de mercado para todas as ações.
  • Os sinais são avaliados uma vez por vela finalizada. O buffer de histórico adicional evita ordens duplicadas na mesma barra.
  • Os dois slots de entrada atuam de forma independente. Desabilite um slot definindo o volume correspondente como 0 ou desabilitando o indicador Allow*Entry.
  • A conversão não replica os números mágicos do MetaTrader nem os modos de margem. O dimensionamento do portfólio é inteiramente controlado pelos parâmetros PrimaryVolume e SecondaryVolume.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Strategy converted from the MetaTrader expert Exp_XWPR_Histogram_Vol.
/// Computes a volume-weighted Williams %R histogram inline and trades on strong colour transitions.
/// </summary>
public class ExpXwprHistogramVolStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprPeriod;
	private readonly StrategyParam<int> _smoothingLength;
	private readonly StrategyParam<decimal> _highLevel2;
	private readonly StrategyParam<decimal> _lowLevel2;
	private readonly StrategyParam<int> _signalCooldownBars;

	private WilliamsR _wpr;
	private SimpleMovingAverage _histSma;
	private SimpleMovingAverage _volSma;
	private int? _prevColor;
	private int _cooldownRemaining;
	private DateTimeOffset? _lastEntryTime;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
	public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
	public decimal HighLevel2 { get => _highLevel2.Value; set => _highLevel2.Value = value; }
	public decimal LowLevel2 { get => _lowLevel2.Value; set => _lowLevel2.Value = value; }
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }

	public ExpXwprHistogramVolStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_wprPeriod = Param(nameof(WprPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("WPR Period", "Williams %R lookback", "Indicator");

		_smoothingLength = Param(nameof(SmoothingLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Smoothing", "Smoothing length", "Indicator");

		_highLevel2 = Param(nameof(HighLevel2), 17m)
			.SetDisplay("High Level 2", "Strong bullish zone", "Indicator");

		_lowLevel2 = Param(nameof(LowLevel2), -17m)
			.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 48)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_wpr = null;
		_histSma = null;
		_volSma = null;
		_prevColor = null;
		_cooldownRemaining = 0;
		_lastEntryTime = null;
	}

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

		_prevColor = null;
		_cooldownRemaining = 0;
		_lastEntryTime = null;

		_wpr = new WilliamsR { Length = WprPeriod };
		_histSma = new SimpleMovingAverage { Length = SmoothingLength };
		_volSma = new SimpleMovingAverage { Length = SmoothingLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var wprValue = _wpr.Process(candle);
		if (!wprValue.IsFormed)
			return;

		var wpr = wprValue.ToDecimal();
		var volume = candle.TotalVolume > 0m ? candle.TotalVolume : 1m;
		var histRaw = (wpr + 50m) * volume;
		var histSmoothed = _histSma.Process(new DecimalIndicatorValue(_histSma, histRaw, candle.OpenTime) { IsFinal = true });
		var volSmoothed = _volSma.Process(new DecimalIndicatorValue(_volSma, volume, candle.OpenTime) { IsFinal = true });

		if (!histSmoothed.IsFormed || !volSmoothed.IsFormed)
			return;

		var baseline = volSmoothed.ToDecimal();
		if (baseline == 0m)
			return;

		var hist = histSmoothed.ToDecimal();
		var strongBullLevel = HighLevel2 * baseline;
		var strongBearLevel = LowLevel2 * baseline;

		var color = hist >= strongBullLevel ? 0 : hist <= strongBearLevel ? 4 : 2;

		if (_prevColor == null)
		{
			_prevColor = color;
			return;
		}

		var previousColor = _prevColor.Value;
		_prevColor = color;

		if (_cooldownRemaining > 0 || HasRecentEntry(candle))
			return;

		if (previousColor != 0 && color == 0 && Position <= 0)
		{
			var volumeToBuy = Volume + Math.Abs(Position);
			BuyMarket(volumeToBuy);
			_cooldownRemaining = SignalCooldownBars;
			_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		}
		else if (previousColor != 4 && color == 4 && Position >= 0)
		{
			var volumeToSell = Volume + Math.Abs(Position);
			SellMarket(volumeToSell);
			_cooldownRemaining = SignalCooldownBars;
			_lastEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		}
	}

	private bool HasRecentEntry(ICandleMessage candle)
	{
		if (!_lastEntryTime.HasValue)
			return false;

		var candleTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
		return candleTime.Date == _lastEntryTime.Value.Date;
	}
}