GitHub で見る

MACD 古典的な戦略のサンプル

この戦略は、StockSharp の高レベルの API を使用して、MetaTrader 4「MACD サンプル」エキスパート アドバイザを再現します。これは単一の商品で両方向に取引し、元のロジックを反映しています。トレンド EMA が方向を確認している間に、MACD ラインがゼロの正しい側でシグナルラインを横切ったときに取引を開始します。保護命令は、オプションのトレーリング ストップを使用して、StockSharp の組み込みリスク マネージャーに変換されます。

取引ロジック

  1. MACD と EMA に十分な履歴が含まれるように、少なくとも 100 個のキャンドルが完成するまで待ちます。
  2. 標準の MACD (12, 26, 9) を、その信号線と方向性フィルターとして機能する 26 期間の指数移動平均とともに計算します。
  3. ロングエントリー – ポジションが存在しない場合にのみ許可されます。 MACD はゼロ未満であるがシグナル ラインを上回っている必要があり、前の MACD 値はシグナルを下回っており、MACD の絶対値は設定可能な MacdOpenLevel しきい値 (価格ポイント単位) を超えており、トレンド EMA は上昇しています。
  4. ショートエントリー – 対称セットアップ: MACD は信号を下回るゼロクロスを上回っており、前の MACD は信号を上回っており、現在の値は MacdOpenLevel のしきい値を超えており、トレンド EMA は下降しています。
  5. ロングイグジット – MACD がゼロの正側の信号の下で戻り、値が MacdCloseLevel を超えたとき。ポジションは、StartProtection が管理するトレーリング ストップまたはテイクプロフィットによって、より早くクローズすることもできます。
  6. ショートエグジット – MACD が負側の信号をクロスオーバーし、MACD の絶対値が MacdCloseLevel を超えるとき、または保護モジュールによって。

戦略が一度に複数のポジションを保持することはありません。すべてのエントリは、Volume プロパティによってサイズ設定された成行注文を使用します。保護ロジックは StockSharp のリスク コントローラーに依存しているため、テイクプロフィット ディスタンスとトレーリング ストップは商品ティック サイズと同期したままになります。

パラメーター

名前 説明 デフォルト 注意事項
FastEmaPeriod MACD によって使用される高速の EMA 期間。 12 最適化可能な範囲は 6 ~ 18。
SlowEmaPeriod MACD が使用している遅い EMA 期間。 26 最適化可能な範囲は 20 ~ 32 です。
SignalPeriod MACD 以内に EMA 期間を信号します。 9 最適化可能な範囲は 5 ~ 13 です。
TrendMaPeriod 指向性フィルターの EMA の長さ。 26 最適化可能な範囲は 20 ~ 40 です。
MacdOpenLevel MACD ポイント (価格ステップ) で表されるエントリーしきい値。 3 MT4 コードの MACDOpenLevel に相当します。
MacdCloseLevel 終了しきい値は MACD ポイントで表されます。 2 MACDCloseLevel と同等。
TakeProfitPoints 価格ポイント(商品ティックサイズを乗じたもの)で利益を得る。 50 テイクプロフィットを無効にするには、0 に設定します。
TrailingStopPoints 価格帯のトレーリングストップ。 30 トレーリングストップを無効にするには、0 に設定します。
CandleType インジケーターの更新に使用されるローソク足シリーズ。 5分の時間枠 あらゆる StockSharp キャンドル タイプをサポートします。

実装メモ

  • MACD および EMA インジケーターは、BindEx/Bind を通じてキャンドル サブスクリプションにバインドされており、手動でキャッシュせずに、すぐに使用できる値を StockSharp にフィードできます。
  • ポジションは、プラットフォームが IsFormedAndOnlineAndAllowTrading() を報告する場合にのみオープンされ、履歴データがまだ読み込まれている間、または接続がオフラインである間は取引できません。
  • 「ポイント」を参照するすべてのしきい値は、商品価格ステップによって自動的に調整され、MetaTrader の Point 定数を模倣します。
  • StartProtection は、MetaTrader の固定テイクプロフィットとトレーリングストップを取引所側の保護注文に変換します。対応するパラメータを変更して、各モジュールを有効または無効にします。
  • 広範なログ (LogInfo) により各取引の決定が文書化され、移行検証中の元のエキスパート アドバイザーとの比較が簡素化されます。

