Ichimoku Volume Strategy
Implementation of strategy - Ichimoku + Volume. Buy when price is above Kumo cloud, Tenkan-sen is above Kijun-sen, and volume is above average. Sell when price is below Kumo cloud, Tenkan-sen is below Kijun-sen, and volume is above average.
Testing indicates an average annual return of about 40%. It performs best in the crypto market.
Ichimoku components define the directional bias while surging volume confirms interest. Trades open when price aligns with the cloud and volume picks up.
It fits traders who like to follow cloud breakouts with participation. Risk is restricted by an ATR-based stop.
Details
- Entry Criteria:
- Long:
Price > Cloud && Tenkan > Kijun && Volume > AvgVolume - Short:
Price < Cloud && Tenkan < Kijun && Volume > AvgVolume
- Long:
- Long/Short: Both
- Exit Criteria:
- Cloud breakout in opposite direction
- Stops: Percent-based using
StopLoss - Default Values:
TenkanPeriod= 9KijunPeriod= 26SenkouSpanPeriod= 52VolumeAvgPeriod= 20StopLoss= new Unit(2, UnitTypes.Percent)CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: Ichimoku Cloud, Volume
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining Ichimoku (manual Tenkan/Kijun) with volume filter.
/// Buys when price above Kumo, Tenkan above Kijun, volume above average.
/// Sells when price below Kumo, Tenkan below Kijun, volume above average.
/// </summary>
public class IchimokuVolumeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private readonly List<decimal> _vols = new();
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Tenkan-sen period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Volume average period.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public IchimokuVolumeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetRange(5, 20)
.SetDisplay("Tenkan Period", "Tenkan-sen period (fast)", "Ichimoku");
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetRange(15, 40)
.SetDisplay("Kijun Period", "Kijun-sen period (slow)", "Ichimoku");
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetRange(10, 50)
.SetDisplay("Volume Average Period", "Period for volume moving average", "Volume");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_vols.Clear();
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = KijunPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
var vol = candle.TotalVolume;
_highs.Add(high);
_lows.Add(low);
_vols.Add(vol);
var tenkanPrd = TenkanPeriod;
var kijunPrd = KijunPeriod;
var volPrd = VolumeAvgPeriod;
var minBars = Math.Max(kijunPrd, volPrd);
if (_highs.Count < minBars)
{
if (_cooldown > 0) _cooldown--;
return;
}
// Manual Tenkan-sen: (highest high + lowest low) / 2 over tenkan period
var count = _highs.Count;
decimal tenkanHH = decimal.MinValue, tenkanLL = decimal.MaxValue;
for (int i = count - tenkanPrd; i < count; i++)
{
if (_highs[i] > tenkanHH) tenkanHH = _highs[i];
if (_lows[i] < tenkanLL) tenkanLL = _lows[i];
}
var tenkan = (tenkanHH + tenkanLL) / 2m;
// Manual Kijun-sen
decimal kijunHH = decimal.MinValue, kijunLL = decimal.MaxValue;
for (int i = count - kijunPrd; i < count; i++)
{
if (_highs[i] > kijunHH) kijunHH = _highs[i];
if (_lows[i] < kijunLL) kijunLL = _lows[i];
}
var kijun = (kijunHH + kijunLL) / 2m;
// Senkou Span A = (Tenkan + Kijun) / 2
var senkouA = (tenkan + kijun) / 2m;
// Senkou Span B = (highest high + lowest low) / 2 over 2*kijun period (use kijun period for simplicity)
var senkouB = kijun; // simplified: use Kijun as proxy for Senkou B
var upperKumo = Math.Max(senkouA, senkouB);
var lowerKumo = Math.Min(senkouA, senkouB);
// Volume average
decimal sumVol = 0;
for (int i = count - volPrd; i < count; i++)
sumVol += _vols[i];
var avgVol = sumVol / volPrd;
var highVolume = vol > avgVol;
// Trim lists
var maxKeep = minBars * 3;
if (_highs.Count > maxKeep)
{
var trim = _highs.Count - minBars * 2;
_highs.RemoveRange(0, trim);
_lows.RemoveRange(0, trim);
_vols.RemoveRange(0, trim);
}
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Buy: price above cloud + Tenkan above Kijun + high volume
if (close > upperKumo && tenkan > kijun && highVolume && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Sell: price below cloud + Tenkan below Kijun + high volume
else if (close < lowerKumo && tenkan < kijun && highVolume && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price drops below kijun
if (Position > 0 && close < kijun)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price rises above kijun
else if (Position < 0 && close > kijun)
{
BuyMarket();
_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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class ichimoku_volume_strategy(Strategy):
"""
Strategy combining Ichimoku (manual Tenkan/Kijun) with volume filter.
"""
def __init__(self):
super(ichimoku_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetRange(5, 20) \
.SetDisplay("Tenkan Period", "Tenkan-sen period (fast)", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetRange(15, 40) \
.SetDisplay("Kijun Period", "Kijun-sen period (slow)", "Ichimoku")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("Volume Average Period", "Period for volume moving average", "Volume")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._highs = []
self._lows = []
self._vols = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ichimoku_volume_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._vols = []
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._kijun_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
vol = float(candle.TotalVolume)
self._highs.append(high)
self._lows.append(low)
self._vols.append(vol)
tenkan_prd = self._tenkan_period.Value
kijun_prd = self._kijun_period.Value
vol_prd = self._volume_avg_period.Value
min_bars = max(kijun_prd, vol_prd)
if len(self._highs) < min_bars:
if self._cooldown > 0:
self._cooldown -= 1
return
count = len(self._highs)
# Manual Tenkan-sen
tenkan_hh = max(self._highs[count - tenkan_prd:count])
tenkan_ll = min(self._lows[count - tenkan_prd:count])
tenkan = (tenkan_hh + tenkan_ll) / 2.0
# Manual Kijun-sen
kijun_hh = max(self._highs[count - kijun_prd:count])
kijun_ll = min(self._lows[count - kijun_prd:count])
kijun = (kijun_hh + kijun_ll) / 2.0
# Senkou Span A and B
senkou_a = (tenkan + kijun) / 2.0
senkou_b = kijun # simplified
upper_kumo = max(senkou_a, senkou_b)
lower_kumo = min(senkou_a, senkou_b)
# Volume average
sum_vol = sum(self._vols[count - vol_prd:count])
avg_vol = sum_vol / vol_prd
high_volume = vol > avg_vol
# Trim lists
max_keep = min_bars * 3
if len(self._highs) > max_keep:
trim = len(self._highs) - min_bars * 2
self._highs = self._highs[trim:]
self._lows = self._lows[trim:]
self._vols = self._vols[trim:]
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
# Buy: price above cloud + Tenkan above Kijun + high volume
if close > upper_kumo and tenkan > kijun and high_volume and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif close < lower_kumo and tenkan < kijun and high_volume and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long: price drops below kijun
if self.Position > 0 and close < kijun:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > kijun:
self.BuyMarket()
self._cooldown = cd
def OnReseted(self):
super(ichimoku_volume_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._vols = []
self._cooldown = 0
def CreateClone(self):
return ichimoku_volume_strategy()