GitHub で見る

ADX 出来高乗数戦略

ADX 出来高乗数戦略は、Average Directional Index によるトレンド強度と出来高急増フィルターを組み合わせます。ADX がしきい値を超え、優勢な方向線がトレンド方向を示し、現在の出来高がユーザー定義の係数で乗算した移動平均を上回る場合にのみ取引に参入します。

詳細

  • エントリー条件:
    • ADX がしきい値を超え DI+ > DI- で出来高が SMA(出来高) * 乗数より大きい → ロング。
    • ADX がしきい値を超え DI- > DI+ で出来高が SMA(出来高) * 乗数より大きい → ショート。
  • ロング/ショート: 両方。
  • エグジット条件:
    • 逆シグナルがポジションの反転をトリガーします。
  • ストップ: なし。
  • デフォルト値:
    • AdxPeriod = 21
    • AdxThreshold = 26
    • VolumeMultiplier = 1.8
    • VolumePeriod = 20
  • フィルター:
    • カテゴリ: トレンドフォロー
    • 方向: 両方
    • インジケーター: ADX, Volume SMA
    • ストップ: いいえ
    • 複雑さ: 低
    • 時間軸: 任意
    • 季節性: いいえ
    • ニューラルネットワーク: いいえ
    • ダイバージェンス: いいえ
    • リスクレベル: 中
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ADX Volume Multiplier strategy.
/// When ADX is above AdxThreshold and the candle volume exceeds VolumeMultiplier times its SMA, the strategy goes long if DI+ is above
/// DI- and short if DI- is above DI+; the opposite signal reverses the position.
/// </summary>
public class AdxVolumeMultiplierStrategy : Strategy
{
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<decimal> _adxThreshold;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<int> _volumePeriod;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _volumeSma;

	/// <summary>
	/// ADX period.
	/// </summary>
	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	/// <summary>
	/// ADX level a trend must exceed.
	/// </summary>
	public decimal AdxThreshold
	{
		get => _adxThreshold.Value;
		set => _adxThreshold.Value = value;
	}

	/// <summary>
	/// Multiple of the average volume the candle volume must exceed.
	/// </summary>
	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	/// <summary>
	/// Period of the volume SMA.
	/// </summary>
	public int VolumePeriod
	{
		get => _volumePeriod.Value;
		set => _volumePeriod.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public AdxVolumeMultiplierStrategy()
	{
		_adxPeriod = Param(nameof(AdxPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("ADX Period", "ADX period", "ADX");

		_adxThreshold = Param(nameof(AdxThreshold), 26m)
			.SetDisplay("ADX Threshold", "ADX level a trend must exceed", "ADX");

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.8m)
			.SetGreaterThanZero()
			.SetDisplay("Volume Multiplier", "Multiple of the average volume the candle volume must exceed", "Volume");

		_volumePeriod = Param(nameof(VolumePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Volume Period", "Period of the volume SMA", "Volume");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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();
		_volumeSma = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var adx = new AverageDirectionalIndex { Length = AdxPeriod };
		_volumeSma = new SimpleMovingAverage { Length = VolumePeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(adx, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);

			var oscillators = CreateChartArea();
			if (oscillators != null)
				DrawIndicator(oscillators, adx);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var volumeAverage = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();

		if (!adxValue.IsFormed || !_volumeSma.IsFormed)
			return;

		var adxTyped = (IAverageDirectionalIndexValue)adxValue;
		if (adxTyped.MovingAverage is not decimal adx || adxTyped.Dx.Plus is not decimal diPlus || adxTyped.Dx.Minus is not decimal diMinus)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var volumeSurge = candle.TotalVolume > volumeAverage * VolumeMultiplier;
		var strong = adx > AdxThreshold;

		if (strong && volumeSurge && diPlus > diMinus && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));
		else if (strong && volumeSurge && diMinus > diPlus && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
	}
}