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
- Williams %R (
WprPeriod) is shifted by +50 and multiplied by the selected candle volume (VolumeMode).
- Both the weighted Williams %R and the raw volume pass through identical smoothing filters (
SmoothingMethod,
SmoothingLength, SmoothingPhase).
- Four dynamic levels are derived from the smoothed volume:
HighLevel2, HighLevel1, LowLevel1 and LowLevel2.
- 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CandleIndicatorValue, SimpleMovingAverage, WilliamsR
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_xwpr_histogram_vol_strategy(Strategy):
def __init__(self):
super(exp_xwpr_histogram_vol_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._wpr_period = self.Param("WprPeriod", 7) \
.SetDisplay("WPR Period", "Williams %R lookback", "Indicator")
self._smoothing_length = self.Param("SmoothingLength", 5) \
.SetDisplay("Smoothing", "Smoothing length", "Indicator")
self._high_level2 = self.Param("HighLevel2", Decimal(17)) \
.SetDisplay("High Level 2", "Strong bullish zone", "Indicator")
self._low_level2 = self.Param("LowLevel2", Decimal(-17)) \
.SetDisplay("Low Level 2", "Strong bearish zone", "Indicator")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 48) \
.SetDisplay("Signal Cooldown", "Bars to wait after a new entry", "Trading")
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def WprPeriod(self):
return self._wpr_period.Value
@property
def SmoothingLength(self):
return self._smoothing_length.Value
@property
def HighLevel2(self):
return self._high_level2.Value
@property
def LowLevel2(self):
return self._low_level2.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(exp_xwpr_histogram_vol_strategy, self).OnReseted()
self._wpr = None
self._hist_sma = None
self._vol_sma = None
self._prev_color = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(exp_xwpr_histogram_vol_strategy, self).OnStarted2(time)
self._prev_color = None
self._cooldown_remaining = 0
self._wpr = WilliamsR()
self._wpr.Length = self.WprPeriod
self._hist_sma = SimpleMovingAverage()
self._hist_sma.Length = self.SmoothingLength
self._vol_sma = SimpleMovingAverage()
self._vol_sma.Length = self.SmoothingLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
wpr_result = self._wpr.Process(CandleIndicatorValue(self._wpr, candle))
if not wpr_result.IsFormed:
return
wpr = wpr_result.Value
volume = candle.TotalVolume if candle.TotalVolume > Decimal(0) else Decimal(1)
hist_raw = (wpr + Decimal(50)) * volume
hist_smoothed = process_float(self._hist_sma, hist_raw, candle.OpenTime, True)
vol_smoothed = process_float(self._vol_sma, volume, candle.OpenTime, True)
if not hist_smoothed.IsFormed or not vol_smoothed.IsFormed:
return
baseline = vol_smoothed.Value
if baseline == Decimal(0):
return
hist = hist_smoothed.Value
strong_bull_level = self.HighLevel2 * baseline
strong_bear_level = self.LowLevel2 * baseline
if hist >= strong_bull_level:
color = 0
elif hist <= strong_bear_level:
color = 4
else:
color = 2
if self._prev_color is None:
self._prev_color = color
return
previous_color = self._prev_color
self._prev_color = color
if self._cooldown_remaining > 0:
return
if previous_color != 0 and color == 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.SignalCooldownBars
elif previous_color != 4 and color == 4 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.SignalCooldownBars
def CreateClone(self):
return exp_xwpr_histogram_vol_strategy()