Ver en GitHub

Estrategia Exp XWPR Histograma Vol

Descripción general

Esta estrategia es una conversión en C# del experto de MetaTrader Exp_XWPR_Histogram_Vol. Opera en los cambios de color del indicador personalizado XWPR Histograma Vol, que multiplica el oscilador Williams %R por el volumen de la vela y suaviza el resultado. El port mantiene el esquema original de gestión de dinero de dos slots (volumen primario y secundario) y reproduce las mismas reglas de entrada y salida basadas en color usando la API de alto nivel de StockSharp.

El algoritmo procesa solo velas finalizadas. En cada nueva barra, inspecciona el color del histograma un número configurable de barras en el pasado y reacciona cuando las transiciones de color cruzan los umbrales alcistas o bajistas definidos por el indicador.

Lógica del indicador

  1. Williams %R (WprPeriod) se desplaza en +50 y se multiplica por el volumen de vela seleccionado (VolumeMode).
  2. Tanto el Williams %R ponderado como el volumen bruto pasan por filtros de suavizado idénticos (SmoothingMethod, SmoothingLength, SmoothingPhase).
  3. Se derivan cuatro niveles dinámicos del volumen suavizado: HighLevel2, HighLevel1, LowLevel1 y LowLevel2.
  4. Los colores del histograma corresponden a las zonas definidas por esos niveles:
    • 0 – histograma por encima de HighLevel2 (alcista fuerte).
    • 1 – histograma entre HighLevel1 y HighLevel2 (alcista moderado).
    • 2 – histograma entre LowLevel1 y HighLevel1 (neutral).
    • 3 – histograma entre LowLevel2 y LowLevel1 (bajista moderado).
    • 4 – histograma por debajo de LowLevel2 (bajista fuerte).

Reglas de señal

La estrategia lee dos colores históricos por evaluación: barra SignalBar + 1 (más antigua) y barra SignalBar (más reciente).

  • Abrir largo primario (volumen = PrimaryVolume) cuando el color de la barra más antigua es 1 y el color de la barra más nueva se mueve a 2, 3 o 4. El movimiento simultáneamente solicita el cierre de posiciones cortas.
  • Abrir largo secundario (volumen = SecondaryVolume) cuando el color de la barra más antigua es 0 y el color de la barra más nueva se convierte en cualquier cosa distinta de 0. La misma señal también cierra cortos.
  • Abrir corto primario (volumen = PrimaryVolume) cuando el color de la barra más antigua es 3 y el color de la barra más nueva sube a 0, 1 o 2, mientras también cierra largos.
  • Abrir corto secundario (volumen = SecondaryVolume) cuando el color de la barra más antigua es 4 y el color de la barra más nueva se convierte en 0, 1, 2 o 3, nuevamente forzando salidas largas.
  • Cerrar largos cuando el color más antiguo es 3 o 4 (zona bajista).
  • Cerrar cortos cuando el color más antiguo es 0 o 1 (zona alcista).

Se mantienen dos slots de posición independientes para cada dirección. Una señal solo activa una orden si el slot correspondiente está actualmente inactivo y el indicador de entrada relevante (AllowLongEntry, AllowShortEntry) lo permite.

Gestión de riesgo

  • StopLossSteps y TakeProfitSteps se traducen a órdenes protectoras de StockSharp a través de StartProtection. Los valores se expresan en pasos de precio del instrumento.
  • DeviationSteps se conserva para compatibilidad con la lista de entradas MQL. Las órdenes de mercado de StockSharp no lo usan.

Parámetros

Nombre Descripción
CandleType Marco temporal usado para construir las velas suministradas al indicador.
PrimaryVolume, SecondaryVolume Volúmenes aplicados por los slots de nivel uno y nivel dos.
AllowLongEntry, AllowShortEntry Habilitar apertura de nuevas posiciones largas o cortas.
AllowLongExit, AllowShortExit Habilitar cierre de exposición larga o corta cuando aparezcan señales de salida.
StopLossSteps, TakeProfitSteps Distancias protectoras opcionales en pasos de precio (0 deshabilita la protección respectiva).
DeviationSteps Reservado para compatibilidad; no tiene efecto en las órdenes de StockSharp.
SignalBar Número de velas cerradas para desplazar la evaluación de señal (0 = última vela finalizada).
WprPeriod Período de retrospección para el cálculo de Williams %R.
VolumeMode Selecciona entre conteo de ticks (Tick) o volumen real (Real) en el histograma.
HighLevel2, HighLevel1 Multiplicadores que definen los umbrales alcistas superiores.
LowLevel1, LowLevel2 Multiplicadores que definen los umbrales bajistas inferiores.
SmoothingMethod Tipo de media móvil usada tanto para el histograma como para el volumen de referencia.
SmoothingLength Longitud de los filtros de suavizado.
SmoothingPhase Fase enviada a suavizadores basados en Jurik (ignorada por otros métodos).

Notas de uso

  • La estrategia opera en un único valor devuelto por GetWorkingSecurities() y usa órdenes de mercado para todas las acciones.
  • Las señales se evalúan una vez por vela finalizada. El buffer de historial adicional evita órdenes duplicadas en la misma barra.
  • Los dos slots de entrada actúan de forma independiente. Deshabilite un slot estableciendo el volumen correspondiente en 0 o deshabilitando el indicador Allow*Entry.
  • La conversión no replica los números mágicos de MetaTrader ni los modos de margen. El dimensionamiento de la cartera está completamente controlado por los parámetros PrimaryVolume y 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;
	}
}