Ichimoku Volume Cluster
The Ichimoku Volume Cluster strategy is built around Ichimoku Cloud with volume cluster confirmation.
Signals trigger when its indicators confirms trend changes on intraday (1h) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like TenkanPeriod, KijunPeriod. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52VolumeAvgPeriod = 20VolumeStdDevMultiplier = 2.0mCandleType = TimeSpan.FromHours(1).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: multiple indicators
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (1h)
- 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 Ichimoku Cloud with volume cluster confirmation.
/// </summary>
public class IchimokuVolumeClusterStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<decimal> _volumeStdDevMultiplier;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeAvg;
private StandardDeviation _volumeStdDev;
/// <summary>
/// Tenkan-sen (Conversion Line) period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen (Base Line) period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B (Leading Span B) period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Volume average period.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Volume standard deviation multiplier.
/// </summary>
public decimal VolumeStdDevMultiplier
{
get => _volumeStdDevMultiplier.Value;
set => _volumeStdDevMultiplier.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize a new instance of <see cref="IchimokuVolumeClusterStrategy"/>.
/// </summary>
public IchimokuVolumeClusterStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen (Conversion Line)", "Ichimoku Settings")
.SetOptimize(7, 12, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun-sen Period", "Period for Kijun-sen (Base Line)", "Ichimoku Settings")
.SetOptimize(20, 30, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (Leading Span B)", "Ichimoku Settings")
.SetOptimize(40, 60, 4);
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Average Period", "Period for volume average and standard deviation", "Volume Settings")
.SetOptimize(10, 30, 5);
_volumeStdDevMultiplier = Param(nameof(VolumeStdDevMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Volume StdDev Multiplier", "Standard deviation multiplier for volume threshold", "Volume Settings")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumeAvg = new SMA { Length = VolumeAvgPeriod };
_volumeStdDev = new StandardDeviation { Length = VolumeAvgPeriod };
// Create Ichimoku indicator
var ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod },
};
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Bind Ichimoku to subscription
subscription
.BindEx(ichimoku, ProcessCandle)
.Start();
// Setup stop-loss at Kijun-sen level
StartProtection(
takeProfit: new Unit(0), // We'll handle exits in the strategy logic
stopLoss: new Unit(0), // Using Kijun-sen as dynamic stop-loss
useMarketOrders: true
);
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ichimoku);
DrawOwnTrades(area);
// Create second area for volume
var volumeArea = CreateChartArea();
if (volumeArea != null)
{
DrawIndicator(volumeArea, _volumeAvg);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue v)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
var volume = candle.TotalVolume;
var volumeAvgValue = _volumeAvg.Process(new DecimalIndicatorValue(_volumeAvg, volume, candle.ServerTime)).ToDecimal();
var volumeStdDevValue = _volumeStdDev.Process(new DecimalIndicatorValue(_volumeStdDev, volume, candle.ServerTime)).ToDecimal();
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ichimokuTyped = (IchimokuValue)v;
// Extract Ichimoku values
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 cloud position
var priceAboveCloud = candle.ClosePrice > Math.Max(senkouA, senkouB);
var priceBelowCloud = candle.ClosePrice < Math.Min(senkouA, senkouB);
// Check for volume spike
var volumeThreshold = volumeAvgValue + VolumeStdDevMultiplier * volumeStdDevValue;
var hasVolumeSpike = candle.TotalVolume > volumeThreshold;
// Define entry conditions
var longEntryCondition = priceAboveCloud && tenkan > kijun && hasVolumeSpike && Position <= 0;
var shortEntryCondition = priceBelowCloud && tenkan < kijun && hasVolumeSpike && Position >= 0;
// Define exit conditions
var longExitCondition = priceBelowCloud && Position > 0;
var shortExitCondition = priceAboveCloud && Position < 0;
// Log current values
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, Volume: {candle.TotalVolume}, Threshold: {volumeThreshold}");
LogInfo($"Tenkan: {tenkan}, Kijun: {kijun}, Senkou A: {senkouA}, Senkou B: {senkouB}");
// Execute trading logic
if (longEntryCondition)
{
// Calculate position size
var positionSize = Volume + Math.Abs(Position);
// Enter long position
BuyMarket(positionSize);
LogInfo($"Long entry: Price={candle.ClosePrice}, Volume={candle.TotalVolume}, Threshold={volumeThreshold}");
}
else if (shortEntryCondition)
{
// Calculate position size
var positionSize = Volume + Math.Abs(Position);
// Enter short position
SellMarket(positionSize);
LogInfo($"Short entry: Price={candle.ClosePrice}, Volume={candle.TotalVolume}, Threshold={volumeThreshold}");
}
else if (longExitCondition)
{
// Exit long position
SellMarket(Math.Abs(Position));
LogInfo($"Long exit: Price={candle.ClosePrice}, Reason=Below Cloud");
}
else if (shortExitCondition)
{
// Exit short position
BuyMarket(Math.Abs(Position));
LogInfo($"Short exit: Price={candle.ClosePrice}, Reason=Above Cloud");
}
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit
from StockSharp.Algo.Indicators import Ichimoku, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ichimoku_volume_cluster_strategy(Strategy):
"""
Strategy based on Ichimoku Cloud with volume cluster confirmation.
"""
def __init__(self):
super(ichimoku_volume_cluster_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen (Conversion Line)", "Ichimoku Settings")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun-sen Period", "Period for Kijun-sen (Base Line)", "Ichimoku Settings")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (Leading Span B)", "Ichimoku Settings")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume Average Period", "Period for volume average and standard deviation", "Volume Settings")
self._volume_std_dev_multiplier = self.Param("VolumeStdDevMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Volume StdDev Multiplier", "Standard deviation multiplier for volume threshold", "Volume Settings")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles for strategy", "General")
self._volume_avg = None
self._volume_std_dev = None
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ichimoku_volume_cluster_strategy, self).OnStarted2(time)
vol_period = int(self._volume_avg_period.Value)
self._volume_avg = SimpleMovingAverage()
self._volume_avg.Length = vol_period
self._volume_std_dev = StandardDeviation()
self._volume_std_dev.Length = vol_period
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
ichimoku.Kijun.Length = int(self._kijun_period.Value)
ichimoku.SenkouB.Length = int(self._senkou_span_b_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ichimoku, self._process_candle).Start()
self.StartProtection(Unit(0), Unit(0), True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
volume_area = self.CreateChartArea()
if volume_area is not None:
self.DrawIndicator(volume_area, self._volume_avg)
def _process_candle(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
volume = candle.TotalVolume
volume_avg_value = float(process_float(self._volume_avg, volume, candle.ServerTime, True))
volume_std_dev_value = float(process_float(self._volume_std_dev, volume, candle.ServerTime, True))
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_f = float(tenkan)
kijun_f = float(kijun)
senkou_a_f = float(senkou_a)
senkou_b_f = float(senkou_b)
close_price = float(candle.ClosePrice)
price_above_cloud = close_price > max(senkou_a_f, senkou_b_f)
price_below_cloud = close_price < min(senkou_a_f, senkou_b_f)
vsm = float(self._volume_std_dev_multiplier.Value)
volume_threshold = volume_avg_value + vsm * volume_std_dev_value
has_volume_spike = float(volume) > volume_threshold
long_entry = price_above_cloud and tenkan_f > kijun_f and has_volume_spike and self.Position <= 0
short_entry = price_below_cloud and tenkan_f < kijun_f and has_volume_spike and self.Position >= 0
long_exit = price_below_cloud and self.Position > 0
short_exit = price_above_cloud and self.Position < 0
if long_entry:
position_size = self.Volume + Math.Abs(self.Position)
self.BuyMarket(position_size)
elif short_entry:
position_size = self.Volume + Math.Abs(self.Position)
self.SellMarket(position_size)
elif long_exit:
self.SellMarket(Math.Abs(self.Position))
elif short_exit:
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return ichimoku_volume_cluster_strategy()