在 GitHub 上查看

MACD Volume Cluster

MACD Volume Cluster 策略基于 MACD Volume Cluster。

当 its indicators confirms trend changes 在日内(5m)数据上得到确认时触发信号,适合积极交易者。

止损依赖于 ATR 倍数以及 FastMacdPeriod, SlowMacdPeriod 等参数,可根据需要调整以平衡风险与收益。

详情

  • 入场条件:参见指标条件实现.
  • 多空方向:双向.
  • 退出条件:反向信号或止损逻辑.
  • 止损:是,基于指标计算.
  • 默认值:
    • FastMacdPeriod = 12
    • SlowMacdPeriod = 26
    • MacdSignalPeriod = 9
    • VolumePeriod = 20
    • VolumeDeviationFactor = 2.0m
    • CandleType = 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}");
		}
	}
}