GitHub で見る

デュアル MA トレンド確認戦略

概要

デュアル MA トレンド確認戦略は、遅い指数移動平均(EMA)と速い線形加重移動平均(LWMA)を組み合わせたオリジナルのMetaTraderエキスパートを再現します。システムは両方の移動平均が同じ方向に整列するのを待ち、ポジションに入る前に前のローソク足の終値を追加の確認として使用します。アイデアは、遅いトレンドフィルターと速い確認フィルターが同時に上向きまたは下向きに傾いているときだけ、強いモメンタムの動きに参加することです。

StockSharpの実装は完全に完成したローソク足のみを処理し、最後の3つのバーにわたって各移動平均の傾きを追跡し、組み込みのStartProtectionメカニズムを通じて保護注文を自動的に管理します。戦略はインストゥルメントに依存しません: ローソク足を提供し、インストゥルメントの価格ステップを通じて「ポイント」の概念をサポートする任意の証券とタイムフレームで操作できます。

インジケーター

  • 遅いEMA – デフォルト期間57。支配的なトレンド方向を表します。戦略は取引前にEMAが2本の連続したローソク足で増加(または減少)することを要求します。
  • 速いLWMA – デフォルト期間3。モメンタム確認フィルターとして機能します。その傾きは遅いEMAと一致しなければならず、モメンタムがトレンドをサポートしていることを強化します。

パラメーター

パラメーター デフォルト 説明
SlowMaLength 57 遅いEMAトレンドフィルターの期間。
FastMaLength 3 速いLWMA確認フィルターの期間。
StopLossPoints 100 インストゥルメントのポイントで表された保護ストップ距離(Security.PriceStepで乗算)。
TakeProfitPoints 100 インストゥルメントのポイントで表されたテイクプロフィット距離(Security.PriceStepで乗算)。
CandleType 15分タイムフレーム すべての計算に使用するローソク足データタイプ。

すべてのパラメーターはStrategyParam<T>値として公開されているため、実行時に変更したり、StockSharpの最適化ツールを通じて最適化したりできます。

トレーディングルール

ロングセットアップ

  1. 遅いEMAが上昇している: 現在の値 > 前の値 > 2ローソク足前の値。
  2. 速いLWMAが上昇している: 現在の値 > 前の値 > 2ローソク足前の値。
  3. 前のローソク足の終値が遅いEMAの前の値を上回っています。
  4. 遅いEMAの現在の値が速いLWMAの現在の値を上回っています。
  5. 現在のポジションはフラットまたはショートです。
  6. すべての条件が満たされると、戦略はロングポジションに転換するためにVolume + |Position|の買い成行注文を送信します。

ショートセットアップ

  1. 遅いEMAが下降している: 現在の値 < 前の値 < 2ローソク足前の値。
  2. 速いLWMAが下降している: 現在の値 < 前の値 < 2ローソク足前の値。
  3. 前のローソク足の終値が遅いEMAの前の値を下回っています。
  4. 遅いEMAの現在の値が速いLWMAの現在の値を下回っています。
  5. 現在のポジションはフラットまたはロングです。
  6. すべての条件が満たされると、戦略はショートポジションに転換するためにVolume + |Position|の売り成行注文を送信します。

保護ロジック

  • StartProtectionStopLossPointsTakeProfitPointsSecurity.PriceStepで乗算して絶対価格オフセットに変換します。ストップロスとテイクプロフィットの注文は、エンジンがリミット注文がサポートされていなくてもポジションをクローズできるように成行エグジットとして出されます。
  • 反対のシグナルが現れると、戦略は保護注文に関係なく直ちにポジションを反転します。

実装の詳細

  • 完成したローソク足のみが処理され、オリジナルのMQLバージョンの新しいバーチェックをエミュレートします。
  • 戦略はインジケーター履歴の検索を避けるために、最後の2つの移動平均値と前の終値をプライベートフィールドに保持します。
  • IsFormedAndOnlineAndAllowTrading()は、すべてのデータストリームがアクティブで取引が許可されているときにのみ取引が行われることを保証します。
  • 取引方向ログ(LogInfo)はデバッグとライブモニタリングの透明性を提供します。
  • チャートレンダリング(利用可能な場合)はローソク足と両方の移動平均を描画して迅速な視覚的検証を行います。

使用上の注意

  • インストゥルメントのロットサイズに応じてVolumeを選択してください。戦略は常にVolume + |Position|サイズの成行注文を送信して効率的に反転します。
  • 定義されたPriceStepのないインストゥルメントで実行する場合、コードは1の値にフォールバックします。ティックサイズが異なる場合はそれに応じてパラメーターを調整してください。
  • 最適化は移動平均期間と保護距離に焦点を当てて、戦略を異なる市場に適応させることができます。
  • 必要に応じて追加のフィルター(ボラティリティ、セッション時間など)と組み合わせてください。モジュール構造により拡張が容易です。

