GitHub で見る

ADX クロッシング戦略

ADX クロッシング戦略は、Average Directional Index (ADX) インジケーターを中心に構築されています。正の方向性指標(+DI)と負の方向性指標(-DI)のクロスオーバーを分析して、潜在的なトレンド転換を特定します。

+DIが-DIを上抜けすると、戦略は強気シグナルとみなし、既存のショートポジションをオプションで閉じながらロングポジションを開くことができます。反対に、+DIが-DIを下抜けすると、弱気シグナルとして扱われ、ショートエントリーとロングポジションのオプションのクローズを促します。内蔵リスク管理によるオプションのストップロスとテイクプロフィットレベルがサポートされています。

インジケーター

この戦略はStockSharpの AverageDirectionalIndex インジケーターを使用します。必要なのは方向性ラインのみで、ADXのメイン値は意思決定に使用されません。

パラメーター

  • ADX Period – ADX計算の長さ。デフォルトは 50
  • Candle Type – ローソク足サブスクリプションに使用する時間軸。デフォルトは 1時間
  • Allow Buy Open – ロングポジションの開設を有効にする。デフォルトは true
  • Allow Sell Open – ショートポジションの開設を有効にする。デフォルトは true
  • Allow Buy Close – 売りシグナルでロングポジションをクローズすることを許可する。デフォルトは true
  • Allow Sell Close – 買いシグナルでショートポジションをクローズすることを許可する。デフォルトは true
  • Stop Loss – 絶対価格単位でのストップロス距離。デフォルトは 1000
  • Take Profit – 絶対価格単位でのテイクプロフィット距離。デフォルトは 2000

トレードロジック

  1. 選択した時間軸のローソク足をサブスクライブし、ADXインジケーターを計算する。
  2. クロスオーバーを検出するために+DIと-DIの前回値を追跡する。
  3. 強気クロスオーバー(+DIが-DIを上抜け)時:
    • Allow Sell Close が有効な場合、ショートポジションをクローズする。
    • Allow Buy Open が有効な場合、ロングポジションを開く。
  4. 弱気クロスオーバー(+DIが-DIを下抜け)時:
    • Allow Buy Close が有効な場合、ロングポジションをクローズする。
    • Allow Sell Open が有効な場合、ショートポジションを開く。
  5. 保護的なストップロスとテイクプロフィットのレベルは StartProtection を使用して適用されます。

注意事項

  • 完成済みのローソク足(CandleStates.Finished)のみが処理されます。
  • 戦略はストップレベルについてStockSharpの内蔵リスク管理に依存します。
  • ポジションは現在のボリュームで反対の成行注文を送ることでクローズされます。

この戦略は教育目的を意図しており、ライブ市場で使用する前にさらなる調整が必要な場合があります。

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>
/// Strategy based on crossovers of the +DI and -DI lines of the ADX indicator.
/// It opens or closes positions when directional lines cross each other.
/// </summary>
public class AdxCrossingStrategy : Strategy
{
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _allowBuyOpen;
	private readonly StrategyParam<bool> _allowSellOpen;
	private readonly StrategyParam<bool> _allowBuyClose;
	private readonly StrategyParam<bool> _allowSellClose;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trendThreshold;

	private decimal _prevPlusDi;
	private decimal _prevMinusDi;
	private bool _isInitialized;

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

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

	/// <summary>
	/// Permission to open long positions.
	/// </summary>
	public bool AllowBuyOpen
	{
		get => _allowBuyOpen.Value;
		set => _allowBuyOpen.Value = value;
	}

	/// <summary>
	/// Permission to open short positions.
	/// </summary>
	public bool AllowSellOpen
	{
		get => _allowSellOpen.Value;
		set => _allowSellOpen.Value = value;
	}

	/// <summary>
	/// Permission to close long positions.
	/// </summary>
	public bool AllowBuyClose
	{
		get => _allowBuyClose.Value;
		set => _allowBuyClose.Value = value;
	}

	/// <summary>
	/// Permission to close short positions.
	/// </summary>
	public bool AllowSellClose
	{
		get => _allowSellClose.Value;
		set => _allowSellClose.Value = value;
	}

	/// <summary>
	/// Stop-loss in absolute price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take-profit in absolute price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Minimal ADX strength required to trade.
	/// </summary>
	public decimal TrendThreshold
	{
		get => _trendThreshold.Value;
		set => _trendThreshold.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public AdxCrossingStrategy()
	{
		_adxPeriod = Param(nameof(AdxPeriod), 50)
			.SetDisplay("ADX Period", "Period of ADX indicator", "Indicators")
			
			.SetOptimize(10, 100, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for calculations", "General");

		_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
			.SetDisplay("Allow Buy Open", "Enable opening long positions", "Permissions");

		_allowSellOpen = Param(nameof(AllowSellOpen), true)
			.SetDisplay("Allow Sell Open", "Enable opening short positions", "Permissions");

		_allowBuyClose = Param(nameof(AllowBuyClose), true)
			.SetDisplay("Allow Buy Close", "Enable closing long positions", "Permissions");

		_allowSellClose = Param(nameof(AllowSellClose), true)
			.SetDisplay("Allow Sell Close", "Enable closing short positions", "Permissions");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetDisplay("Stop Loss", "Absolute stop-loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetDisplay("Take Profit", "Absolute take-profit in price units", "Risk");

		_trendThreshold = Param(nameof(TrendThreshold), 15m)
			.SetDisplay("Trend Threshold", "Minimal ADX strength required to trade", "Indicators");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevPlusDi = default;
		_prevMinusDi = default;
		_isInitialized = false;
	}

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

		var adx = new AverageDirectionalIndex { Length = AdxPeriod };

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

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

		StartProtection(
			stopLoss: StopLoss > 0m ? new Unit(StopLoss, UnitTypes.Absolute) : null,
			takeProfit: TakeProfit > 0m ? new Unit(TakeProfit, UnitTypes.Absolute) : null);
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var adx = (AverageDirectionalIndexValue)adxValue;

		if (adx.Dx.Plus is not decimal plusDi || adx.Dx.Minus is not decimal minusDi)
			return;

		if (!_isInitialized)
		{
			_prevPlusDi = plusDi;
			_prevMinusDi = minusDi;
			_isInitialized = true;
			return;
		}

		if (adx.MovingAverage is not decimal adxStrength || adxStrength < TrendThreshold)
		{
			_prevPlusDi = plusDi;
			_prevMinusDi = minusDi;
			return;
		}

		var buySignal = plusDi > minusDi && _prevPlusDi <= _prevMinusDi;
		var sellSignal = plusDi < minusDi && _prevPlusDi >= _prevMinusDi;

		if (buySignal)
		{
			if (AllowBuyOpen && Position <= 0)
				BuyMarket(Position < 0 ? Volume + Math.Abs(Position) : Volume);
			else if (AllowSellClose && Position < 0)
				BuyMarket(Math.Abs(Position));
		}

		if (sellSignal)
		{
			if (AllowSellOpen && Position >= 0)
				SellMarket(Position > 0 ? Volume + Position : Volume);
			else if (AllowBuyClose && Position > 0)
				SellMarket(Math.Abs(Position));
		}

		_prevPlusDi = plusDi;
		_prevMinusDi = minusDi;
	}
}