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= 14TenkanPeriod= 9KijunPeriod= 26SenkouSpanBPeriod= 52CandleType= 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;
}
}
}
}
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, CandleStates
from StockSharp.Algo.Indicators import WilliamsR, Ichimoku
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class williams_ichimoku_strategy(Strategy):
"""
Strategy based on Williams %R and Ichimoku indicators.
"""
def __init__(self):
super(williams_ichimoku_strategy, self).__init__()
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Williams %R Period", "Period for Williams %R calculation", "Indicators")
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen line (Ichimoku)", "Indicators")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun-sen Period", "Period for Kijun-sen line (Ichimoku)", "Indicators")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B line (Ichimoku)", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 60) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(30)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_williams_r = -50.0
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(williams_ichimoku_strategy, self).OnReseted()
self._prev_williams_r = -50.0
self._cooldown = 0
def OnStarted2(self, time):
super(williams_ichimoku_strategy, self).OnStarted2(time)
self._prev_williams_r = -50.0
self._cooldown = 0
williams_r = WilliamsR()
williams_r.Length = self._williams_r_period.Value
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self._tenkan_period.Value
ichimoku.Kijun.Length = self._kijun_period.Value
ichimoku.SenkouB.Length = self._senkou_span_b_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(williams_r, ichimoku, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, williams_r)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, williams_r_value, ichimoku_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
tenkan = ichimoku_value.Tenkan
kijun = ichimoku_value.Kijun
senkou_a = ichimoku_value.SenkouA
senkou_b = ichimoku_value.SenkouB
if tenkan is None or kijun is None or senkou_a is None or senkou_b is None:
return
tenkan = float(tenkan)
kijun = float(kijun)
senkou_a = float(senkou_a)
senkou_b = float(senkou_b)
kumo_top = max(senkou_a, senkou_b)
kumo_bottom = min(senkou_a, senkou_b)
price = float(candle.ClosePrice)
is_price_above_kumo = price > kumo_top
is_price_below_kumo = price < kumo_bottom
wr = float(williams_r_value)
crossed_below_90 = self._prev_williams_r >= -90 and wr < -90
crossed_above_10 = self._prev_williams_r <= -10 and wr > -10
self._prev_williams_r = wr
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
if self._cooldown == 0 and crossed_below_90 and price > kumo_top * 1.002 and is_price_above_kumo and tenkan > kijun:
if self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif self._cooldown == 0 and crossed_above_10 and price < kumo_bottom * 0.998 and is_price_below_kumo and tenkan < kijun:
if self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif (self.Position > 0 and price < kumo_bottom) or (self.Position < 0 and price > kumo_top):
if self.Position > 0:
self.SellMarket(abs(self.Position))
self._cooldown = cooldown_val
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._cooldown = cooldown_val
def CreateClone(self):
return williams_ichimoku_strategy()