Exp XWPR Histogram Vol Direct Strategy
Overview
This strategy is a StockSharp port of the MetaTrader expert advisor Exp_XWPR_Histogram_Vol_Direct. It reproduces the original
approach of weighting Williams %R values by volume, smoothing the result, and opening trades when the histogram slope changes
color. Orders are triggered on fully-formed candles and use optional protective stop-loss and take-profit measured in price steps.
Core Logic
- Calculate Williams %R on the selected timeframe.
- Shift the oscillator by +50, multiply it by the chosen volume source (tick or real), and smooth the stream with a configurable
moving average.
- Smooth the raw volume with the same moving average to rebuild the indicator bands (HighLevel2, HighLevel1, LowLevel1, LowLevel2).
- Track the color of the histogram slope: blue (
0) when the smoothed value rises, magenta (1) when it falls. The strategy
keeps a short history buffer to compare the last two completed colors respecting the SignalShift parameter.
- Execute actions when the previous color changes:
- Color transition
0 → 1: close shorts (if enabled) and optionally open a new long position.
- Color transition
1 → 0: close longs (if enabled) and optionally open a new short position.
The zone classification (Neutral/Bullish/Bearish/Extreme) is logged for context but does not block trades, matching the behavior of
the original advisor which reads the color buffer only.
Parameters
| Parameter |
Description |
WilliamsPeriod |
Lookback length for Williams %R. |
HighLevel2, HighLevel1, LowLevel1, LowLevel2 |
Multipliers applied to the smoothed volume to rebuild the indicator bands. |
SmoothingType |
Moving average family used for both the weighted value and the volume streams (SMA, EMA, SMMA, WMA, Hull, VWMA, DEMA, TEMA). |
SmoothingLength |
Length of the smoothing moving average. |
SignalShift |
How many bars back to read the color buffer (1 reproduces the MetaTrader default). |
EnableLongEntries / EnableShortEntries |
Allow or block opening long/short positions. |
EnableLongExits / EnableShortExits |
Allow or block closing long/short positions. |
VolumeSource |
Choose between tick count or real volume for weighting. |
StopLossPoints / TakeProfitPoints |
Optional protective targets expressed in price steps. |
CandleType |
Candle type and timeframe used for analysis and trading. |
Use the base Volume property of the strategy to define the entry size. Position reversal is handled by sending the absolute
position quantity plus the configured lot size, similar to the MQL expert advisor.
Usage Notes
- The smoothing phase (
MA_Phase in MetaTrader) is not supported because StockSharp moving averages do not expose that parameter.
- Ensure sufficient history is loaded for the chosen timeframe so that the moving averages are fully formed before trading starts.
- The strategy works on any instrument supported by StockSharp; set
CandleType to the desired resolution (for example 4-hour
time frame to match the original defaults).
- Tick-volume weighting requires data sources that provide tick counts inside candle messages. Otherwise, switch to real volume.
Logging and Visualization
The strategy draws candles and the Williams %R indicator on the default chart area. Trade actions log the detected zone and the
smoothed histogram value to aid debugging and comparison with the MetaTrader reference implementation.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Direct Williams %R histogram strategy with volume-weighted smoothing.
/// Trades only on strong bullish and bearish zone flips.
/// </summary>
public class ExpXwprHistogramVolDirectStrategy : Strategy
{
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<int> _highLevel1;
private readonly StrategyParam<int> _lowLevel1;
private readonly StrategyParam<MovingAverageKinds> _smoothingType;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<bool> _enableLongEntries;
private readonly StrategyParam<bool> _enableShortEntries;
private readonly StrategyParam<bool> _enableLongExits;
private readonly StrategyParam<bool> _enableShortExits;
private readonly StrategyParam<VolumeSources> _volumeSource;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private WilliamsR _williams;
private DecimalLengthIndicator _valueSmoother;
private DecimalLengthIndicator _volumeSmoother;
private int? _previousZone;
private int _cooldownRemaining;
public int WilliamsPeriod { get => _williamsPeriod.Value; set => _williamsPeriod.Value = value; }
public int HighLevel1 { get => _highLevel1.Value; set => _highLevel1.Value = value; }
public int LowLevel1 { get => _lowLevel1.Value; set => _lowLevel1.Value = value; }
public MovingAverageKinds SmoothingType { get => _smoothingType.Value; set => _smoothingType.Value = value; }
public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
public bool EnableLongEntries { get => _enableLongEntries.Value; set => _enableLongEntries.Value = value; }
public bool EnableShortEntries { get => _enableShortEntries.Value; set => _enableShortEntries.Value = value; }
public bool EnableLongExits { get => _enableLongExits.Value; set => _enableLongExits.Value = value; }
public bool EnableShortExits { get => _enableShortExits.Value; set => _enableShortExits.Value = value; }
public VolumeSources VolumeSource { get => _volumeSource.Value; set => _volumeSource.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ExpXwprHistogramVolDirectStrategy()
{
_williamsPeriod = Param(nameof(WilliamsPeriod), 14)
.SetRange(5, 200)
.SetDisplay("Williams %R Period", "Lookback for the Williams %R oscillator", "Indicator");
_highLevel1 = Param(nameof(HighLevel1), 1)
.SetRange(-200, 200)
.SetDisplay("High Level 1", "Bullish threshold", "Indicator");
_lowLevel1 = Param(nameof(LowLevel1), -1)
.SetRange(-200, 200)
.SetDisplay("Low Level 1", "Bearish threshold", "Indicator");
_smoothingType = Param(nameof(SmoothingType), MovingAverageKinds.Simple)
.SetDisplay("Smoothing Type", "Moving average type used for smoothing", "Indicator");
_smoothingLength = Param(nameof(SmoothingLength), 12)
.SetRange(2, 200)
.SetDisplay("Smoothing Length", "Moving average length", "Indicator");
_enableLongEntries = Param(nameof(EnableLongEntries), true)
.SetDisplay("Enable Long Entries", "Allow the strategy to open long positions", "Trading Rules");
_enableShortEntries = Param(nameof(EnableShortEntries), true)
.SetDisplay("Enable Short Entries", "Allow the strategy to open short positions", "Trading Rules");
_enableLongExits = Param(nameof(EnableLongExits), true)
.SetDisplay("Enable Long Exits", "Allow the strategy to close long positions", "Trading Rules");
_enableShortExits = Param(nameof(EnableShortExits), true)
.SetDisplay("Enable Short Exits", "Allow the strategy to close short positions", "Trading Rules");
_volumeSource = Param(nameof(VolumeSource), VolumeSources.Tick)
.SetDisplay("Volume Source", "Type of volume used for weighting", "Indicator");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 5)
.SetRange(1, 200)
.SetDisplay("Signal Cooldown", "Bars to wait between new entries", "Trading Rules");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetRange(0, 10000)
.SetDisplay("Stop Loss (ticks)", "Protective stop distance in price steps", "Risk Management");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetRange(0, 10000)
.SetDisplay("Take Profit (ticks)", "Profit target distance in price steps", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type used for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_williams = null;
_valueSmoother = null;
_volumeSmoother = null;
_previousZone = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_williams = new WilliamsR { Length = WilliamsPeriod };
_valueSmoother = CreateMovingAverage(SmoothingType, SmoothingLength);
_volumeSmoother = CreateMovingAverage(SmoothingType, SmoothingLength);
_previousZone = null;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var williamsValue = _williams.Process(candle);
if (!_williams.IsFormed)
return;
var wprValue = williamsValue.ToDecimal();
// Williams %R ranges from -100 to 0; shift to 0..100
var normalized = wprValue + 100m;
var bullishLevel = 80m;
var bearishLevel = 20m;
var zone = normalized >= bullishLevel ? 1 : normalized <= bearishLevel ? -1 : 0;
if (_previousZone == null)
{
_previousZone = zone;
return;
}
if (_previousZone.Value != zone && _cooldownRemaining == 0 && Position == 0)
{
if (zone > 0 && EnableLongEntries)
{
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (zone < 0 && EnableShortEntries)
{
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
}
_previousZone = zone;
}
private decimal GetWeightedVolume(ICandleMessage candle)
{
if (VolumeSource == VolumeSources.Tick && candle.TotalTicks is int ticks && ticks > 0)
return ticks;
return candle.TotalVolume > 0m ? candle.TotalVolume : 1m;
}
private static DecimalLengthIndicator CreateMovingAverage(MovingAverageKinds type, int length)
{
return type switch
{
MovingAverageKinds.Simple => new SMA { Length = length },
MovingAverageKinds.Exponential => new EMA { Length = length },
MovingAverageKinds.Smoothed => new SmoothedMovingAverage { Length = length },
MovingAverageKinds.Weighted => new WeightedMovingAverage { Length = length },
MovingAverageKinds.Hull => new HullMovingAverage { Length = length },
MovingAverageKinds.VolumeWeighted => new VolumeWeightedMovingAverage { Length = length },
MovingAverageKinds.DoubleExponential => new DoubleExponentialMovingAverage { Length = length },
MovingAverageKinds.TripleExponential => new TripleExponentialMovingAverage { Length = length },
_ => new SMA { Length = length },
};
}
public enum MovingAverageKinds
{
Simple,
Exponential,
Smoothed,
Weighted,
Hull,
VolumeWeighted,
DoubleExponential,
TripleExponential,
}
public enum VolumeSources
{
Tick,
Real,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import WilliamsR, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class exp_xwpr_histogram_vol_direct_strategy(Strategy):
def __init__(self):
super(exp_xwpr_histogram_vol_direct_strategy, self).__init__()
self._williams_period = self.Param("WilliamsPeriod", 14) \
.SetDisplay("Williams %R Period", "Lookback for the Williams %R oscillator", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 5) \
.SetDisplay("Signal Cooldown", "Bars to wait between new entries", "Trading Rules")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle type used for analysis", "General")
self._williams = None
self._previous_zone = None
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def WilliamsPeriod(self):
return self._williams_period.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(exp_xwpr_histogram_vol_direct_strategy, self).OnReseted()
self._williams = None
self._previous_zone = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exp_xwpr_histogram_vol_direct_strategy, self).OnStarted2(time)
self._williams = WilliamsR()
self._williams.Length = self.WilliamsPeriod
self._previous_zone = None
self._cooldown_remaining = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
self.StartProtection(Unit(2, UnitTypes.Percent), Unit(1, UnitTypes.Percent))
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
williams_value = self._williams.Process(CandleIndicatorValue(self._williams, candle))
if not self._williams.IsFormed:
return
wpr_value = float(williams_value)
normalized = wpr_value + 100.0
bullish_level = 80.0
bearish_level = 20.0
if normalized >= bullish_level:
zone = 1
elif normalized <= bearish_level:
zone = -1
else:
zone = 0
if self._previous_zone is None:
self._previous_zone = zone
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._previous_zone = zone
return
if self._previous_zone != zone and self._cooldown_remaining == 0 and self.Position == 0:
if zone > 0:
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif zone < 0:
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
self._previous_zone = zone
def CreateClone(self):
return exp_xwpr_histogram_vol_direct_strategy()