Auf GitHub ansehen

MACD Volume Cluster-Strategie

Die MACD Volume Cluster-Strategie basiert auf der Analyse von MACD-Volumen-Clustern.

Signale werden ausgelöst, wenn die Indikatoren Trendwechsel auf Intraday-Daten (5m) bestätigen. Dies macht die Methode für aktive Trader geeignet.

Stops basieren auf ATR-Vielfachen und Faktoren wie FastMacdPeriod, SlowMacdPeriod. Passen Sie diese Standardwerte an, um Risiko und Ertrag auszubalancieren.

Details

  • Einstiegskriterien: Siehe Implementierung für Indikatorbedingungen.
  • Long/Short: Beide Richtungen.
  • Ausstiegskriterien: Gegensätzliches Signal oder Stop-Logik.
  • Stops: Ja, unter Verwendung indikatorbasierter Berechnungen.
  • Standardwerte:
    • FastMacdPeriod = 12
    • SlowMacdPeriod = 26
    • MacdSignalPeriod = 9
    • VolumePeriod = 20
    • VolumeDeviationFactor = 2.0m
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
  • Filter:
    • Kategorie: Trendfolge
    • Richtung: Beide
    • Indikatoren: Mehrere Indikatoren
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Intraday (5m)
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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}");
		}
	}
}