GitHub で見る

MACD ゼロフィルタークロス

概要

MACD Zero Filtered Cross は、MetaTrader 4 Expert Advisor Robot_MACD_12.26.9 の C# ポートです。オリジナルロボットが見守る MACD ラインとそのシグナルラインの間のクロスオーバーですが、両方のラインが存在する間のみロングエントリーが発生するように新しい取引をフィルターします。 ゼロ軸の下に留まり、両方の線が軸の上に留まっている間のみショートエントリーが発生します。 StockSharp バージョンでは、 同じクロスオーバー ロジックにより、フレームワークと統合されたリスク管理が追加されます (ポートフォリオ バランス フィルタリングと統合されたテイクプロフィット) 管理)、最適化をサポートする戦略パラメーターを通じてすべての構成可能な値を公開します。

この戦略は、設定可能な時間枠からの完成したローソク足に依存します。インジケーター値は組み込みによって提供されます。 MovingAverageConvergenceDivergenceSignal インジケーター。戦略が高レベルの API および BindEx の使用ガイドラインを尊重します。

戦略ロジック

指標の計算

  • MACD ライン – 高速指数移動平均と低速指数移動平均の差 (デフォルトの長さ: 12 と 26)。
  • シグナルライン – MACD ラインに適用される指数移動平均 (デフォルトの長さ: 9)。
  • ゼロフィルター – ゼロを基準とした両方のラインの符号により、クロスオーバーがポジションエントリーをトリガーできるかどうかが決まります。

エントリールール

  • 長いセットアップ
    • MACD 線は信号線 (MACD[t-1] < Signal[t-1] および MACD[t] > Signal[t]) の上で交差する必要があります。
    • MACD ラインと信号ラインは両方ともクロスオーバー後はゼロ未満でなければなりません。
    • 現在のネットポジションはフラットまたはショートでなければなりません。既存のショートは、ロングを試行する直前に閉じられます。
    • オプションのバランスフィルターでは、新しい注文が送信される前に、ポートフォリオ値が構成可能な最小値を超える必要があります。
  • 簡単なセットアップ
    • MACD 線は信号線 (MACD[t-1] > Signal[t-1]MACD[t] < Signal[t]) の下で交差する必要があります。
    • クロスオーバー後は両方のインジケーター ラインがゼロより上になければなりません。
    • 現在のネットポジションはフラットまたはロングでなければなりません。既存のロングは、新しいショートが送信される前にフラット化されます。
    • バランス フィルターは短いエントリに対称的に適用されます。

終了ルール

  • クロスオーバーエグジット – MACD ラインがシグナルラインを通って現在のポジションに戻ると、ストラテジーは終了します。 市場での公開取引。これはオリジナルの EA を反映しており、以前は常に反対側のクロスオーバーの位置を平坦化していました。 新しい機会を探しています。
  • 固定テイクプロフィット – ユニットベースのテイクプロフィット(価格ポイントで表現)は、StartProtection を介して適用されます。レベルは合ってる MQL パラメータ TakeProfit を使用し、楽器のポイント値を使用します。

リスクと資本の管理

  • ボリューム処理LotVolume パラメーターは MT4 ロットサイズを反映します。この戦略は、各エントリに対してその正確なボリュームを送信します。
  • バランス フィルターMinimumBalancePerVolume パラメーターは、リクエストされたボリュームを乗算して最小ポートフォリオを決定します 新しいエントリを許可する前に値が必要です。残高チェックが失敗した場合、ストラテジーはメッセージをログに記録し、取引をスキップします。 元のフリーマージンセーフガードと一致します。
  • データの整合性 – シグナルは完成したキャンドルでのみ処理され、IsFormedAndOnlineAndAllowTrading() がそれを確認した後に処理されます。 接続とインジケーターの両方が準備完了です。

パラメーター

パラメータ 説明
FastPeriod 高速 MACD コンポーネントの EMA の長さ。
SlowPeriod 遅い MACD コンポーネントの EMA の長さ。
SignalPeriod MACD 信号線の EMA の長さ。
TakeProfitPoints 価格ポイントにおける保護的なテイクプロフィットまでの距離。無効にするには、0 に設定します。
LotVolume 基本注文量。MT4 バージョンの「ロット」入力に相当します。
MinimumBalancePerVolume ポジションをオープンする前に取引量単位ごとに必要なポートフォリオの最小値。フィルタをスキップするには、0 に設定します。
CandleType ローソク足を構築し、インジケーターチェーンにフィードするために使用される時間枠。