推奨最適化範囲

  • SlowMaLength: ステップ5–10で20 – 120。
  • FastMaLength: ステップ1で2 – 10。
  • StopLossPoints / TakeProfitPoints: インストゥルメントのボラティリティに応じて50 – 200。

これらの範囲はオリジナルのエキスパート設定を忠実に反映しながら、他のインストゥルメントへの柔軟性を提供します。

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving average trend confirmation strategy.
/// Uses a slow EMA and a fast LWMA to detect synchronized trends.
/// Enters long when both averages slope upward, price stays above the slow EMA, and the slow EMA is above the fast LWMA.
/// Enters short when both averages slope downward, price stays below the slow EMA, and the slow EMA is below the fast LWMA.
/// Built-in stop-loss and take-profit are defined in instrument points.
/// </summary>
public class DualMaTrendConfirmationStrategy : Strategy
{
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousClose;
	private decimal _slowPrevious;
	private decimal _slowPrevious2;
	private decimal _fastPrevious;
	private decimal _fastPrevious2;
	private int _historyCount;

	/// <summary>
	/// Slow EMA period length.
	/// </summary>
	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	/// <summary>
	/// Fast LWMA period length.
	/// </summary>
	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in instrument points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in instrument points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="DualMaTrendConfirmationStrategy"/> class.
	/// </summary>
	public DualMaTrendConfirmationStrategy()
	{
		_slowMaLength = Param(nameof(SlowMaLength), 57)
			.SetDisplay("Slow EMA Length", "Period for the slow EMA trend filter", "Moving Averages")
			.SetRange(10, 200)
			;

		_fastMaLength = Param(nameof(FastMaLength), 3)
			.SetDisplay("Fast LWMA Length", "Period for the fast LWMA confirmation filter", "Moving Averages")
			.SetRange(1, 50)
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 100m)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
			.SetDisplay("Take Profit (points)", "Take-profit distance measured in instrument points", "Risk Management")
			.SetRange(10m, 500m)
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for moving average calculations", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		// Clear stored history so the next candle starts with a clean state.
		_previousClose = 0m;
		_slowPrevious = 0m;
		_slowPrevious2 = 0m;
		_fastPrevious = 0m;
		_fastPrevious2 = 0m;
		_historyCount = 0;
	}

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

		var slowEma = new ExponentialMovingAverage
		{
			Length = SlowMaLength
		};

		var fastLwma = new WeightedMovingAverage
		{
			Length = FastMaLength
		};

		var subscription = SubscribeCandles(CandleType);

		var step = Security.PriceStep ?? 1m;

		// Enable automatic stop-loss and take-profit management based on point offsets.
		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute),
			useMarketOrders: true);

		subscription
			.Bind(slowEma, fastLwma, (candle, slowValue, fastValue) => ProcessCandle(candle, slowValue, fastValue, slowEma, fastLwma))
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal slowValue, decimal fastValue, ExponentialMovingAverage slowEma, WeightedMovingAverage fastLwma)
	{
		// Work only with fully formed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure both indicators produced reliable values before trading logic.
		if (!slowEma.IsFormed || !fastLwma.IsFormed)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		// Accumulate at least two previous candles for slope calculations.
		if (_historyCount < 2)
		{
			UpdateHistory(slowValue, fastValue, candle.ClosePrice);
			return;
		}

		var slowRising = slowValue > _slowPrevious && _slowPrevious > _slowPrevious2;
		var fastRising = fastValue > _fastPrevious && _fastPrevious > _fastPrevious2;
		var slowFalling = slowValue < _slowPrevious && _slowPrevious < _slowPrevious2;
		var fastFalling = fastValue < _fastPrevious && _fastPrevious < _fastPrevious2;
		var priceAboveSlow = _previousClose > _slowPrevious;
		var priceBelowSlow = _previousClose < _slowPrevious;
		var slowAboveFast = slowValue > fastValue;
		var slowBelowFast = slowValue < fastValue;

		if (slowRising && fastRising && priceAboveSlow && slowAboveFast && Position <= 0)
		{
			BuyMarket();
		}
		else if (slowFalling && fastFalling && priceBelowSlow && slowBelowFast && Position >= 0)
		{
			SellMarket();
		}

		UpdateHistory(slowValue, fastValue, candle.ClosePrice);
	}

	private void UpdateHistory(decimal slowValue, decimal fastValue, decimal closePrice)
	{
		// Shift previous values so the last two candles are always available.
		_slowPrevious2 = _slowPrevious;
		_slowPrevious = slowValue;
		_fastPrevious2 = _fastPrevious;
		_fastPrevious = fastValue;
		_previousClose = closePrice;

		if (_historyCount < 2)
			_historyCount++;
	}
}