使用のヒント

  • 元の EA は、日中の時間枠で外国為替メジャーをターゲットにしています。同様のシンボルから始めて、楽器が異なるティックサイズを使用している場合はパラメータを調整します。
  • 特殊なティック値を持つシンボルをテストする場合は、Security.PriceStep が構成されていることを確認してください。それ以外の場合は、デフォルトの 1.0 が使用されます。
  • ポジションごとのストップを超えてアカウントレベルの資金管理が必要な場合は、StockSharp のポートフォリオ保護機能と組み合わせてください。

タグ

  • トレンドフォロー
  • 勢い
  • MACD クロスオーバー
  • 日中 (デフォルトは 5 分)
  • トレーリングストップ+テイクプロフィット
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 strategy that replicates the original MetaTrader "MACD Sample" expert advisor behaviour.
/// </summary>
public class MacdSampleClassicStrategy : Strategy
{

	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _trendMaPeriod;
	private readonly StrategyParam<decimal> _macdOpenLevel;
	private readonly StrategyParam<decimal> _macdCloseLevel;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _minimumHistoryCandles;

	private decimal _pointSize;
	private decimal? _prevMacd;
	private decimal? _prevSignal;
	private decimal? _trendMaCurrent;
	private decimal? _trendMaPrevious;
	private int _finishedCandles;
	private DateTimeOffset? _lastProcessedTime;

	/// <summary>
	/// Fast EMA period for the MACD indicator.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for the MACD indicator.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Period of the trend EMA used as direction filter.
	/// </summary>
	public int TrendMaPeriod
	{
		get => _trendMaPeriod.Value;
		set => _trendMaPeriod.Value = value;
	}

	/// <summary>
	/// Threshold for MACD entries expressed in points (price steps).
	/// </summary>
	public decimal MacdOpenLevel
	{
		get => _macdOpenLevel.Value;
		set => _macdOpenLevel.Value = value;
	}

	/// <summary>
	/// Threshold for MACD exits expressed in points (price steps).
	/// </summary>
	public decimal MacdCloseLevel
	{
		get => _macdCloseLevel.Value;
		set => _macdCloseLevel.Value = value;
	}

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

	/// <summary>
	/// Trailing stop distance measured in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	/// <summary>
	/// Number of finished candles required before the strategy begins trading.
	/// </summary>
	public int MinimumHistoryCandles
	{
		get => _minimumHistoryCandles.Value;
		set => _minimumHistoryCandles.Value = value;
	}

	/// <summary>
	/// Initialize default parameters for the MACD Sample strategy.
	/// </summary>
	public MacdSampleClassicStrategy()
	{
		Volume = 1;

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
		.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators")
		
		.SetOptimize(6, 18, 2);

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
		.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators")
		
		.SetOptimize(20, 32, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
		.SetDisplay("Signal EMA", "Signal EMA period for MACD", "Indicators")
		
		.SetOptimize(5, 13, 2);

		_trendMaPeriod = Param(nameof(TrendMaPeriod), 26)
		.SetDisplay("Trend EMA", "EMA period used for directional filter", "Indicators")
		
		.SetOptimize(20, 40, 2);

		_macdOpenLevel = Param(nameof(MacdOpenLevel), 0m)
		.SetDisplay("MACD Open", "Entry threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 5m, 1m);

		_macdCloseLevel = Param(nameof(MacdCloseLevel), 0m)
		.SetDisplay("MACD Close", "Exit threshold in MACD points", "Signals")
		
		.SetOptimize(1m, 4m, 1m);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
		.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
		
		.SetOptimize(20m, 100m, 10m);

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
		.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
		
		.SetOptimize(10m, 60m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used for analysis", "General");
		_minimumHistoryCandles = Param(nameof(MinimumHistoryCandles), 30)
			.SetDisplay("Warm-up candles", "Number of finished candles required before trading starts", "General")
			.SetGreaterThanZero();
	}

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

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

		_pointSize = 0m;
		_prevMacd = null;
		_prevSignal = null;
		_trendMaCurrent = null;
		_trendMaPrevious = null;
		_finishedCandles = 0;
		_lastProcessedTime = null;
	}

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

		_pointSize = Security?.PriceStep ?? 1m;

		// Configure indicators exactly as in the original expert advisor.
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastEmaPeriod },
				LongMa = { Length = SlowEmaPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

		var trendMa = new ExponentialMovingAverage { Length = TrendMaPeriod };

		// Subscribe to candles and bind indicators for automatic updates.
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, ProcessMacdValues);
		subscription.Bind(trendMa, ProcessTrendMaValue);
		subscription.Start();

		// Visualize price, indicators and trades when a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, macd);
			DrawIndicator(area, trendMa);
			DrawOwnTrades(area);
		}