追加の注意事項

  • この戦略では、BindEx オーバーロードを使用して、MACD インジケーターが MACD とシグナル値の両方を単一で提供できるようにします。 GetValue への手動呼び出しを行わないコールバック。
  • C# コード内のコメントはすべて英語で書かれており、プロジェクト ガイドラインに準拠しています。
  • この戦略の Python 翻訳はありません。 C# 実装のみが API パッケージで提供されます。
  • 元の MT4 の動作を最も忠実に再現するには、EA が実行されていたチャートと一致するローソク足の時間枠を選択します。 そして、ボリュームパラメータを以前に取引されたロットサイズと一致させます。
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>
/// Port of the MetaTrader 4 expert advisor Robot_MACD_12.26.9.
/// Trades MACD signal-line crossovers, but only enters longs while both lines stay below zero and shorts while they stay above zero.
/// Includes an optional balance filter and a fixed take-profit expressed in instrument points.
/// </summary>
public class MacdZeroFilteredCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _lotVolume;
	private readonly StrategyParam<decimal> _minimumBalancePerVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private decimal? _previousMacd;
	private decimal? _previousSignal;

	/// <summary>
	/// Fast EMA length used by MACD.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA length used by MACD.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Base trading volume that mirrors the "Lots" setting in the original robot.
	/// </summary>
	public decimal LotVolume
	{
		get => _lotVolume.Value;
		set => _lotVolume.Value = value;
	}

	/// <summary>
	/// Minimum account value required per traded volume unit before opening new positions.
	/// </summary>
	public decimal MinimumBalancePerVolume
	{
		get => _minimumBalancePerVolume.Value;
		set => _minimumBalancePerVolume.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public MacdZeroFilteredCrossStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Short EMA period for MACD", "MACD")
			
			.SetOptimize(6, 18, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Long EMA period for MACD", "MACD")
			
			.SetOptimize(20, 40, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Signal Period", "Signal line length for MACD", "MACD")
			
			.SetOptimize(6, 12, 1);

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300m)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Fixed take-profit distance in price points", "Risk Management");

		_lotVolume = Param(nameof(LotVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Lot Volume", "Trading volume per order", "Trading")
			.SetOptimize(1m, 5m, 1m);

		_minimumBalancePerVolume = Param(nameof(MinimumBalancePerVolume), 1000m)
			.SetNotNegative()
			.SetDisplay("Balance per Volume", "Required balance per volume unit before opening trades", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe that drives MACD calculations", "General");
	}

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

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

		_previousMacd = null;
		_previousSignal = null;
	}

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

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = FastPeriod },
				LongMa = { Length = SlowPeriod },
			},
			SignalMa = { Length = SignalPeriod }
		};

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

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

		if (TakeProfitPoints > 0m)
		{
			StartProtection(new Unit(TakeProfitPoints, UnitTypes.Absolute), null);
		}

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		// Work only with completed candles to avoid premature signals.
		if (candle.State != CandleStates.Finished)
			return;

		// Skip processing when the strategy is not ready or trading is disabled.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var typed = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		// Ensure both MACD and signal components are available before calculating.
		if (typed.Macd is not decimal macdLine || typed.Signal is not decimal signalLine)
			return;

		if (_previousMacd is decimal prevMacd && _previousSignal is decimal prevSignal)
		{
			var crossUp = prevMacd < prevSignal && macdLine > signalLine;
			var crossDown = prevMacd > prevSignal && macdLine < signalLine;

			// Close existing long position when MACD crosses below the signal line.
			if (crossDown && Position > 0m)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_previousMacd = macdLine;
				_previousSignal = signalLine;
				return;
			}

			// Close existing short position when MACD crosses above the signal line.
			if (crossUp && Position < 0m)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_previousMacd = macdLine;
				_previousSignal = signalLine;
				return;
			}

			// Enter long only when the crossover happens below zero (momentum still negative).
			if (crossUp && macdLine < 0m && signalLine < 0m && Position <= 0m && HasRequiredBalance())
			{
				var volume = LotVolume;
				BuyMarket(volume);
			}

			// Enter short only when the crossover happens above zero (momentum still positive).
			else if (crossDown && macdLine > 0m && signalLine > 0m && Position >= 0m && HasRequiredBalance())
			{
				var volume = LotVolume;
				SellMarket(volume);
			}
		}

		_previousMacd = macdLine;
		_previousSignal = signalLine;
	}

	private bool HasRequiredBalance()
	{
		// If portfolio information is not available, assume requirements are met.
		var balance = Portfolio?.CurrentValue;
		if (balance is null)
			return true;

		var required = MinimumBalancePerVolume * LotVolume;
		if (required <= 0m)
			return true;

		if (balance.Value >= required)
			return true;

		return false;
	}
}