在 GitHub 上查看

MACD AO Pattern 策略

概述

该策略是 FORTRADER MACD.mq5 智能交易程序的 StockSharp 版本。它实现了所谓的“AOP”形态:当 MACD 指标远离零轴后回抽形成钩形时,系统跟随预期的反转方向开仓,并立即设置以点数表示的固定止损和止盈。

策略逻辑

数据准备

  • 使用 CandleType 参数指定的 K 线序列(默认 5 分钟)。
  • 采用可配置快线、慢线和信号线周期的标准 MACD 指标(默认 12/26/9)。
  • 保存最近三个已完成 K 线的 MACD 主线值,以复现 MQL 中 iMACD(...,1..3) 的索引访问。

做空(看跌钩形)

  1. 预备阶段:当最近一根完成的 K 线 MACD 主线低于 BearishExtremeLevel(默认 −0.0015)时,启动做空观察模式。
  2. 回抽到中性:MACD 升回 BearishNeutralLevel(默认 −0.0005)以上,进入钩形确认阶段。
  3. 钩形确认:最近三个 MACD 值需形成局部高点(macd₁ < macd₂ > macd₃),同时最新值仍低于中性阈值而再前一根仍高于阈值,确保动能衰减。
  4. 入场:若当前没有多头仓位(Position <= 0),按 OrderVolume 成交量市价卖出。随后根据 StopLossPipsTakeProfitPips(通过 GetPipSize 换算成价格)计算止损与止盈。
  5. 若 MACD 转为正值,立即取消做空状态机,等待下一次深度负值伸展。

做多(看涨钩形)

  1. 预备阶段:当 MACD 突破 BullishExtremeLevel(默认 +0.0015)时,激活做多观察。
  2. 立即取消:若 MACD 跌破零轴,则放弃该多头机会,与原始 EA 保持一致。
  3. 回抽到中性:MACD 回落到 BullishNeutralLevel(默认 +0.0005)以下,进入钩形确认。
  4. 钩形确认:最近三个 MACD 值需形成局部低点(macd₁ > macd₂ < macd₃),并满足中性阈值条件。
  5. 入场:若当前没有空头敞口(Position >= 0),按 OrderVolume 市价买入,并设置对称的止损/止盈。

风险管理

  • _stopPrice_takePrice 始终跟踪保护价位,每根完成 K 线都会利用最高价/最低价检查是否触发,模拟原 EA 在服务器端执行的效果。
  • 点值通过 Security.PriceStep 转换为绝对价格。对于 3 位或 5 位报价的外汇品种,乘以 10 以匹配 MQL 对“Fractional pip”的处理。
  • 当仓位因保护价位出场后立即清除这些价位,并等待下一轮状态机流程。

参数

参数 说明 默认值
CandleType 策略使用的 K 线数据类型。 5 分钟
OrderVolume 每次市价单的下单量。 0.1
TakeProfitPips 止盈距离(点)。已启用优化。 60
StopLossPips 止损距离(点)。已启用优化。 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

其他说明

  • 策略仅在每根完成的 K 线上执行一次计算,对应原始 MQL 中的 PrevBars 检查。
  • 仅采用固定止损和止盈,不包含追踪机制或重复进场,直到状态机重新激活。
  • 原 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;
	}
}