Открыть на GitHub

Стратегия 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 = 14
    • RsiOversold = 30m
    • RsiOverbought = 70m
    • WilliamsRPeriod = 14
    • WilliamsROversold = -80m
    • WilliamsROverbought = -20m
    • StopLoss = 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;
	}
}