Кластер объёма MACD
Стратегия MACD Volume Cluster построена на анализе объёмных кластеров MACD. Сигналы формируются, когда индикаторы подтверждают смену тренда на внутридневных данных (5м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров FastMacdPeriod, SlowMacdPeriod. Эти значения можно изменять для баланса риска и прибыли.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
FastMacdPeriod = 12SlowMacdPeriod = 26MacdSignalPeriod = 9VolumePeriod = 20VolumeDeviationFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: multiple indicators
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (5m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// MACD with Volume Cluster strategy.
/// Enters positions when MACD signal coincides with abnormal volume spike.
/// </summary>
public class MacdVolumeClusterStrategy : Strategy
{
private readonly StrategyParam<int> _fastMacdPeriod;
private readonly StrategyParam<int> _slowMacdPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _volumeDeviationFactor;
private readonly StrategyParam<DataType> _candleType;
private decimal _avgVolume;
private decimal _volumeStdDev;
private int _processedCandles;
/// <summary>
/// Fast MACD EMA period.
/// </summary>
public int FastMacdPeriod
{
get => _fastMacdPeriod.Value;
set => _fastMacdPeriod.Value = value;
}
/// <summary>
/// Slow MACD EMA period.
/// </summary>
public int SlowMacdPeriod
{
get => _slowMacdPeriod.Value;
set => _slowMacdPeriod.Value = value;
}
/// <summary>
/// MACD signal line period.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Period for volume average calculation.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Volume deviation factor for volume spike detection.
/// </summary>
public decimal VolumeDeviationFactor
{
get => _volumeDeviationFactor.Value;
set => _volumeDeviationFactor.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public MacdVolumeClusterStrategy()
{
_fastMacdPeriod = Param(nameof(FastMacdPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast MACD Period", "Period for fast EMA in MACD calculation", "MACD Settings")
.SetOptimize(8, 16, 2);
_slowMacdPeriod = Param(nameof(SlowMacdPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow MACD Period", "Period for slow EMA in MACD calculation", "MACD Settings")
.SetOptimize(20, 30, 2);
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal Period", "Period for signal line in MACD calculation", "MACD Settings")
.SetOptimize(7, 12, 1);
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Period for volume moving average calculation", "Volume Settings")
.SetOptimize(10, 30, 5);
_volumeDeviationFactor = Param(nameof(VolumeDeviationFactor), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Volume Deviation Factor", "Factor multiplied by standard deviation to detect volume spikes", "Volume Settings")
.SetOptimize(1.5m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_avgVolume = 0;
_volumeStdDev = 0;
_processedCandles = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create MACD indicator
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastMacdPeriod },
LongMa = { Length = SlowMacdPeriod },
},
SignalMa = { Length = MacdSignalPeriod }
};
// Create volume-based indicators
var smaVolume = new SMA
{
Length = VolumePeriod
};
var stdDevVolume = new StandardDeviation
{
Length = VolumePeriod
};
// Create subscription for candles
var subscription = SubscribeCandles(CandleType);
// Bind MACD and process volume separately
subscription
.BindEx(macd, ProcessMacdAndVolume)
.Start();
// Start position protection
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private void ProcessMacdAndVolume(ICandleMessage candle, IIndicatorValue macdValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Calculate volume statistics
_processedCandles++;
// Using exponential moving average approach for volume statistics
// to avoid keeping large arrays of historical volumes
if (_processedCandles == 1)
{
_avgVolume = candle.TotalVolume;
_volumeStdDev = 0;
}
else
{
// Update average volume with smoothing factor
decimal alpha = 2.0m / (VolumePeriod + 1);
decimal oldAvg = _avgVolume;
_avgVolume = alpha * candle.TotalVolume + (1 - alpha) * _avgVolume;
// Update standard deviation (simplified approach)
decimal volumeDev = Math.Abs(candle.TotalVolume - oldAvg);
_volumeStdDev = alpha * volumeDev + (1 - alpha) * _volumeStdDev;
}
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
var macd = macdTyped.Macd;
var signal = macdTyped.Signal;
// Determine if we have a volume spike
bool isVolumeSpike = candle.TotalVolume > (_avgVolume + VolumeDeviationFactor * _volumeStdDev);
// Log the values
LogInfo($"MACD: {macd}, Signal: {signal}, Volume: {candle.TotalVolume}, " +
$"Avg Volume: {_avgVolume}, StdDev: {_volumeStdDev}, Volume Spike: {isVolumeSpike}");
// Trading logic
if (isVolumeSpike)
{
// Buy signal: MACD line crosses above signal line with volume spike
if (macd > signal && Position <= 0)
{
// Close any existing short position
if (Position < 0)
BuyMarket(Math.Abs(Position));
// Open long position
BuyMarket(Volume);
LogInfo($"Buy signal: MACD ({macd}) > Signal ({signal}) with volume spike ({candle.TotalVolume})");
}
// Sell signal: MACD line crosses below signal line with volume spike
else if (macd < signal && Position >= 0)
{
// Close any existing long position
if (Position > 0)
SellMarket(Math.Abs(Position));
// Open short position
SellMarket(Volume);
LogInfo($"Sell signal: MACD ({macd}) < Signal ({signal}) with volume spike ({candle.TotalVolume})");
}
}
// Exit logic: MACD crosses back
if ((Position > 0 && macd < signal) ||
(Position < 0 && macd > signal))
{
ClosePosition();
LogInfo($"Exit signal: MACD and Signal crossed. Position closed at {candle.ClosePrice}");
}
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class macd_volume_cluster_strategy(Strategy):
"""
MACD with Volume Cluster strategy.
Enters positions when MACD signal coincides with abnormal volume spike.
"""
def __init__(self):
super(macd_volume_cluster_strategy, self).__init__()
self._fast_macd_period = self.Param("FastMacdPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("Fast MACD Period", "Period for fast EMA in MACD calculation", "MACD Settings") \
.SetCanOptimize(True) \
.SetOptimize(8, 16, 2)
self._slow_macd_period = self.Param("SlowMacdPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Slow MACD Period", "Period for slow EMA in MACD calculation", "MACD Settings") \
.SetCanOptimize(True) \
.SetOptimize(20, 30, 2)
self._macd_signal_period = self.Param("MacdSignalPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal Period", "Period for signal line in MACD calculation", "MACD Settings") \
.SetCanOptimize(True) \
.SetOptimize(7, 12, 1)
self._volume_period = self.Param("VolumePeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume Period", "Period for volume moving average calculation", "Volume Settings") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 5)
self._volume_deviation_factor = self.Param("VolumeDeviationFactor", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Volume Deviation Factor", "Factor multiplied by standard deviation to detect volume spikes", "Volume Settings") \
.SetCanOptimize(True) \
.SetOptimize(1.5, 3.0, 0.5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._avg_volume = 0.0
self._volume_std_dev = 0.0
self._processed_candles = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(macd_volume_cluster_strategy, self).OnReseted()
self._avg_volume = 0.0
self._volume_std_dev = 0.0
self._processed_candles = 0
def OnStarted2(self, time):
super(macd_volume_cluster_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = int(self._fast_macd_period.Value)
macd.Macd.LongMa.Length = int(self._slow_macd_period.Value)
macd.SignalMa.Length = int(self._macd_signal_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self.ProcessMacdAndVolume).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def ProcessMacdAndVolume(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
self._processed_candles += 1
volume = float(candle.TotalVolume)
vol_period = int(self._volume_period.Value)
vol_factor = float(self._volume_deviation_factor.Value)
if self._processed_candles == 1:
self._avg_volume = volume
self._volume_std_dev = 0.0
else:
alpha = 2.0 / (vol_period + 1)
old_avg = self._avg_volume
self._avg_volume = alpha * volume + (1.0 - alpha) * self._avg_volume
volume_dev = abs(volume - old_avg)
self._volume_std_dev = alpha * volume_dev + (1.0 - alpha) * self._volume_std_dev
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd_line = macd_value.Macd
signal_line = macd_value.Signal
if macd_line is None or signal_line is None:
return
macd_line = float(macd_line)
signal_line = float(signal_line)
is_volume_spike = volume > (self._avg_volume + vol_factor * self._volume_std_dev)
if is_volume_spike:
if macd_line > signal_line and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
elif macd_line < signal_line and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
if (self.Position > 0 and macd_line < signal_line) or \
(self.Position < 0 and macd_line > signal_line):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return macd_volume_cluster_strategy()