Стратегия возврата по Williams %R
Индикатор Williams %R колеблется между 0 и −100, показывая, когда цена закрывается у крайних значений недавнего диапазона. Эта стратегия продаёт такие экстремы, когда индикатор удаляется от своего среднего.
Тестирование показывает среднегодичную доходность около 154%. Стратегию лучше запускать на фондовом рынке.
Длинная сделка активируется, когда Williams %R опускается ниже среднего минус DeviationMultiplier стандартных отклонений. Короткая сделка открывается, когда показатель поднимается выше среднего плюс этот множитель. Выход происходит, когда Williams %R возвращается к своему среднему уровню.
Подход подходит трейдерам, использующим истощение импульса для входа. Защитный стоп‑лосс ограничивает риск, если цена продолжит обновлять экстремумы.
Подробности
- Условия входа:
- Long: %R < Avg − DeviationMultiplier * StdDev
- Short: %R > Avg + DeviationMultiplier * StdDev
- Long/Short: обе стороны.
- Условия выхода:
- Long: выход при %R > Avg
- Short: выход при %R < Avg
- Стопы: да, процентный стоп‑лосс.
- Параметры по умолчанию:
WilliamsRPeriod= 14AveragePeriod= 20DeviationMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Возврат к среднему
- Направление: Обе стороны
- Индикаторы: Williams %R
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Williams %R Mean Reversion strategy.
/// This strategy enters positions when Williams %R is significantly below or above its average value.
/// </summary>
public class WilliamsRMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private decimal _prevWilliamsR;
private decimal _avgWilliamsR;
private decimal _stdDevWilliamsR;
private decimal _sumWilliamsR;
private decimal _sumSquaresWilliamsR;
private int _count;
private readonly Queue<decimal> _williamsRValues = [];
/// <summary>
/// Williams %R Period.
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Period for calculating mean and standard deviation of Williams %R.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Deviation multiplier for entry signals.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public WilliamsRMeanReversionStrategy()
{
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetOptimize(7, 21, 7)
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators");
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetOptimize(10, 50, 10)
.SetDisplay("Average Period", "Period for calculating Williams %R average and standard deviation", "Settings");
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.SetGreaterThanZero()
.SetOptimize(1.5m, 3m, 0.5m)
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetOptimize(1m, 3m, 0.5m)
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevWilliamsR = 0;
_avgWilliamsR = 0;
_stdDevWilliamsR = 0;
_sumWilliamsR = 0;
_sumSquaresWilliamsR = 0;
_count = 0;
_williamsRValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
// Reset variables
// Create Williams %R indicator
var williamsR = new WilliamsR { Length = WilliamsRPeriod };
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(williamsR, ProcessCandle)
.Start();
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, williamsR);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(0m), // We'll manage exits ourselves based on Williams %R
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent)
);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue williamsRValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Extract Williams %R value
var currentWilliamsR = williamsRValue.ToDecimal();
// Update Williams %R statistics
UpdateWilliamsRStatistics(currentWilliamsR);
// Save current Williams %R for next iteration
_prevWilliamsR = currentWilliamsR;
// If we don't have enough data yet for statistics
if (_count < AveragePeriod)
return;
// Check for entry conditions
if (Position == 0)
{
// Long entry - Williams %R is significantly below its average
if (currentWilliamsR < _avgWilliamsR - DeviationMultiplier * _stdDevWilliamsR)
{
BuyMarket(Volume);
LogInfo($"Long entry: Williams %R = {currentWilliamsR}, Avg = {_avgWilliamsR}, StdDev = {_stdDevWilliamsR}");
}
// Short entry - Williams %R is significantly above its average
else if (currentWilliamsR > _avgWilliamsR + DeviationMultiplier * _stdDevWilliamsR)
{
SellMarket(Volume);
LogInfo($"Short entry: Williams %R = {currentWilliamsR}, Avg = {_avgWilliamsR}, StdDev = {_stdDevWilliamsR}");
}
}
// Check for exit conditions
else if (Position > 0) // Long position
{
if (currentWilliamsR > _avgWilliamsR)
{
ClosePosition();
LogInfo($"Long exit: Williams %R = {currentWilliamsR}, Avg = {_avgWilliamsR}");
}
}
else if (Position < 0) // Short position
{
if (currentWilliamsR < _avgWilliamsR)
{
ClosePosition();
LogInfo($"Short exit: Williams %R = {currentWilliamsR}, Avg = {_avgWilliamsR}");
}
}
}
private void UpdateWilliamsRStatistics(decimal currentWilliamsR)
{
// Add current value to the queue
_williamsRValues.Enqueue(currentWilliamsR);
_sumWilliamsR += currentWilliamsR;
_sumSquaresWilliamsR += currentWilliamsR * currentWilliamsR;
_count++;
// If queue is larger than period, remove oldest value
if (_williamsRValues.Count > AveragePeriod)
{
var oldestWilliamsR = _williamsRValues.Dequeue();
_sumWilliamsR -= oldestWilliamsR;
_sumSquaresWilliamsR -= oldestWilliamsR * oldestWilliamsR;
_count--;
}
// Calculate average and standard deviation
if (_count > 0)
{
_avgWilliamsR = _sumWilliamsR / _count;
if (_count > 1)
{
var variance = (_sumSquaresWilliamsR - (_sumWilliamsR * _sumWilliamsR) / _count) / (_count - 1);
_stdDevWilliamsR = variance <= 0 ? 0 : (decimal)Math.Sqrt((double)variance);
}
else
{
_stdDevWilliamsR = 0;
}
}
}
}
import clr
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.Collections.Generic import Queue
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class williams_r_mean_reversion_strategy(Strategy):
"""
Williams %R Mean Reversion strategy.
This strategy enters positions when Williams %R is significantly below or above its average value.
"""
def __init__(self):
super(williams_r_mean_reversion_strategy, self).__init__()
# Williams %R Period.
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(7, 21, 7) \
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
# Period for calculating mean and standard deviation of Williams %R.
self._average_period = self.Param("AveragePeriod", 20) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 10) \
.SetDisplay("Average Period", "Period for calculating Williams %R average and standard deviation", "Settings")
# Deviation multiplier for entry signals.
self._deviation_multiplier = self.Param("DeviationMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(1.5, 3.0, 0.5) \
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings")
# Candle type.
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
# Stop-loss percentage.
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5) \
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management")
# Internal statistics
self._prev_williams_r = 0.0
self._avg_williams_r = 0.0
self._std_dev_williams_r = 0.0
self._sum_williams_r = 0.0
self._sum_squares_williams_r = 0.0
self._count = 0
self._williams_r_values = Queue[float]()
@property
def WilliamsRPeriod(self):
return self._williams_r_period.Value
@WilliamsRPeriod.setter
def WilliamsRPeriod(self, value):
self._williams_r_period.Value = value
@property
def AveragePeriod(self):
return self._average_period.Value
@AveragePeriod.setter
def AveragePeriod(self, value):
self._average_period.Value = value
@property
def DeviationMultiplier(self):
return self._deviation_multiplier.Value
@DeviationMultiplier.setter
def DeviationMultiplier(self, value):
self._deviation_multiplier.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
def OnStarted2(self, time):
super(williams_r_mean_reversion_strategy, self).OnStarted2(time)
# Create Williams %R indicator
williams_r = WilliamsR()
williams_r.Length = self.WilliamsRPeriod
# Create subscription and bind indicator
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(williams_r, self.ProcessCandle).Start()
# Setup chart visualization
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, williams_r)
self.DrawOwnTrades(area)
# Enable position protection
self.StartProtection(
takeProfit=Unit(0),
stopLoss=Unit(self.StopLossPercent, UnitTypes.Percent)
)
def OnReseted(self):
super(williams_r_mean_reversion_strategy, self).OnReseted()
self._prev_williams_r = 0.0
self._avg_williams_r = 0.0
self._std_dev_williams_r = 0.0
self._sum_williams_r = 0.0
self._sum_squares_williams_r = 0.0
self._count = 0
self._williams_r_values.Clear()
def ProcessCandle(self, candle, williams_r_value):
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
# Check if strategy is ready to trade
# Extract Williams %R value
current_williams_r = float(williams_r_value)
# Update Williams %R statistics
self.UpdateWilliamsRStatistics(current_williams_r)
# Save current Williams %R for next iteration
self._prev_williams_r = current_williams_r
# If we don't have enough data yet for statistics
if self._count < self.AveragePeriod:
return
# Check for entry conditions
if self.Position == 0:
# Long entry - Williams %R is significantly below its average
if current_williams_r < self._avg_williams_r - self.DeviationMultiplier * self._std_dev_williams_r:
self.BuyMarket(self.Volume)
self.LogInfo(f"Long entry: Williams %R = {current_williams_r}, Avg = {self._avg_williams_r}, StdDev = {self._std_dev_williams_r}")
# Short entry - Williams %R is significantly above its average
elif current_williams_r > self._avg_williams_r + self.DeviationMultiplier * self._std_dev_williams_r:
self.SellMarket(self.Volume)
self.LogInfo(f"Short entry: Williams %R = {current_williams_r}, Avg = {self._avg_williams_r}, StdDev = {self._std_dev_williams_r}")
# Check for exit conditions
elif self.Position > 0: # Long position
if current_williams_r > self._avg_williams_r:
self.ClosePosition()
self.LogInfo(f"Long exit: Williams %R = {current_williams_r}, Avg = {self._avg_williams_r}")
elif self.Position < 0: # Short position
if current_williams_r < self._avg_williams_r:
self.ClosePosition()
self.LogInfo(f"Short exit: Williams %R = {current_williams_r}, Avg = {self._avg_williams_r}")
def UpdateWilliamsRStatistics(self, current_williams_r):
# Add current value to the queue
self._williams_r_values.Enqueue(current_williams_r)
self._sum_williams_r += current_williams_r
self._sum_squares_williams_r += current_williams_r * current_williams_r
self._count += 1
# If queue is larger than period, remove oldest value
if self._williams_r_values.Count > self.AveragePeriod:
oldest = self._williams_r_values.Dequeue()
self._sum_williams_r -= oldest
self._sum_squares_williams_r -= oldest * oldest
self._count -= 1
# Calculate average and standard deviation
if self._count > 0:
self._avg_williams_r = self._sum_williams_r / self._count
if self._count > 1:
variance = (self._sum_squares_williams_r - (self._sum_williams_r * self._sum_williams_r) / self._count) / (self._count - 1)
self._std_dev_williams_r = 0 if variance <= 0 else Math.Sqrt(float(variance))
else:
self._std_dev_williams_r = 0.0
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return williams_r_mean_reversion_strategy()