GitHub で見る

MACD AO パターン戦略

概要

この戦略は、FORTRADERのMACD.mq5エキスパートアドバイザーを忠実にStockSharpへ移植したものです。MACDオシレーターがゼロラインから大きく乖離した後、中立方向へフックバックする「AOP」パターンを実装しています。フックが確認されると、予想される反転方向にエントリーし、pips単位の固定ストップロスとテイクプロフィットを即座に適用します。

戦略ロジック

データ準備

  • CandleTypeパラメーターで選択されたローソク足シリーズ(デフォルト:5分足)で動作します。
  • 設定可能な速足、遅足、シグナル期間の標準MACDインジケーターを使用します(デフォルト:12/26/9)。
  • MQLのインデックスベースアクセス(iMACD(...,1..3))を再現するため、直近3本の完成したローソク足のMACD主線値を保存します。

ショート設定(弱気フック)

  1. アーミング – 最後に閉じたローソク足のMACD主線がBearishExtremeLevel(デフォルト−0.0015)を下回ると、戦略は反転の監視を開始します。
  2. 中立プルバック – MACDがBearishNeutralLevel(デフォルト−0.0005)を上回ると、フック検証ステージが有効になります。
  3. フック確認 – 直前の3つのMACD値がローカル最大値(macd₁ < macd₂ > macd₃)を形成し、最新値が中立レベルを下回り、古い値が上回っている必要があります。これにより、モメンタムが衰退していることを確認する元のパターンが再現されます。
  4. エントリー – ロングポジションが開いていない場合(Position <= 0)、OrderVolumeの成行売り注文が送信されます。保護レベルが即座に計算されます:エントリーのStopLossPips上にストップロス、TakeProfitPips下にテイクプロフィット(GetPipSizeで価格に変換)。
  5. MACDの正の読みはセットアップをキャンセルし、新しい深い負の動きが現れるまで内部の弱気ステートマシンをリセットします。

ロング設定(強気フック)

  1. アーミング – MACDがBullishExtremeLevel(デフォルト+0.0015)を上回ると、強気監視モードが有効になります。
  2. 即時キャンセル – MACDがゼロを下回ると、MQLのロジックを反映して強気シナリオが放棄されます。
  3. 中立プルバックBullishNeutralLevel(デフォルト+0.0005)を下回る下落でフック確認の準備が整います。
  4. フック確認 – 3つの保存されたMACD値が中立しきい値を尊重しながらローカル最小値(macd₁ > macd₂ < macd₃)を作る必要があります。
  5. エントリー – ショートのエクスポージャーがない場合(Position >= 0)、戦略はOrderVolumeで成行買いし、ショートルールと対称的にエントリー周辺にストップロスとテイクプロフィットを設定します。

リスク管理

  • ストップロスとテイクプロフィットは常に_stopPrice_takePriceで有効です。元のEAのブローカー側の執行をエミュレートするため、記録された高値/安値を使用して完成したローソク足ごとに評価されます。
  • PipsはSecurity.PriceStepを使用して絶対価格に変換されます。3桁と5桁のFXシンボルの場合、分数pipsのMQL調整に合わせてステップに10が乗算されます。
  • 保護レベルによりポジションを終了すると、即座にクリアして次のローソク足での新しいセットアップを待ちます。

パラメーター

パラメーター 説明 デフォルト値
CandleType 戦略が処理するローソク足データシリーズ。 5分時間軸
OrderVolume 各成行注文で送信する出来高。 0.1
TakeProfitPips pipsでの利益目標までの距離。最適化対象。 60
StopLossPips pipsでのストップロスまでの距離。最適化対象。 70
MacdFastPeriod MACDの速EMA長。 12
MacdSlowPeriod MACDの遅EMA長。 26
MacdSignalPeriod MACDのシグナルEMA長。 9
BearishExtremeLevel ショート機会をアームする負のMACDしきい値。 −0.0015
BearishNeutralLevel 弱気フックの検証に使用する負のMACDしきい値。 −0.0005
BullishExtremeLevel ロング機会をアームする正のMACDしきい値。 +0.0015
BullishNeutralLevel 強気フックの検証に使用する正のMACDしきい値。 +0.0005

