Стратегия прорыва Williams %R
Эта стратегия ищет всплески импульса, наблюдая за индикатором Williams %R относительно его исторического среднего. Когда осциллятор выходит далеко за пределы типичных значений, это может указывать на начало сильного движения.
Тестирование показывает среднегодичную доходность около 91%. Стратегию лучше запускать на фондовом рынке.
Длинная позиция открывается, когда %R поднимается выше среднего плюс Multiplier, умноженный на оценку стандартного отклонения. Короткая позиция берется, когда %R опускается ниже среднего минус тот же множитель. Сделка закрывается, когда %R возвращается к своему среднему или срабатывает стоп‑лосс.
Подход рассчитан на трейдеров прорывов, которые хотят раннего участия в зарождающемся тренде. Риск по позиции управляется процентным стопом от цены входа.
Подробности
- Условия входа:
- Лонг: %R > Avg + Multiplier * StdDev
- Шорт: %R < Avg - Multiplier * StdDev
- Длинные/короткие: обе стороны.
- Условия выхода:
- Лонг: выход при %R < Avg
- Шорт: выход при %R > Avg
- Стопы: да, процентный стоп‑лосс.
- Значения по умолчанию:
WilliamsRPeriod= 14AvgPeriod= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Breakout
- Направление: оба
- Индикаторы: 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 that trades on Williams %R breakouts.
/// When Williams %R crosses above the overbought level or below the oversold level,
/// it enters position in the corresponding direction. Exits when Williams %R
/// crosses back through its moving average.
/// </summary>
public class WilliamsRBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<int> _avgPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private WilliamsR _williamsR;
private SimpleMovingAverage _williamsRAverage;
private bool _prevInitialized;
private decimal _prevWilliamsRValue;
private decimal _prevWilliamsRAvgValue;
private int _cooldown;
/// <summary>
/// Williams %R period.
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Period for Williams %R average calculation.
/// </summary>
public int AvgPeriod
{
get => _avgPeriod.Value;
set => _avgPeriod.Value = value;
}
/// <summary>
/// Overbought level for Williams %R (e.g. -10).
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Oversold level for Williams %R (e.g. -90).
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Initialize <see cref="WilliamsRBreakoutStrategy"/>.
/// </summary>
public WilliamsRBreakoutStrategy()
{
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
.SetOptimize(10, 30, 2);
_avgPeriod = Param(nameof(AvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for Williams %R average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_overboughtLevel = Param(nameof(OverboughtLevel), -10m)
.SetDisplay("Overbought Level", "Williams %R overbought threshold", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), -90m)
.SetDisplay("Oversold Level", "Williams %R oversold threshold", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLoss = Param(nameof(StopLoss), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop Loss percentage", "Risk Management")
.SetOptimize(1.0m, 5.0m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevInitialized = false;
_prevWilliamsRValue = 0;
_prevWilliamsRAvgValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_williamsR = new WilliamsR { Length = WilliamsRPeriod };
_williamsRAverage = new SimpleMovingAverage { Length = AvgPeriod };
// Create subscription and bind Williams %R
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_williamsR, ProcessCandle)
.Start();
// Enable stop loss protection
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Percent)
);
// Create chart area for visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsR);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wrValue)
{
if (candle.State != CandleStates.Finished)
return;
// Feed WR value through SMA to get the average (must set IsFinal for buffer to accumulate)
var input = new DecimalIndicatorValue(_williamsRAverage, wrValue, candle.ServerTime) { IsFinal = true };
var avgResult = _williamsRAverage.Process(input);
if (!_williamsRAverage.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var currentWilliamsRAvg = avgResult.ToDecimal();
if (!_prevInitialized)
{
_prevWilliamsRValue = wrValue;
_prevWilliamsRAvgValue = currentWilliamsRAvg;
_prevInitialized = true;
return;
}
// Cooldown between trades (minimum bars between signals)
if (_cooldown > 0)
{
_cooldown--;
_prevWilliamsRValue = wrValue;
_prevWilliamsRAvgValue = currentWilliamsRAvg;
return;
}
const int cooldownBars = 100;
// Williams %R breakout detection using crossover of extreme levels
// Williams %R crossing above overbought level from below = bullish breakout
if (_prevWilliamsRValue <= OverboughtLevel && wrValue > OverboughtLevel && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldown = cooldownBars;
}
// Williams %R crossing below oversold level from above = bearish breakout
else if (_prevWilliamsRValue >= OversoldLevel && wrValue < OversoldLevel && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldown = cooldownBars;
}
// Exit long when Williams %R drops below the midpoint (-50)
else if (Position > 0 && _prevWilliamsRValue >= -50m && wrValue < -50m)
{
SellMarket(Math.Abs(Position));
_cooldown = cooldownBars;
}
// Exit short when Williams %R rises above the midpoint (-50)
else if (Position < 0 && _prevWilliamsRValue <= -50m && wrValue > -50m)
{
BuyMarket(Math.Abs(Position));
_cooldown = cooldownBars;
}
// Update previous values
_prevWilliamsRValue = wrValue;
_prevWilliamsRAvgValue = currentWilliamsRAvg;
}
}
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 StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import WilliamsR, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class williams_r_breakout_strategy(Strategy):
"""
Strategy that trades on Williams %R breakouts.
When Williams %R crosses above the overbought level or below the oversold level,
it enters position in the corresponding direction. Exits when Williams %R
crosses back through its moving average.
"""
def __init__(self):
super(williams_r_breakout_strategy, self).__init__()
self._williamsRPeriod = self.Param("WilliamsRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 2)
self._avgPeriod = self.Param("AvgPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Average Period", "Period for Williams %R average calculation", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 5)
self._overboughtLevel = self.Param("OverboughtLevel", -10.0) \
.SetDisplay("Overbought Level", "Williams %R overbought threshold", "Indicators")
self._oversoldLevel = self.Param("OversoldLevel", -90.0) \
.SetDisplay("Oversold Level", "Williams %R oversold threshold", "Indicators")
self._candleType = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stopLoss = self.Param("StopLoss", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop Loss percentage", "Risk Management") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 5.0, 0.5)
self._prevInitialized = False
self._prevWilliamsRValue = 0
self._prevWilliamsRAvgValue = 0
self._cooldown = 0
self._williamsR = None
self._williamsRAverage = None
@property
def WilliamsRPeriod(self):
return self._williamsRPeriod.Value
@WilliamsRPeriod.setter
def WilliamsRPeriod(self, value):
self._williamsRPeriod.Value = value
@property
def AvgPeriod(self):
return self._avgPeriod.Value
@AvgPeriod.setter
def AvgPeriod(self, value):
self._avgPeriod.Value = value
@property
def OverboughtLevel(self):
return self._overboughtLevel.Value
@OverboughtLevel.setter
def OverboughtLevel(self, value):
self._overboughtLevel.Value = value
@property
def OversoldLevel(self):
return self._oversoldLevel.Value
@OversoldLevel.setter
def OversoldLevel(self, value):
self._oversoldLevel.Value = value
@property
def CandleType(self):
return self._candleType.Value
@CandleType.setter
def CandleType(self, value):
self._candleType.Value = value
@property
def StopLoss(self):
return self._stopLoss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stopLoss.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(williams_r_breakout_strategy, self).OnReseted()
self._prevInitialized = False
self._prevWilliamsRValue = 0
self._prevWilliamsRAvgValue = 0
self._cooldown = 0
def OnStarted2(self, time):
super(williams_r_breakout_strategy, self).OnStarted2(time)
# Create indicators
self._williamsR = WilliamsR()
self._williamsR.Length = self.WilliamsRPeriod
self._williamsRAverage = SimpleMovingAverage()
self._williamsRAverage.Length = self.AvgPeriod
# Create subscription and bind Williams %R
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._williamsR, self.ProcessCandle).Start()
# Enable stop loss protection
self.StartProtection(
takeProfit=Unit(0, UnitTypes.Absolute),
stopLoss=Unit(self.StopLoss, UnitTypes.Percent)
)
# Create chart area for visualization
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._williamsR)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, wrValue):
if candle.State != CandleStates.Finished:
return
if not wrValue.IsFinal:
return
wrVal = float(wrValue)
# Feed WR value through SMA to get the average (must set IsFinal for buffer to accumulate)
avgResult = process_float(self._williamsRAverage, wrVal, candle.ServerTime, True)
if not self._williamsRAverage.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
currentWilliamsRAvg = float(avgResult)
if not self._prevInitialized:
self._prevWilliamsRValue = wrVal
self._prevWilliamsRAvgValue = currentWilliamsRAvg
self._prevInitialized = True
return
# Cooldown between trades (minimum bars between signals)
if self._cooldown > 0:
self._cooldown -= 1
self._prevWilliamsRValue = wrVal
self._prevWilliamsRAvgValue = currentWilliamsRAvg
return
cooldownBars = 100
# Williams %R breakout detection using crossover of extreme levels
# Williams %R crossing above overbought level from below = bullish breakout
if self._prevWilliamsRValue <= self.OverboughtLevel and wrVal > self.OverboughtLevel and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = cooldownBars
# Williams %R crossing below oversold level from above = bearish breakout
elif self._prevWilliamsRValue >= self.OversoldLevel and wrVal < self.OversoldLevel and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = cooldownBars
# Exit long when Williams %R drops below the midpoint (-50)
elif self.Position > 0 and self._prevWilliamsRValue >= -50.0 and wrVal < -50.0:
self.SellMarket(abs(self.Position))
self._cooldown = cooldownBars
# Exit short when Williams %R rises above the midpoint (-50)
elif self.Position < 0 and self._prevWilliamsRValue <= -50.0 and wrVal > -50.0:
self.BuyMarket(abs(self.Position))
self._cooldown = cooldownBars
# Update previous values
self._prevWilliamsRValue = wrVal
self._prevWilliamsRAvgValue = currentWilliamsRAvg
def CreateClone(self):
return williams_r_breakout_strategy()