		var takeProfitDistance = TakeProfitPoints * _pointSize;
		var trailingDistance = TrailingStopPoints * _pointSize;

		if (takeProfitDistance > 0m || trailingDistance > 0m)
		{
			StartProtection(
			takeProfitDistance > 0m ? new Unit(takeProfitDistance, UnitTypes.Absolute) : null,
			trailingDistance > 0m ? new Unit(trailingDistance, UnitTypes.Absolute) : null,
			isStopTrailing: trailingDistance > 0m);
		}
	}

	private void ProcessTrendMaValue(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Keep current and previous EMA values for the directional filter.
		_trendMaPrevious = _trendMaCurrent;
		_trendMaCurrent = maValue;
	}

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

		// Avoid duplicate processing for the same candle.
		if (_lastProcessedTime != candle.OpenTime)
		{
			_lastProcessedTime = candle.OpenTime;
			_finishedCandles++;
		}

		// indicators checked below

		if (_finishedCandles < MinimumHistoryCandles)
		return;

		var macdSignal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		if (macdSignal.Macd is not decimal macdCurrent ||
		macdSignal.Signal is not decimal signalCurrent)
		{
			return;
		}

		if (_prevMacd is not decimal macdPrevious ||
		_prevSignal is not decimal signalPrevious ||
		_trendMaCurrent is not decimal trendMaCurrent ||
		_trendMaPrevious is not decimal trendMaPrevious)
		{
			_prevMacd = macdCurrent;
			_prevSignal = signalCurrent;
			return;
		}

		var macdOpenThreshold = MacdOpenLevel * _pointSize;
		var macdCloseThreshold = MacdCloseLevel * _pointSize;

		// Determine trend direction using EMA slope.
		var isTrendUp = trendMaCurrent > trendMaPrevious;
		var isTrendDown = trendMaCurrent < trendMaPrevious;

		var buySignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdOpenThreshold &&
		isTrendUp;

		var sellSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdOpenThreshold &&
		isTrendDown;

		var exitLongSignal = macdCurrent > 0m &&
		macdCurrent < signalCurrent &&
		macdPrevious > signalPrevious &&
		macdCurrent > macdCloseThreshold;

		var exitShortSignal = macdCurrent < 0m &&
		macdCurrent > signalCurrent &&
		macdPrevious < signalPrevious &&
		Math.Abs(macdCurrent) > macdCloseThreshold;

		if (buySignal && Position == 0m)
		{
			// MACD crossed up in negative territory and EMA confirms uptrend.
			BuyMarket();
			LogInfo($"Open long: MACD {macdCurrent:F5} above signal {signalCurrent:F5}.");
		}
		else if (sellSignal && Position == 0m)
		{
			// MACD crossed down in positive territory and EMA confirms downtrend.
			SellMarket();
			LogInfo($"Open short: MACD {macdCurrent:F5} below signal {signalCurrent:F5}.");
		}
		else if (exitLongSignal && Position > 0m)
		{
			// MACD crossed back below the signal line in positive zone - close long.
			SellMarket();
			LogInfo($"Close long: MACD {macdCurrent:F5} dropped under signal {signalCurrent:F5}.");
		}
		else if (exitShortSignal && Position < 0m)
		{
			// MACD crossed back above the signal line in negative zone - close short.
			BuyMarket();
			LogInfo($"Close short: MACD {macdCurrent:F5} rose above signal {signalCurrent:F5}.");
		}

		_prevMacd = macdCurrent;
		_prevSignal = signalCurrent;
	}
}