Fractal WPR Strategy
This strategy uses the Williams %R oscillator to generate trading signals based on crossings of overbought and oversold levels. It is adapted from an MQL5 expert advisor and demonstrates a simple momentum reversal system.
How It Works
- A Williams %R indicator with configurable period is calculated on the selected timeframe.
- Two horizontal levels define extreme zones:
HighLevelmarks the overbought area (default −30).LowLevelmarks the oversold area (default −70).
- When
Trendis set toDirect:- Crossing downward through
LowLevelopens a long position and closes any short position. - Crossing upward through
HighLevelopens a short position and closes any long position.
- Crossing downward through
- When
Trendis set toAgainst, the reactions to crossings are reversed. - Optional parameters allow enabling or disabling opening and closing of long or short positions separately.
- Stop‑loss and take‑profit distances in ticks are applied using the high‑level protection API.
Only completed candles are processed to avoid reacting to intrabar noise.
Parameters
WprPeriod– Williams %R calculation period.HighLevel– threshold for the overbought zone.LowLevel– threshold for the oversold zone.Trend– trading mode (DirectorAgainst).BuyPositionOpen– allow opening long positions.SellPositionOpen– allow opening short positions.BuyPositionClose– allow closing long positions.SellPositionClose– allow closing short positions.StopLossTicks– stop‑loss distance in ticks.TakeProfitTicks– take‑profit distance in ticks.CandleType– candle timeframe used for analysis.
Indicators
- Williams %R
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy using Williams %R crossings with optional trend direction.
/// </summary>
public class FractalWprStrategy : Strategy
{
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<TrendModes> _trend;
private readonly StrategyParam<bool> _buyPositionOpen;
private readonly StrategyParam<bool> _sellPositionOpen;
private readonly StrategyParam<bool> _buyPositionClose;
private readonly StrategyParam<bool> _sellPositionClose;
private readonly StrategyParam<int> _stopLossTicks;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<DataType> _candleType;
private WilliamsR _wpr;
private decimal? _prevWpr;
/// <summary>
/// Williams %R calculation period.
/// </summary>
public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
/// <summary>
/// Overbought threshold.
/// </summary>
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
/// <summary>
/// Oversold threshold.
/// </summary>
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
/// <summary>
/// Trading direction mode.
/// </summary>
public TrendModes Trend { get => _trend.Value; set => _trend.Value = value; }
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPositionOpen { get => _buyPositionOpen.Value; set => _buyPositionOpen.Value = value; }
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPositionOpen { get => _sellPositionOpen.Value; set => _sellPositionOpen.Value = value; }
/// <summary>
/// Allow closing long positions.
/// </summary>
public bool BuyPositionClose { get => _buyPositionClose.Value; set => _buyPositionClose.Value = value; }
/// <summary>
/// Allow closing short positions.
/// </summary>
public bool SellPositionClose { get => _sellPositionClose.Value; set => _sellPositionClose.Value = value; }
/// <summary>
/// Stop loss distance in ticks.
/// </summary>
public int StopLossTicks { get => _stopLossTicks.Value; set => _stopLossTicks.Value = value; }
/// <summary>
/// Take profit distance in ticks.
/// </summary>
public int TakeProfitTicks { get => _takeProfitTicks.Value; set => _takeProfitTicks.Value = value; }
/// <summary>
/// Candle type and timeframe.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="FractalWprStrategy"/> class.
/// </summary>
public FractalWprStrategy()
{
_wprPeriod = Param(nameof(WprPeriod), 30)
.SetDisplay("WPR Period", "Williams %R calculation period", "Indicators")
.SetGreaterThanZero()
;
_highLevel = Param(nameof(HighLevel), -30m)
.SetDisplay("High Level", "Overbought threshold", "Levels");
_lowLevel = Param(nameof(LowLevel), -70m)
.SetDisplay("Low Level", "Oversold threshold", "Levels");
_trend = Param(nameof(Trend), TrendModes.Direct)
.SetDisplay("Trend Mode", "Trading direction mode", "General");
_buyPositionOpen = Param(nameof(BuyPositionOpen), true)
.SetDisplay("Buy Open", "Allow opening long positions", "Permissions");
_sellPositionOpen = Param(nameof(SellPositionOpen), true)
.SetDisplay("Sell Open", "Allow opening short positions", "Permissions");
_buyPositionClose = Param(nameof(BuyPositionClose), true)
.SetDisplay("Buy Close", "Allow closing long positions", "Permissions");
_sellPositionClose = Param(nameof(SellPositionClose), true)
.SetDisplay("Sell Close", "Allow closing short positions", "Permissions");
_stopLossTicks = Param(nameof(StopLossTicks), 1000)
.SetDisplay("Stop Loss", "Stop loss distance in ticks", "Protection")
.SetGreaterThanZero();
_takeProfitTicks = Param(nameof(TakeProfitTicks), 2000)
.SetDisplay("Take Profit", "Take profit distance in ticks", "Protection")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_wpr = null!;
_prevWpr = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_wpr = new WilliamsR { Length = WprPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_wpr, ProcessCandle)
.Start();
var step = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 1m;
StartProtection(
stopLoss: new Unit(step * StopLossTicks, UnitTypes.Absolute),
takeProfit: new Unit(step * TakeProfitTicks, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _wpr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue wprValue)
{
if (candle.State != CandleStates.Finished)
return;
var wpr = wprValue.ToDecimal();
if (_prevWpr.HasValue && IsFormedAndOnlineAndAllowTrading())
{
if (Trend == TrendModes.Direct)
{
if (_prevWpr > LowLevel && wpr <= LowLevel)
{
if (BuyPositionOpen && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
if (SellPositionClose && Position < 0)
BuyMarket(Math.Abs(Position));
}
if (_prevWpr < HighLevel && wpr >= HighLevel)
{
if (SellPositionOpen && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
if (BuyPositionClose && Position > 0)
SellMarket(Position);
}
}
else
{
if (_prevWpr > LowLevel && wpr <= LowLevel)
{
if (SellPositionOpen && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
if (BuyPositionClose && Position > 0)
SellMarket(Position);
}
if (_prevWpr < HighLevel && wpr >= HighLevel)
{
if (BuyPositionOpen && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
if (SellPositionClose && Position < 0)
BuyMarket(Math.Abs(Position));
}
}
}
_prevWpr = wpr;
}
/// <summary>
/// Trend trading modes.
/// </summary>
public enum TrendModes
{
Direct,
Against
}
}
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
from StockSharp.Algo.Strategies import Strategy
class fractal_wpr_strategy(Strategy):
"""
Fractal WPR: Williams %R level crossings with directional mode.
Direct mode: buy on oversold crossing, sell on overbought.
Against mode: reverse signals.
Uses StartProtection for SL/TP.
"""
def __init__(self):
super(fractal_wpr_strategy, self).__init__()
self._wpr_period = self.Param("WprPeriod", 30) \
.SetDisplay("WPR Period", "Williams %R calculation period", "Indicators")
self._high_level = self.Param("HighLevel", -30.0) \
.SetDisplay("High Level", "Overbought threshold", "Levels")
self._low_level = self.Param("LowLevel", -70.0) \
.SetDisplay("Low Level", "Oversold threshold", "Levels")
self._trend = self.Param("Trend", 0) \
.SetDisplay("Trend Mode", "0=Direct, 1=Against", "General")
self._stop_loss_ticks = self.Param("StopLossTicks", 1000) \
.SetDisplay("Stop Loss", "Stop loss distance in ticks", "Protection")
self._take_profit_ticks = self.Param("TakeProfitTicks", 2000) \
.SetDisplay("Take Profit", "Take profit distance in ticks", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_wpr = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fractal_wpr_strategy, self).OnReseted()
self._prev_wpr = None
def OnStarted2(self, time):
super(fractal_wpr_strategy, self).OnStarted2(time)
wpr = WilliamsR()
wpr.Length = self._wpr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(wpr, self._process_candle).Start()
ps = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps <= 0:
ps = 1.0
sl_dist = ps * self._stop_loss_ticks.Value
tp_dist = ps * self._take_profit_ticks.Value
self.StartProtection(
Unit(tp_dist, UnitTypes.Absolute),
Unit(sl_dist, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, wpr)
self.DrawOwnTrades(area)
def _process_candle(self, candle, wpr_value):
if candle.State != CandleStates.Finished:
return
wpr = float(wpr_value)
if self._prev_wpr is not None and self.IsFormedAndOnlineAndAllowTrading():
high_level = self._high_level.Value
low_level = self._low_level.Value
is_direct = self._trend.Value == 0
if is_direct:
if self._prev_wpr > low_level and wpr <= low_level:
if self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
if self._prev_wpr < high_level and wpr >= high_level:
if self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
else:
if self._prev_wpr > low_level and wpr <= low_level:
if self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
if self._prev_wpr < high_level and wpr >= high_level:
if self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._prev_wpr = wpr
def CreateClone(self):
return fractal_wpr_strategy()