GitHub で見る

Williams R Ichimoku Strategy

This setup combines the momentum extremes of Williams %R with the trend structure defined by the Ichimoku Cloud. The idea is to join strong moves only when price sits on the favourable side of the cloud and the short term lines confirm the bias.

Testing indicates an average annual return of about 73%. It performs best in the crypto market.

A long opportunity appears when the oscillator drops below -80 while price holds above the cloud and Tenkan-sen crosses above Kijun-sen. A short signal occurs when %R climbs above -20 with price below the cloud and Tenkan-sen under Kijun-sen. The position remains open until price crosses the opposite side of the cloud.

Because the method waits for several pieces of confirmation, it suits traders who prefer clear trend filters over fast reversals. Dynamic stops are set around the Kijun-sen so risk adjusts with the underlying trend strength.

Details

  • Entry Criteria:
    • Long: %R < -80 && price above Ichimoku cloud and Tenkan-sen > Kijun-sen
    • Short: %R > -20 && price below Ichimoku cloud and Tenkan-sen < Kijun-sen
  • Long/Short: Both sides.
  • Exit Criteria:
    • Long: Exit when price crosses below the cloud
    • Short: Exit when price crosses above the cloud
  • Stops: Yes.
  • Default Values:
    • WilliamsRPeriod = 14
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanBPeriod = 52
    • CandleType = TimeSpan.FromMinutes(15)
  • Filters:
    • Category: Mixed
    • Direction: Both
    • Indicators: Williams R Ichimoku
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Intraday
    • 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;

namespace StockSharp.Samples.Strategies;
	
/// <summary>
/// Strategy based on Williams %R and Ichimoku indicators.
/// Enters long when Williams %R is below -80 (oversold) and price is above Ichimoku Cloud with Tenkan-sen > Kijun-sen.
/// Enters short when Williams %R is above -20 (overbought) and price is below Ichimoku Cloud with Tenkan-sen < Kijun-sen.
/// </summary>
public class WilliamsIchimokuStrategy : Strategy
{
	private readonly StrategyParam<int> _williamsRPeriod;
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;
	
	private WilliamsR _williamsR;
	private Ichimoku _ichimoku;
	
	private decimal? _lastKijun;
	private decimal _prevWilliamsR;
	private int _cooldown;
	
	/// <summary>
	/// Williams %R indicator period.
	/// </summary>
	public int WilliamsRPeriod
	{
		get => _williamsRPeriod.Value;
		set => _williamsRPeriod.Value = value;
	}
	
	/// <summary>
	/// Tenkan-sen period (Ichimoku).
	/// </summary>
	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}
	
	/// <summary>
	/// Kijun-sen period (Ichimoku).
	/// </summary>
	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}
	
	/// <summary>
	/// Senkou Span B period (Ichimoku).
	/// </summary>
	public int SenkouSpanBPeriod
	{
		get => _senkouSpanBPeriod.Value;
		set => _senkouSpanBPeriod.Value = value;
	}
	
	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Candle type parameter.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Constructor.
	/// </summary>
	public WilliamsIchimokuStrategy()
	{
		_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R Period", "Period for Williams %R calculation", "Indicators")
			
			.SetOptimize(10, 20, 2);
			
		_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen line (Ichimoku)", "Indicators")
			
			.SetOptimize(7, 13, 1);
			
		_kijunPeriod = Param(nameof(KijunPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Kijun-sen Period", "Period for Kijun-sen line (Ichimoku)", "Indicators")
			
			.SetOptimize(20, 30, 2);
			
		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
			.SetGreaterThanZero()
			.SetDisplay("Senkou Span B Period", "Period for Senkou Span B line (Ichimoku)", "Indicators")
			
			.SetOptimize(40, 60, 4);

		_cooldownBars = Param(nameof(CooldownBars), 60)
			.SetRange(1, 200)
			.SetDisplay("Cooldown Bars", "Bars between trades", "General");
			
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_williamsR = null;
		_ichimoku = null;
		_lastKijun = null;
		_prevWilliamsR = -50m;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Initialize indicators
		_williamsR = new WilliamsR
		{
			Length = WilliamsRPeriod
		};
		
		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanBPeriod }
		};
		
		// Create candles subscription
		var subscription = SubscribeCandles(CandleType);
		
		// Bind indicators to subscription
		subscription
			.BindEx(_williamsR, _ichimoku, ProcessCandle)
			.Start();
		
		// Setup chart if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _williamsR);
			DrawIndicator(area, _ichimoku);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle, IIndicatorValue williamsRValue, IIndicatorValue ichimokuValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;
			
		// Skip if strategy is not ready to trade
		if (!IsFormedAndOnlineAndAllowTrading())
			return;
			
		// Extract Ichimoku values
		var ichimokuTyped = (IchimokuValue)ichimokuValue;

		if (ichimokuTyped.Tenkan is not decimal tenkan)
			return;

		if (ichimokuTyped.Kijun is not decimal kijun)
			return;

		if (ichimokuTyped.SenkouA is not decimal senkouA)
			return;

		if (ichimokuTyped.SenkouB is not decimal senkouB)
			return;

		// Determine if price is above or below the Kumo (cloud)
		var kumoTop = Math.Max(senkouA, senkouB);
		var kumoBottom = Math.Min(senkouA, senkouB);
		var isPriceAboveKumo = candle.ClosePrice > kumoTop;
		var isPriceBelowKumo = candle.ClosePrice < kumoBottom;

		var williamsRDec = williamsRValue.ToDecimal();
		var crossedBelow80 = _prevWilliamsR >= -90m && williamsRDec < -90m;
		var crossedAbove20 = _prevWilliamsR <= -10m && williamsRDec > -10m;
		_prevWilliamsR = williamsRDec;
		if (_cooldown > 0)
			_cooldown--;

		// Save current Kijun for stop-loss
		_lastKijun = kijun;
		
		// Trading logic
		if (_cooldown == 0 && crossedBelow80 && candle.ClosePrice > kumoTop * 1.002m && isPriceAboveKumo && tenkan > kijun)
		{
			// Long signal: %R < -80 (oversold), price above Kumo, Tenkan > Kijun
			if (Position <= 0)
			{
				// Close any existing short position and open long
				BuyMarket(Volume + Math.Abs(Position));
				_cooldown = CooldownBars;
			}
		}
		else if (_cooldown == 0 && crossedAbove20 && candle.ClosePrice < kumoBottom * 0.998m && isPriceBelowKumo && tenkan < kijun)
		{
			// Short signal: %R > -20 (overbought), price below Kumo, Tenkan < Kijun
			if (Position >= 0)
			{
				// Close any existing long position and open short
				SellMarket(Volume + Math.Abs(Position));
				_cooldown = CooldownBars;
			}
		}
		else if ((Position > 0 && candle.ClosePrice < kumoBottom) || 
				(Position < 0 && candle.ClosePrice > kumoTop))
		{
			// Exit positions when price crosses the Kumo
			if (Position > 0)
			{
				SellMarket(Math.Abs(Position));
				_cooldown = CooldownBars;
			}
			else if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_cooldown = CooldownBars;
			}
		}
	}
}