GitHub で見る

MA Crossover ADX戦略

概要

MA Crossover ADX戦略は、MetaTraderのエキスパートアドバイザーMA_Crossover_ADXを直接移植したものです。指数移動平均(EMA)の傾きとAverage Directional Index(ADX)の確認を組み合わせ、トレンド環境でのみ参加します。StockSharp実装は設定可能な時間軸の確定済みローソク足を処理し、シグナル発行前にEMAとADXの更新を同期します。保護用ストップロスとテイクプロフィットの距離は、戦略のポイントベースのリスクパラメーターを使って新しい各ポジションへ自動的に付与されます。

指標とデータ

  • 指数移動平均(EMA): 主なトレンドフィルターとして機能します。戦略は直近3つのEMA値を追跡して2つの連続した傾きを計算し、元のEAのStateEMA(0)およびStateEMA(1)チェックを模倣します。
  • Average Directional Index(ADX): トレンド強度のメインラインと、正/負の方向性指標(DI+/DI-)を提供します。DI+とDI-の差はEAのStateADX(0)条件を再現し、メインラインは最小強度しきい値を課します。
  • 終値系列: 直前ローソク足の終値を前回EMAと比較し、エントリー前に市場が移動平均から離れたことを確認します。

すべての指標は同じローソク足購読で動作するため、判断前にEMAとADXの値が同一バーで確定していることが保証されます。

取引ロジック

ロングエントリー

  1. 現在のEMA傾き(EMA[0] - EMA[1])が正である。
  2. 以前のEMA傾き(EMA[1] - EMA[2])も正で、加速を示している。
  3. 直前ローソク足の終値が前回EMA値より上にある。
  4. ADXメインラインが設定しきい値を上回る。
  5. DI+がDI-を上回り、強気の方向優位を示す。

すべてのルールが一致し、ポジションがない場合、戦略は設定した取引数量で成行買い注文を送信します。ショートポジションがある場合、強気条件が現れ次第閉じられます。

ショートエントリー

  1. 現在のEMA傾きが負である。
  2. 以前のEMA傾きも負である。
  3. 直前ローソク足の終値が前回EMA値より下にある。
  4. ADXメインラインがしきい値を上回る。
  5. DI-がDI+を上回り、弱気モメンタムを示す。

5つの条件がすべて満たされ、戦略がフラットな場合、成行売り注文を出します。弱気フィルターが現れた場合、オープン中のロングポジションは即座に閉じられます。

決済ルール

  • ロングポジション: ショートエントリー条件が成立したときに決済し、市場モメンタムが下向きになった場合にロングから退出します。
  • ショートポジション: ロングエントリー条件が成立したときに決済します。
  • 保護注文: StartProtectionは、銘柄のPriceStepに設定済みポイント距離を掛けて計算したストップロスとテイクプロフィット注文を付与します。これらの注文はStockSharpのネイティブ保護注文エンジンに従ってアクティブポジションを追跡します。

パラメーター

名前 既定値 説明
AdxPeriod 33 ADX計算に使うバー数。
AdxThreshold 22 トレンドを検証するために必要なADXメインラインの最小値。
EmaPeriod 39 傾き検出に使うEMAの長さ。
StopLossPoints 400 銘柄ポイントで測るストップロス距離(PriceStepを掛ける)。
TakeProfitPoints 900 銘柄ポイントで測るテイクプロフィット距離。
TradeVolume 0.1 新しい各成行注文で送信する数量。
CandleType 1時間足 すべての指標計算を駆動するローソク足タイプ。

使用上の注意

  • 銘柄が有効なPriceStepを提供していることを確認してください。ステップがない場合、保護注文を計算できるように戦略は既定で1ポイントを使います。
  • パラメーターはSetCanOptimize(true)により最適化向きで、異なるEMA/ADX組み合わせのバックテストや最適化が可能です。
  • C#実装内のコメントは、プロジェクトガイドラインに従って意図的に英語で書かれています。
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;

public class MaCrossoverAdxStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MaCrossoverAdxStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}