追加メモ

  • 戦略はMQLの元のPrevBarsガードを模倣して、完成したローソク足ごとに1回のみ反応します。
  • ストップロス/テイクプロフィット管理は純粋に価格ベースです。ステートマシンの完全なサイクルが再び完了するまで、トレーリング調整や再エントリーはありません。
  • ソースEAはヘッジアカウント向けに設計されていますが、この移植版は新規注文送信前にPositionをチェックして単一ネットポジションを強制します。
  • 要望通りPythonバージョンは提供されていません。
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-based reversal strategy that reproduces the FORTRADER AOP pattern.
/// </summary>
public class MacdAoPatternStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<decimal> _bearishExtremeLevel;
	private readonly StrategyParam<decimal> _bearishNeutralLevel;
	private readonly StrategyParam<decimal> _bullishExtremeLevel;
	private readonly StrategyParam<decimal> _bullishNeutralLevel;
	private readonly StrategyParam<DataType> _candleType;

	private MACD _macd = null!;

	private decimal? _macdPrev1;
	private decimal? _macdPrev2;
	private decimal? _macdPrev3;

	private bool _bearishStageArmed;
	private bool _bearishTriggerReady;
	private bool _bearishSignalPending;

	private bool _bullishStageArmed;
	private bool _bullishTriggerReady;
	private bool _bullishSignalPending;

	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Distance to the take-profit level measured in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Distance to the stop-loss level measured in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Volume used for each market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

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

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

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

	/// <summary>
	/// MACD level that arms the bearish setup when the oscillator stretches deeply negative.
	/// </summary>
	public decimal BearishExtremeLevel
	{
		get => _bearishExtremeLevel.Value;
		set => _bearishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bearish hook back toward the zero line.
	/// </summary>
	public decimal BearishNeutralLevel
	{
		get => _bearishNeutralLevel.Value;
		set => _bearishNeutralLevel.Value = value;
	}

	/// <summary>
	/// MACD level that arms the bullish setup when the oscillator stretches deeply positive.
	/// </summary>
	public decimal BullishExtremeLevel
	{
		get => _bullishExtremeLevel.Value;
		set => _bullishExtremeLevel.Value = value;
	}

	/// <summary>
	/// MACD level that confirms the bullish hook back toward the zero line.
	/// </summary>
	public decimal BullishNeutralLevel
	{
		get => _bullishNeutralLevel.Value;
		set => _bullishNeutralLevel.Value = value;
	}

	/// <summary>
	/// Candle data type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="MacdAoPatternStrategy"/>.
	/// </summary>
	public MacdAoPatternStrategy()
	{
		_takeProfitPips = Param(nameof(TakeProfitPips), 60)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk")
			;

		_stopLossPips = Param(nameof(StopLossPips), 70)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk")
			;

		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume for every market order", "Orders");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "Signal EMA length", "Indicators");

		_bearishExtremeLevel = Param(nameof(BearishExtremeLevel), -100m)
			.SetDisplay("Bearish Extreme", "Negative MACD level that arms shorts", "Signals");

		_bearishNeutralLevel = Param(nameof(BearishNeutralLevel), -30m)
			.SetDisplay("Bearish Neutral", "Negative MACD level that confirms the hook", "Signals");

		_bullishExtremeLevel = Param(nameof(BullishExtremeLevel), 100m)
			.SetDisplay("Bullish Extreme", "Positive MACD level that arms longs", "Signals");

		_bullishNeutralLevel = Param(nameof(BullishNeutralLevel), 30m)
			.SetDisplay("Bullish Neutral", "Positive MACD level that confirms the hook", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source series for the strategy", "General");
	}

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

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

		_macdPrev1 = null;
		_macdPrev2 = null;
		_macdPrev3 = null;

		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;

		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;

		_stopPrice = null;
		_takePrice = null;
	}

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

		Volume = OrderVolume;

		_macd = new MACD();
		_macd.ShortMa.Length = MacdFastPeriod;
		_macd.LongMa.Length = MacdSlowPeriod;

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

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

		// First handle protective exits using the finished candle range.
		HandlePositionExit(candle);

		if (!_macd.IsFormed)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		if (_macdPrev1 is null || _macdPrev2 is null || _macdPrev3 is null)
		{
			UpdateMacdHistory(macdLine);
			return;
		}

		var macd1 = _macdPrev1.Value;
		var macd2 = _macdPrev2.Value;
		var macd3 = _macdPrev3.Value;

		// --- Bearish sequence --------------------------------------------------

		if (macd1 < BearishExtremeLevel && !_bearishStageArmed)
		{
			// Arm the bearish setup after a deep negative MACD reading.
			_bearishStageArmed = true;
		}

		if (macd1 > BearishNeutralLevel && _bearishStageArmed)
		{
			// MACD returned toward zero, prepare for the hook confirmation.
			_bearishStageArmed = false;
			_bearishTriggerReady = true;
		}

		var bearishHook = _bearishTriggerReady &&
			macd1 < macd2 &&
			macd2 > macd3 &&
			macd1 < BearishNeutralLevel &&
			macd2 > BearishNeutralLevel;

		if (bearishHook)
		{
			// Confirm the bearish hook pattern.
			_bearishTriggerReady = false;
			_bearishSignalPending = true;
		}

		if (macd1 > 0)
		{
			// Positive MACD invalidates the bearish scenario.
			ResetBearishState();
		}

		if (_bearishSignalPending && Position <= 0)
		{
			// Execute the short entry with predefined stop-loss and take-profit.
			SellMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice + StopLossPips * pip;
			_takePrice = entryPrice - TakeProfitPips * pip;

			ResetBearishState();
		}

		// --- Bullish sequence --------------------------------------------------

		if (macd1 > BullishExtremeLevel && !_bullishStageArmed)
		{
			// Arm the bullish setup after a strong positive MACD expansion.
			_bullishStageArmed = true;
		}

		if (macd1 < 0)
		{
			// Negative MACD cancels the bullish scenario immediately.
			ResetBullishState();
		}
		else if (macd1 < BullishNeutralLevel && _bullishStageArmed)
		{
			// MACD retraced toward zero, allow the hook confirmation.
			_bullishStageArmed = false;
			_bullishTriggerReady = true;
		}

		var bullishHook = _bullishTriggerReady &&
			macd1 > macd2 &&
			macd2 < macd3 &&
			macd1 > BullishNeutralLevel &&
			macd2 < BullishNeutralLevel;

		if (bullishHook)
		{
			// Confirm the bullish hook pattern.
			_bullishTriggerReady = false;
			_bullishSignalPending = true;
		}

		if (_bullishSignalPending && Position >= 0)
		{
			// Execute the long entry with the configured targets.
			BuyMarket();

			var pip = GetPipSize();
			var entryPrice = candle.ClosePrice;
			_stopPrice = entryPrice - StopLossPips * pip;
			_takePrice = entryPrice + TakeProfitPips * pip;

			ResetBullishState();
		}

		UpdateMacdHistory(macdLine);
	}

	private void HandlePositionExit(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				// Long stop-loss hit inside the finished candle range.
				SellMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				// Long take-profit reached.
				SellMarket();
				ResetProtectionLevels();
			}
		}
		else if (Position < 0)
		{
			var exitVolume = Math.Abs(Position);

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				// Short stop-loss triggered within the candle.
				BuyMarket();
				ResetProtectionLevels();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				// Short take-profit reached.
				BuyMarket();
				ResetProtectionLevels();
			}
		}
	}

	private void UpdateMacdHistory(decimal macdValue)
	{
		_macdPrev3 = _macdPrev2;
		_macdPrev2 = _macdPrev1;
		_macdPrev1 = macdValue;
	}

	private void ResetBearishState()
	{
		_bearishStageArmed = false;
		_bearishTriggerReady = false;
		_bearishSignalPending = false;
	}

	private void ResetBullishState()
	{
		_bullishStageArmed = false;
		_bullishTriggerReady = false;
		_bullishSignalPending = false;
	}

	private void ResetProtectionLevels()
	{
		_stopPrice = null;
		_takePrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}
}