Облако Ишимоку и объёмные кластеры
Стратегия Ichimoku Volume Cluster основана на облаке Ишимоку с подтверждением по объёмным кластерам.
Сигналы формируются, когда индикаторы подтверждают изменение тренда на внутридневных данных (1ч). Такой подход подходит активным трейдерам.
Стопы рассчитываются на основе кратных ATR и параметров TenkanPeriod, KijunPeriod. Настройте эти значения для баланса риска и прибыли.
Подробности
- Критерии входа: см. реализацию условий индикаторов.
- Длинные/короткие: обе стороны.
- Критерии выхода: противоположный сигнал или логика стопов.
- Стопы: да, расчёт на основе индикаторов.
- Значения по умолчанию:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52VolumeAvgPeriod = 20VolumeStdDevMultiplier = 2.0mCandleType = TimeSpan.FromHours(1).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: несколько индикаторов
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (1ч)
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()