Ver no GitHub

Exp XWPR Histogram Vol Strategy

Overview

This strategy is a C# conversion of the MetaTrader expert Exp_XWPR_Histogram_Vol. It trades on the colour changes of the XWPR Histogram Vol custom indicator, which multiplies the Williams %R oscillator by the candle volume and smooths the result. The port keeps the original two-slot money management scheme (primary and secondary volume) and reproduces the same colour-driven entry and exit rules while using the StockSharp high-level API.

The algorithm processes finished candles only. On each new bar it inspects the histogram colour a configurable number of bars ahead in the past and reacts when the colour transitions cross the bullish or bearish thresholds defined by the indicator.

Indicator logic

  1. Williams %R (WprPeriod) is shifted by +50 and multiplied by the selected candle volume (VolumeMode).
  2. Both the weighted Williams %R and the raw volume pass through identical smoothing filters (SmoothingMethod, SmoothingLength, SmoothingPhase).
  3. Four dynamic levels are derived from the smoothed volume: HighLevel2, HighLevel1, LowLevel1 and LowLevel2.
  4. Histogram colours correspond to the zones defined by those levels:
    • 0 – histogram above HighLevel2 (strong bullish).
    • 1 – histogram between HighLevel1 and HighLevel2 (moderate bullish).
    • 2 – histogram between LowLevel1 and HighLevel1 (neutral).
    • 3 – histogram between LowLevel2 and LowLevel1 (moderate bearish).
    • 4 – histogram below LowLevel2 (strong bearish).

Signal rules

The strategy reads two historical colours per evaluation: bar SignalBar + 1 (older) and bar SignalBar (more recent).

  • Open primary long (volume = PrimaryVolume) when the older bar colour is 1 and the newer bar colour moves to 2, 3 or 4. The move simultaneously requests the closing of any short positions.
  • Open secondary long (volume = SecondaryVolume) when the older bar colour is 0 and the newer bar colour becomes anything other than 0. The same signal also closes shorts.
  • Open primary short (volume = PrimaryVolume) when the older bar colour is 3 and the newer bar colour rises to 0, 1 or 2, while also closing longs.
  • Open secondary short (volume = SecondaryVolume) when the older bar colour is 4 and the newer bar colour becomes 0, 1, 2 or 3, again forcing long exits.
  • Close longs whenever the older colour is 3 or 4 (bearish zone).
  • Close shorts whenever the older colour is 0 or 1 (bullish zone).

Two independent position slots are maintained for each direction. A signal only triggers an order if the corresponding slot is currently inactive and the relevant entry flag (AllowLongEntry, AllowShortEntry) permits it.

Risk management

  • StopLossSteps and TakeProfitSteps are translated to StockSharp protective orders via StartProtection. The values are expressed in instrument price steps.
  • DeviationSteps is preserved for compatibility with the MQL input list. StockSharp market orders do not use it.

Parameters

Name Description
CandleType Timeframe used to build the candles supplied to the indicator.
PrimaryVolume, SecondaryVolume Volumes applied by the level-one and level-two slots.
AllowLongEntry, AllowShortEntry Enable opening new long or short positions.
AllowLongExit, AllowShortExit Enable closing long or short exposure when exit signals appear.
StopLossSteps, TakeProfitSteps Optional protective distances in price steps (0 disables the respective protection).
DeviationSteps Reserved for compatibility; has no effect on StockSharp orders.
SignalBar Number of closed candles to shift the signal evaluation (0 = latest finished candle).
WprPeriod Lookback period for the Williams %R calculation.
VolumeMode Selects between tick count (Tick) or real volume (Real) in the histogram.
HighLevel2, HighLevel1 Multipliers defining the upper bullish thresholds.
LowLevel1, LowLevel2 Multipliers defining the lower bearish thresholds.
SmoothingMethod Moving average type used for both the histogram and the baseline volume.
SmoothingLength Length of the smoothing filters.
SmoothingPhase Phase forwarded to Jurik-based smoothers (ignored by other methods).

Usage notes

  • The strategy trades a single security returned by GetWorkingSecurities() and uses market orders for all actions.
  • Signals are evaluated once per finished candle. The additional history buffer prevents duplicate orders on the same bar.
  • The two entry slots act independently. Disable a slot by setting the corresponding volume to 0 or disabling the Allow*Entry flag.
  • The conversion does not replicate MetaTrader magic numbers or margin modes. Portfolio sizing is entirely controlled by the PrimaryVolume and SecondaryVolume parameters.
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;
	}
}