Стратегия Rsi Williams R
Реализация стратегии №163 — RSI + Williams %R. Покупка, когда RSI ниже 30 и Williams %R ниже -80 (двойная перепроданность). Продажа, когда RSI выше 70 и Williams %R выше -20 (двойная перекупленность).
Тестирование показывает среднегодичную доходность около 76%. Стратегию лучше запускать на рынке Форекс.
RSI показывает общий импульс, а Williams %R дает более быстрый сигнал разворота. Сделки открываются при согласовании двух осцилляторов.
Подходит активным трейдерам, работающим на коротких движениях. Стоп-лосс основан на ATR.
Подробности
- Условия входа:
- Длинная:
RSI < RsiOversold && WilliamsR < WilliamsROversold - Короткая:
RSI > RsiOverbought && WilliamsR > WilliamsROverbought
- Длинная:
- Long/Short: Оба
- Условия выхода:
- RSI возвращается в нейтральную зону
- Стопы: процентный уровень через
StopLoss - Параметры по умолчанию:
RsiPeriod= 14RsiOversold= 30mRsiOverbought= 70mWilliamsRPeriod= 14WilliamsROversold= -80mWilliamsROverbought= -20mStopLoss= new Unit(2, UnitTypes.Percent)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Mean reversion
- Направление: Оба
- Индикаторы: RSI, Williams %R, 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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Implementation of strategy - RSI + Williams %R.
/// Buy when RSI is below 30 and Williams %R is below -80 (double oversold condition).
/// Sell when RSI is above 70 and Williams %R is above -20 (double overbought condition).
/// </summary>
public class RsiWilliamsRStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
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;
private decimal _prevRsi;
private decimal _prevWilliams;
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.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="RsiWilliamsRStrategy"/>.
/// </summary>
public RsiWilliamsRStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for Relative Strength Index", "RSI Parameters");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetRange(1, 100)
.SetDisplay("RSI Oversold", "RSI level to consider market oversold", "RSI Parameters");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetRange(1, 100)
.SetDisplay("RSI Overbought", "RSI level to consider market overbought", "RSI Parameters");
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R", "Williams %R Parameters");
_williamsROversold = Param(nameof(WilliamsROversold), -80m)
.SetRange(-100, 0)
.SetDisplay("Williams %R Oversold", "Williams %R level to consider market oversold", "Williams %R Parameters");
_williamsROverbought = Param(nameof(WilliamsROverbought), -20m)
.SetRange(-100, 0)
.SetDisplay("Williams %R Overbought", "Williams %R level to consider market overbought", "Williams %R Parameters");
_cooldownBars = Param(nameof(CooldownBars), 180)
.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;
_prevRsi = 0;
_prevWilliams = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var williamsR = new WilliamsR { Length = WilliamsRPeriod };
// Setup candle subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to candles
subscription
.Bind(rsi, williamsR, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
// Create separate area for oscillators
var oscillatorArea = CreateChartArea();
if (oscillatorArea != null)
{
DrawIndicator(oscillatorArea, rsi);
DrawIndicator(oscillatorArea, williamsR);
}
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal williamsRValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevRsi == 0 && _prevWilliams == 0)
{
_prevRsi = rsiValue;
_prevWilliams = williamsRValue;
return;
}
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, " +
$"RSI: {rsiValue} , Williams %R: {williamsRValue}");
if (_cooldown > 0)
{
_cooldown--;
_prevRsi = rsiValue;
_prevWilliams = williamsRValue;
return;
}
var oversoldCross = _prevRsi >= RsiOversold && rsiValue < RsiOversold
&& _prevWilliams >= WilliamsROversold && williamsRValue < WilliamsROversold;
var overboughtCross = _prevRsi <= RsiOverbought && rsiValue > RsiOverbought
&& _prevWilliams <= WilliamsROverbought && williamsRValue > WilliamsROverbought;
// Trading rules
if (oversoldCross && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Buy signal: Double oversold condition - RSI: {rsiValue} < {RsiOversold} and Williams %R: {williamsRValue} < {WilliamsROversold}.");
}
else if (overboughtCross && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Sell signal: Double overbought condition - RSI: {rsiValue} > {RsiOverbought} and Williams %R: {williamsRValue} > {WilliamsROverbought}.");
}
// Exit conditions
else if (rsiValue > 50 && Position > 0)
{
// Exit long position when RSI returns to neutral zone
SellMarket();
_cooldown = CooldownBars;
LogInfo($"Exit long: RSI returned to neutral zone ({rsiValue} > 50). Position: {Position}");
}
else if (rsiValue < 50 && Position < 0)
{
// Exit short position when RSI returns to neutral zone
BuyMarket();
_cooldown = CooldownBars;
LogInfo($"Exit short: RSI returned to neutral zone ({rsiValue} < 50). Position: {Position}");
}
_prevRsi = rsiValue;
_prevWilliams = williamsRValue;
}
}
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
from StockSharp.Algo.Indicators import RelativeStrengthIndex, WilliamsR
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class rsi_williams_r_strategy(Strategy):
"""
RSI + Williams %R strategy.
Buy when both RSI and Williams %R cross into oversold.
Sell when both cross into overbought.
"""
def __init__(self):
super(rsi_williams_r_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Period for RSI", "RSI Parameters")
self._rsi_oversold = self.Param("RsiOversold", 30.0) \
.SetRange(1, 100) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI Parameters")
self._rsi_overbought = self.Param("RsiOverbought", 70.0) \
.SetRange(1, 100) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI Parameters")
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetDisplay("Williams %R Period", "Period for Williams %R", "Williams %R Parameters")
self._williams_r_oversold = self.Param("WilliamsROversold", -80.0) \
.SetRange(-100, 0) \
.SetDisplay("Williams %R Oversold", "Williams %R oversold level", "Williams %R Parameters")
self._williams_r_overbought = self.Param("WilliamsROverbought", -20.0) \
.SetRange(-100, 0) \
.SetDisplay("Williams %R Overbought", "Williams %R overbought level", "Williams %R Parameters")
self._cooldown_bars = self.Param("CooldownBars", 180) \
.SetRange(5, 500) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._cooldown = 0
self._prev_rsi = 0.0
self._prev_williams = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(rsi_williams_r_strategy, self).OnStarted2(time)
self._cooldown = 0
self._prev_rsi = 0.0
self._prev_williams = 0.0
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
williams_r = WilliamsR()
williams_r.Length = self._williams_r_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, williams_r, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
osc_area = self.CreateChartArea()
if osc_area is not None:
self.DrawIndicator(osc_area, rsi)
self.DrawIndicator(osc_area, williams_r)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, rsi_value, williams_r_value):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_value)
wv = float(williams_r_value)
if self._prev_rsi == 0 and self._prev_williams == 0:
self._prev_rsi = rv
self._prev_williams = wv
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_rsi = rv
self._prev_williams = wv
return
cd = self._cooldown_bars.Value
rsi_os = self._rsi_oversold.Value
rsi_ob = self._rsi_overbought.Value
wr_os = self._williams_r_oversold.Value
wr_ob = self._williams_r_overbought.Value
oversold_cross = (self._prev_rsi >= rsi_os and rv < rsi_os
and self._prev_williams >= wr_os and wv < wr_os)
overbought_cross = (self._prev_rsi <= rsi_ob and rv > rsi_ob
and self._prev_williams <= wr_ob and wv > wr_ob)
if oversold_cross and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif overbought_cross and self.Position == 0:
self.SellMarket()
self._cooldown = cd
elif rv > 50 and self.Position > 0:
self.SellMarket()
self._cooldown = cd
elif rv < 50 and self.Position < 0:
self.BuyMarket()
self._cooldown = cd
self._prev_rsi = rv
self._prev_williams = wv
def OnReseted(self):
super(rsi_williams_r_strategy, self).OnReseted()
self._cooldown = 0
self._prev_rsi = 0.0
self._prev_williams = 0.0
def CreateClone(self):
return rsi_williams_r_strategy()