GitHub で見る

TradingLab のベスト MACD 戦略

この戦略は、StockSharp の高レベルの API を使用して、MetaTrader エキスパート アドバイザー「TradingLab_Best_MACD_Strategy」を複製します。移動平均構造、MACD クロスオーバー、動的なサポート/レジスタンス チェックを組み合わせて、勢いや最近の価格反応に合わせた方向性のある取引を開始します。

中核ロジック

  • キャンドル ソース – 構成可能な CandleType パラメータを使用して、完成したキャンドルをサブスクライブします。完了したキャンドルのみが取引の決定を生成します。
  • トレンド フィルター – 200 期間の単純移動平均により、一般的なトレンドが定義されます。ロングトレードでは終値が平均を上回ることが必要ですが、ショートトレードでは終値が平均を下回ることが必要です。
  • サポートとレジスタンスボックス – 20期間の最高値/最低値ウィンドウは、「ボックス」カスタムインジケーターをエミュレートします。以前のレジスタンスまたはサポートレベルに触れると、SignalValidity によって制御される限られた数のローソク足のショートまたはロングセットアップがアームされます。
  • MACD クロスオーバー – 標準の MACD (デフォルトでは 12、26、9) は、前のローソク足のシグナル ラインを横切り、ゼロ ラインの必要な側にとどまる必要があります。有効な各クロスオーバーは、ソース EA からのカウントダウン ロジックをミラーリングして、SignalValidity キャンドルの間信号を維持します。
  • エントリーのタイミング – MACD と対応するサポート/レジスタンスタッチの両方がまだ有効で、そのうちの少なくとも 1 つが現在のローソク足でトリガーされたときにポジションがオープンします。
  • エグジット ロジック – エントリー時に、動的なストップロスとテイクプロフィットのターゲットが移動平均距離に関連して計算されます。テイクプロフィット距離は、停止に使用される調整距離の RiskRewardMultiplier 倍です。保護出口は後続のローソク足を監視し、価格が保存されたレベルを突破すると ClosePosition() を呼び出します。

パラメーター

パラメータ 説明
OrderVolume すべての成行注文で送信される固定量。
SignalValidity MACD とサポート/レジスタンストリガーをアクティブに保つローソク足の数。
MaLength 単純移動平均トレンドフィルタの周期。
BoxPeriod 最近のレジスタンスとサポートを追跡する最高/最低ボックスのルックバック長。
MacdFastLength, MacdSlowLength, MacdSignalLength MACD の高速、低速、信号期間。
StopDistancePoints 移動平均からストップロスまでの距離。MetaTrader スタイルのポイントで表されます (シンボルの価格ステップを掛けた値)。
RiskRewardMultiplier 利食いターゲットを生成するために調整された MA 距離に適用される乗数。
CandleType サブスクライブするローソク足シリーズを説明するデータ型 (デフォルト: 1 時間の時間枠)。

注意事項

  • サポートとレジスタンスの検出は、前のローソク足が 20 期間の最高/最低レベルを突破するかどうかを監視するという元のアイデアに従います。タッチするたびに有効性カウンターが再起動されます。
  • ストップとターゲットは、新しいエントリごとに再計算され、確定的な方法で MetaTrader の足内モニタリングを模倣するために、完成した各ローソク足の高値/安値と比較してチェックされます。
  • 保護管理は機器 PriceStep に依存しています。機器がゼロステップを報告する場合、0.0001 のフォールバックが使用されます。
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;

