Auf GitHub ansehen

MACD Volume Cluster

The MACD Volume Cluster strategy is built around MACD Volume Cluster.

Signals trigger when its indicators confirms trend changes on intraday (5m) data. This makes the method suitable for active traders.

Stops rely on ATR multiples and factors like FastMacdPeriod, SlowMacdPeriod. 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:
    • FastMacdPeriod = 12
    • SlowMacdPeriod = 26
    • MacdSignalPeriod = 9
    • VolumePeriod = 20
    • VolumeDeviationFactor = 2.0m
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: multiple indicators
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Intraday (5m)
    • 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>
/// 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}");
		}
	}
}