Ma Williams R Strategy
Implementation of strategy - MA + Williams %R. Buy when price is above MA and Williams %R is below -80 (oversold). Sell when price is below MA and Williams %R is above -20 (overbought).
Testing indicates an average annual return of about 79%. It performs best in the stocks market.
The moving average shows the prevailing trend direction. Williams %R looks for overbought or oversold points relative to that trend.
Fits swing traders waiting for pullbacks toward the average. Stop-loss distance comes from ATR.
Details
- Entry Criteria:
- Long:
Close > MA && WilliamsR < WilliamsROversold - Short:
Close < MA && WilliamsR > WilliamsROverbought
- Long:
- Long/Short: Both
- Exit Criteria:
- Williams %R returns to middle
- Stops: Percent-based using
StopLoss - Default Values:
MaPeriod= 20MaType= MovingAverageTypeEnum.SimpleWilliamsRPeriod= 14WilliamsROversold= -80mWilliamsROverbought= -20mStopLoss= new Unit(2, UnitTypes.Percent)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Moving Average, Williams %R, R
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Implementation of strategy - MA + Williams %R.
/// Buy when price is above MA and Williams %R is below -80 (oversold).
/// Sell when price is below MA and Williams %R is above -20 (overbought).
/// </summary>
public class MaWilliamsRStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<MovingAverageTypes> _maType;
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<decimal> _williamsROversold;
private readonly StrategyParam<decimal> _williamsROverbought;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<Unit> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private int _cooldown;
/// <summary>
/// Moving Average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Moving Average type.
/// </summary>
public MovingAverageTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Williams %R period.
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Williams %R oversold level (usually below -80).
/// </summary>
public decimal WilliamsROversold
{
get => _williamsROversold.Value;
set => _williamsROversold.Value = value;
}
/// <summary>
/// Williams %R overbought level (usually above -20).
/// </summary>
public decimal WilliamsROverbought
{
get => _williamsROverbought.Value;
set => _williamsROverbought.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss value.
/// </summary>
public Unit StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Candle type used for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="MaWilliamsRStrategy"/>.
/// </summary>
public MaWilliamsRStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for Moving Average", "MA Parameters");
_maType = Param(nameof(MaType), MovingAverageTypes.Simple)
.SetDisplay("MA Type", "Type of Moving Average", "MA Parameters");
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R", "Williams %R Parameters");
_williamsROversold = Param(nameof(WilliamsROversold), -70m)
.SetRange(-100, 0)
.SetDisplay("Williams %R Oversold", "Williams %R level to consider market oversold", "Williams %R Parameters");
_williamsROverbought = Param(nameof(WilliamsROverbought), -30m)
.SetRange(-100, 0)
.SetDisplay("Williams %R Overbought", "Williams %R level to consider market overbought", "Williams %R Parameters");
_cooldownBars = Param(nameof(CooldownBars), 120)
.SetRange(5, 500)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLoss = Param(nameof(StopLoss), new Unit(2, UnitTypes.Percent))
.SetDisplay("Stop Loss", "Stop loss percent or value", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
DecimalLengthIndicator ma;
// Create MA based on selected type
switch (MaType)
{
case MovingAverageTypes.Exponential:
ma = new EMA { Length = MaPeriod };
break;
case MovingAverageTypes.Weighted:
ma = new WeightedMovingAverage { Length = MaPeriod };
break;
case MovingAverageTypes.Smoothed:
ma = new SmoothedMovingAverage { Length = MaPeriod };
break;
case MovingAverageTypes.HullMA:
ma = new HullMovingAverage { Length = MaPeriod };
break;
case MovingAverageTypes.Simple:
default:
ma = new SMA { Length = MaPeriod };
break;
}
var williamsR = new WilliamsR { Length = WilliamsRPeriod };
// Setup candle subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to candles
subscription
.Bind(ma, williamsR, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
// Create separate area for Williams %R
var oscillatorArea = CreateChartArea();
if (oscillatorArea != null)
{
DrawIndicator(oscillatorArea, williamsR);
}
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal williamsRValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Current price
var price = candle.ClosePrice;
// Determine if price is above or below MA
var isPriceAboveMA = price > maValue;
LogInfo($"Candle: {candle.OpenTime}, Close: {price}, " +
$"MA: {maValue}, Price > MA: {isPriceAboveMA}, " +
$"Williams %R: {williamsRValue}");
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Trading rules
if (isPriceAboveMA && williamsRValue <= WilliamsROversold && Position == 0)
{
// Buy signal - price above MA and Williams %R oversold
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Buy signal: Price above MA and Williams %R oversold ({williamsRValue} <= {WilliamsROversold}).");
}
else if (!isPriceAboveMA && williamsRValue >= WilliamsROverbought && Position == 0)
{
// Sell signal - price below MA and Williams %R overbought
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Sell signal: Price below MA and Williams %R overbought ({williamsRValue} >= {WilliamsROverbought}).");
}
// Exit conditions
else if (!isPriceAboveMA && Position > 0)
{
// Exit long position when price falls below MA
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Exit long: Price fell below MA. Position: {Position}");
}
else if (isPriceAboveMA && Position < 0)
{
// Exit short position when price rises above MA
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Exit short: Price rose above MA. Position: {Position}");
}
}
/// <summary>
/// Enum for Moving Average types.
/// </summary>
public enum MovingAverageTypes
{
/// <summary>
/// Simple Moving Average
/// </summary>
Simple,
/// <summary>
/// Exponential Moving Average
/// </summary>
Exponential,
/// <summary>
/// Weighted Moving Average
/// </summary>
Weighted,
/// <summary>
/// Smoothed Moving Average
/// </summary>
Smoothed,
/// <summary>
/// Hull Moving Average
/// </summary>
HullMA
}
}
import clr
clr.AddReference("System.Drawing")
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from System.Drawing import Color
from StockSharp.Messages import UnitTypes, Unit, DataType, ICandleMessage, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage, WeightedMovingAverage, SmoothedMovingAverage, HullMovingAverage, WilliamsR
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class MovingAverageTypeEnum:
"""Enum for Moving Average types."""
Simple = 0
Exponential = 1
Weighted = 2
Smoothed = 3
HullMA = 4
class ma_williams_r_strategy(Strategy):
"""
Implementation of strategy - MA + Williams %R.
Buy when price is above MA and Williams %R is below -70 (oversold).
Sell when price is below MA and Williams %R is above -30 (overbought).
"""
def __init__(self):
super(ma_williams_r_strategy, self).__init__()
# Initialize strategy parameters
self._maPeriod = self.Param("MaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Period for Moving Average", "MA Parameters")
self._maType = self.Param("MaType", MovingAverageTypeEnum.Simple) \
.SetDisplay("MA Type", "Type of Moving Average", "MA Parameters")
self._williamsRPeriod = self.Param("WilliamsRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Williams %R Period", "Period for Williams %R", "Williams %R Parameters")
self._williamsROversold = self.Param("WilliamsROversold", -70.0) \
.SetRange(-100, 0) \
.SetDisplay("Williams %R Oversold", "Williams %R level to consider market oversold", "Williams %R Parameters")
self._williamsROverbought = self.Param("WilliamsROverbought", -30.0) \
.SetRange(-100, 0) \
.SetDisplay("Williams %R Overbought", "Williams %R level to consider market overbought", "Williams %R Parameters")
self._cooldownBars = self.Param("CooldownBars", 120) \
.SetRange(5, 500) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stopLoss = self.Param("StopLoss", Unit(2, UnitTypes.Percent)) \
.SetDisplay("Stop Loss", "Stop loss percent or value", "Risk Management")
self._candleType = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._cooldown = 0
@property
def MaPeriod(self):
return self._maPeriod.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._maPeriod.Value = value
@property
def MaType(self):
return self._maType.Value
@MaType.setter
def MaType(self, value):
self._maType.Value = value
@property
def WilliamsRPeriod(self):
return self._williamsRPeriod.Value
@WilliamsRPeriod.setter
def WilliamsRPeriod(self, value):
self._williamsRPeriod.Value = value
@property
def WilliamsROversold(self):
return self._williamsROversold.Value
@WilliamsROversold.setter
def WilliamsROversold(self, value):
self._williamsROversold.Value = value
@property
def WilliamsROverbought(self):
return self._williamsROverbought.Value
@WilliamsROverbought.setter
def WilliamsROverbought(self, value):
self._williamsROverbought.Value = value
@property
def CooldownBars(self):
return self._cooldownBars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldownBars.Value = value
@property
def StopLoss(self):
return self._stopLoss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stopLoss.Value = value
@property
def CandleType(self):
return self._candleType.Value
@CandleType.setter
def CandleType(self, value):
self._candleType.Value = value
def OnReseted(self):
super(ma_williams_r_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(ma_williams_r_strategy, self).OnStarted2(time)
# Create MA based on selected type
if self.MaType == MovingAverageTypeEnum.Exponential:
ma = ExponentialMovingAverage()
ma.Length = self.MaPeriod
elif self.MaType == MovingAverageTypeEnum.Weighted:
ma = WeightedMovingAverage()
ma.Length = self.MaPeriod
elif self.MaType == MovingAverageTypeEnum.Smoothed:
ma = SmoothedMovingAverage()
ma.Length = self.MaPeriod
elif self.MaType == MovingAverageTypeEnum.HullMA:
ma = HullMovingAverage()
ma.Length = self.MaPeriod
else:
ma = SimpleMovingAverage()
ma.Length = self.MaPeriod
williamsR = WilliamsR()
williamsR.Length = self.WilliamsRPeriod
# Setup candle subscription
subscription = self.SubscribeCandles(self.CandleType)
# Bind indicators to candles
subscription.Bind(ma, williamsR, self.ProcessCandle).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
oscillatorArea = self.CreateChartArea()
if oscillatorArea is not None:
self.DrawIndicator(oscillatorArea, williamsR)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, maValue, williamsRValue):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
# Current price
price = float(candle.ClosePrice)
# Determine if price is above or below MA
isPriceAboveMA = price > maValue
if self._cooldown > 0:
self._cooldown -= 1
return
# Trading rules
if isPriceAboveMA and williamsRValue <= self.WilliamsROversold and self.Position == 0:
# Buy signal - price above MA and Williams %R oversold
self.BuyMarket()
self._cooldown = self.CooldownBars
elif not isPriceAboveMA and williamsRValue >= self.WilliamsROverbought and self.Position == 0:
# Sell signal - price below MA and Williams %R overbought
self.SellMarket()
self._cooldown = self.CooldownBars
# Exit conditions
elif not isPriceAboveMA and self.Position > 0:
# Exit long position when price falls below MA
self.SellMarket()
self._cooldown = self.CooldownBars
elif isPriceAboveMA and self.Position < 0:
# Exit short position when price rises above MA
self.BuyMarket()
self._cooldown = self.CooldownBars
def CreateClone(self):
return ma_williams_r_strategy()