public class TradingLabBestMacdStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _signalValidity;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<int> _boxPeriod;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<decimal> _stopDistancePoints;
	private readonly StrategyParam<decimal> _riskRewardMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private Highest _highest;
	private Lowest _lowest;
	private MovingAverageConvergenceDivergenceSignal _macd;

	private int _resistanceCounter;
	private int _supportCounter;
	private int _macdDownCounter;
	private int _macdUpCounter;

	private decimal? _prevMacdMain;
	private decimal? _prevMacdSignal;

	private decimal? _plannedStop;
	private decimal? _plannedTake;
	private Sides? _plannedSide;

	private decimal? _activeStop;
	private decimal? _activeTake;
	private Sides? _activeSide;

	private decimal? _previousHigh;
	private decimal? _previousLow;
	private bool _hasPreviousCandle;

	/// <summary>
	/// Initializes a new instance of <see cref="TradingLabBestMacdStrategy"/>.
	/// </summary>
	public TradingLabBestMacdStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetDisplay("Order Volume", "Fixed volume sent with each market order", "Risk")
			;

		_signalValidity = Param(nameof(SignalValidity), 7)
			.SetGreaterThanZero()
			.SetDisplay("Signal Validity", "Number of candles a MACD or box trigger remains active", "Filters")
			;

		_maLength = Param(nameof(MaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Length", "Simple moving average period used as the trend filter", "Filters")
			;

		_boxPeriod = Param(nameof(BoxPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Box Period", "Lookback length for the support/resistance box", "Filters")
			;

		_macdFastLength = Param(nameof(MacdFastLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast Length", "Fast EMA length for MACD", "Indicators")
			;

		_macdSlowLength = Param(nameof(MacdSlowLength), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow Length", "Slow EMA length for MACD", "Indicators")
			;

		_macdSignalLength = Param(nameof(MacdSignalLength), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal Length", "Signal line length for MACD", "Indicators")
			;

		_stopDistancePoints = Param(nameof(StopDistancePoints), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Distance (points)", "Protective stop distance from the moving average expressed in points", "Risk")
			;

		_riskRewardMultiplier = Param(nameof(RiskRewardMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Risk-Reward Multiplier", "Multiplier applied to derive the take-profit distance", "Risk")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Data type used to subscribe for candles", "General")
			;
	}

	/// <summary>
	/// Fixed volume sent with every market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Number of candles that keep MACD and support/resistance triggers active.
	/// </summary>
	public int SignalValidity
	{
		get => _signalValidity.Value;
		set => _signalValidity.Value = value;
	}

	/// <summary>
	/// Period for the simple moving average trend filter.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Lookback length for the support/resistance box.
	/// </summary>
	public int BoxPeriod
	{
		get => _boxPeriod.Value;
		set => _boxPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Signal line length used by MACD.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Protective stop distance measured in MetaTrader points.
	/// </summary>
	public decimal StopDistancePoints
	{
		get => _stopDistancePoints.Value;
		set => _stopDistancePoints.Value = value;
	}

	/// <summary>
	/// Multiplier applied to the adjusted moving-average distance to build the take-profit target.
	/// </summary>
	public decimal RiskRewardMultiplier
	{
		get => _riskRewardMultiplier.Value;
		set => _riskRewardMultiplier.Value = value;
	}

	/// <summary>
	/// Candle data type used to subscribe for historical bars.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_sma = null;
		_highest = null;
		_lowest = null;
		_macd = null;

		_resistanceCounter = 0;
		_supportCounter = 0;
		_macdDownCounter = 0;
		_macdUpCounter = 0;

		_prevMacdMain = null;
		_prevMacdSignal = null;

		_plannedStop = null;
		_plannedTake = null;
		_plannedSide = null;

		_activeStop = null;
		_activeTake = null;
		_activeSide = null;

		_previousHigh = null;
		_previousLow = null;
		_hasPreviousCandle = false;
	}

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

		// Configure the indicators that replicate the MetaTrader calculations.
		_sma = new SimpleMovingAverage { Length = MaLength };
		_highest = new Highest { Length = BoxPeriod };
		_lowest = new Lowest { Length = BoxPeriod };
		_macd = new MovingAverageConvergenceDivergenceSignal { Macd = { ShortMa = { Length = MacdFastLength }, LongMa = { Length = MacdSlowLength } }, SignalMa = { Length = MacdSignalLength } };

		// Subscribe to the configured candle stream and bind indicator outputs to the handler.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { _sma, _highest, _lowest, _macd }, ProcessCandle)
			.Start();

		// removed StartProtection
	}

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

		// Manage trailing exits before analysing new signals.
		CheckProtectiveLevels(candle);

		if (values.Length != 4)
		{
			UpdatePreviousCandle(candle, null, null);
			return;
		}

		var smaValue = values[0].ToDecimal();
		var resistanceValue = values[1].ToDecimal();
		var supportValue = values[2].ToDecimal();

		if (values[3] is not IMovingAverageConvergenceDivergenceSignalValue macdValue)
		{
			UpdatePreviousCandle(candle, null, null);
			return;
		}

		var macdMain = macdValue.Macd;
		var macdSignal = macdValue.Signal;

		if (!_sma.IsFormed || !_highest.IsFormed || !_lowest.IsFormed || !_macd.IsFormed)
		{
			UpdatePreviousCandle(candle, macdMain, macdSignal);
			return;
		}

		// Decrease counters that track how many candles each signal remains active.
		if (_resistanceCounter > 0)
			_resistanceCounter--;
		if (_supportCounter > 0)
			_supportCounter--;
		if (_macdDownCounter > 0)
			_macdDownCounter--;
		if (_macdUpCounter > 0)
			_macdUpCounter--;

		var point = GetPointValue();

		if (_hasPreviousCandle)
		{
			// Detect fresh touches of the synthetic resistance/support levels.
			if (_previousHigh.HasValue && resistanceValue > 0m && _previousHigh.Value > resistanceValue)
			{
				_resistanceCounter = SignalValidity;
			}

			if (_previousLow.HasValue && supportValue > 0m && _previousLow.Value < supportValue)
			{
				_supportCounter = SignalValidity;
			}
		}

		if (_prevMacdMain.HasValue && _prevMacdSignal.HasValue)
		{
			// Track MACD crossovers relative to the zero line.
			if (macdMain < macdSignal && _prevMacdMain.Value > _prevMacdSignal.Value && macdMain > 0m)
			{
				_macdDownCounter = SignalValidity;
			}

			if (macdMain > macdSignal && _prevMacdMain.Value < _prevMacdSignal.Value && macdMain < 0m)
			{
				_macdUpCounter = SignalValidity;
			}
		}

		var volume = OrderVolume;

		if (volume > 0m)
		{
			// Evaluate entry conditions once both the MACD and box counters are armed.
			var longSignalActive = _macdUpCounter > 0 && candle.ClosePrice > smaValue;
			var longTriggeredNow = true;
			if (longSignalActive && longTriggeredNow && Position <= 0)
			{
				var stopOffset = StopDistancePoints * point;
				var adjustedDistance = candle.ClosePrice - smaValue + stopOffset;
				if (adjustedDistance > 0m)
				{
					_plannedStop = smaValue - stopOffset;
					_plannedTake = candle.ClosePrice + adjustedDistance * RiskRewardMultiplier;
					_plannedSide = Sides.Buy;
					BuyMarket(volume);
				}
			}

			var shortSignalActive = _macdDownCounter > 0 && candle.ClosePrice < smaValue;
			var shortTriggeredNow = true;
			if (shortSignalActive && shortTriggeredNow && Position >= 0)
			{
				var stopOffset = StopDistancePoints * point;
				var adjustedDistance = smaValue - candle.ClosePrice + stopOffset;
				if (adjustedDistance > 0m)
				{
					_plannedStop = smaValue + stopOffset;
					_plannedTake = candle.ClosePrice - adjustedDistance * RiskRewardMultiplier;
					_plannedSide = Sides.Sell;
					SellMarket(volume);
				}
			}
		}

		UpdatePreviousCandle(candle, macdMain, macdSignal);
	}

	private void CheckProtectiveLevels(ICandleMessage candle)
	{
		if (_activeSide == null || Position == 0)
			return;

		// Close the position if price violates the stored stop-loss or take-profit.
		if (_activeSide == Sides.Buy && Position > 0)
		{
			if (_activeStop.HasValue && candle.LowPrice <= _activeStop.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}

			if (_activeTake.HasValue && candle.HighPrice >= _activeTake.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}
		}
		else if (_activeSide == Sides.Sell && Position < 0)
		{
			if (_activeStop.HasValue && candle.HighPrice >= _activeStop.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
				return;
			}

			if (_activeTake.HasValue && candle.LowPrice <= _activeTake.Value)
			{
				ClearPlannedLevels();
				ClearActiveLevels();
				ClosePosition();
			}
		}
	}

	private void UpdatePreviousCandle(ICandleMessage candle, decimal? macdMain, decimal? macdSignal)
	{
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
		_hasPreviousCandle = true;
		_prevMacdMain = macdMain;
		_prevMacdSignal = macdSignal;
	}

	private decimal GetPointValue()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0.0001m;

		return step;
	}

	private void ClosePosition()
	{
		if (Position > 0)
			SellMarket(Position);
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));
	}

	private void ClearPlannedLevels()
	{
		_plannedStop = null;
		_plannedTake = null;
		_plannedSide = null;
	}

	private void ClearActiveLevels()
	{
		_activeStop = null;
		_activeTake = null;
		_activeSide = null;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade.Order?.Security != Security)
			return;

		if (Position == 0)
		{
			ClearActiveLevels();
			ClearPlannedLevels();
			return;
		}

		if (Position > 0)
		{
			if (_plannedSide == Sides.Buy)
			{
				_activeStop = _plannedStop;
				_activeTake = _plannedTake;
				_activeSide = Sides.Buy;
				ClearPlannedLevels();
			}
			else
			{
				_activeSide = Sides.Buy;
			}
		}
		else if (Position < 0)
		{
			if (_plannedSide == Sides.Sell)
			{
				_activeStop = _plannedStop;
				_activeTake = _plannedTake;
				_activeSide = Sides.Sell;
				ClearPlannedLevels();
			}
			else
			{
				_activeSide = Sides.Sell;
			}
		}
	}
}