在 GitHub 上查看

MACD 背离 RSI 策略

概览

  • 将 MetaTrader 专家顾问 “Macd diver rsi mt4” 移植到 StockSharp 高级 API。
  • 通过 RSI 过滤器与 MACD 背离识别配合来把握单品种的反转机会。
  • 任何时刻只允许持有一个方向的仓位;策略会在空仓状态下才触发新的信号。

信号逻辑

  1. 所选周期的每根收盘 K 线都会驱动四个绑定的指标:
    • 两个独立的 RelativeStrengthIndex(分别用于超卖与超买过滤),始终读取上一根柱子的数值。
    • 两个 MovingAverageConvergenceDivergence 指标,可配置快/慢 EMA 以及信号周期。
  2. 做多条件
    • 前一根柱子的 RSI 需要低于可调的超卖阈值。
    • 最新的 MACD 数值必须在阈值以下形成局部低点,该阈值会根据品种点值自动换算成 3 个点(pips)。
    • 向后遍历历史 MACD 和价格,寻找更早的 MACD 低点以及对应的价格摆动低点;当 MACD 低点抬高且价格创出更低低点(常规背离),或 MACD 低点降低且价格抬高(隐藏背离)时,即视为满足原 MQL 策略的背离条件。
    • 当背离成立且当前无持仓时,按照多头专用的手数与风控参数发送市价买入。
  3. 做空条件 与多头完全对称,使用超买 RSI 过滤并检测 MACD 高点的背离(比较历史高点与当前高点)。
  4. 入场后立即把配置的止损和止盈距离从 pips 转换为价格单位(遵循原始点值映射规则),并调用 SetStopLoss / SetTakeProfit 自动维护。

参数对应

  • LowerRsiPeriodLowerRsiThresholdinp1_Lo_RSIperiod / inp1_Ro_Value
  • BullishFastEmaBullishSlowEmaBullishSignalSmainp2_fastEMA / inp2_slowEMA / inp2_signalSMA
  • BullishVolumeBullishStopLossPipsBullishTakeProfitPipsinp3_VolumeSizeinp3_StopLossPipsinp3_TakeProfitPips
  • UpperRsiPeriodUpperRsiThresholdinp4_Lo_RSIperiod / inp4_Ro_Value
  • BearishFastEmaBearishSlowEmaBearishSignalSmainp5_fastEMA / inp5_slowEMA / inp5_signalSMA
  • BearishVolumeBearishStopLossPipsBearishTakeProfitPipsinp6_VolumeSizeinp6_StopLossPipsinp6_TakeProfitPips
  • CandleType – 计算所用的时间框架。

实现要点

  • MACD 背离阈值依据当前品种的最小报价步长换算为 3 个点,与 MQL 默认的 0.0003 完全一致。
  • 蜡烛、MACD 与价格序列使用最长 600 条的环形缓存,既复刻原版的搜索窗口,又避免过多内存占用。
  • 通过 SubscribeCandles(...).Bind(...) 在一次回调中更新所有指标,只处理状态为 Finished 的 K 线,等同于原策略的“每根新柱子运行一次”。
  • 止损/止盈距离在调用 SetStopLossSetTakeProfit 前都会先转换成实际价格偏移,确保遵守 MQL 代码顶部的点值格式规则。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// MACD Divergence RSI strategy: RSI filter + MACD signal line crossover.
/// Buys when RSI below threshold and MACD crosses above signal.
/// Sells when RSI above threshold and MACD crosses below signal.
/// </summary>
public class MacdDivergenceRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;

	private decimal _prevMacd;
	private decimal _prevSignal;
	private decimal _prevRsi;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }

	public MacdDivergenceRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMacd = 0;
		_prevSignal = 0;
		_prevRsi = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd = { ShortMa = { Length = 12 }, LongMa = { Length = 26 } },
			SignalMa = { Length = 9 }
		};
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, rsi, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue rsiValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!macdValue.IsFinal || !rsiValue.IsFinal) return;
		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue typed) return;
		if (typed.Macd is not decimal macdMain || typed.Signal is not decimal signal) return;

		var rsi = rsiValue.ToDecimal();

		if (_hasPrev)
		{
			if (_prevMacd <= _prevSignal && macdMain > signal && rsi < 40 && Position <= 0)
				BuyMarket();
			else if (_prevMacd >= _prevSignal && macdMain < signal && rsi > 60 && Position >= 0)
				SellMarket();
		}

		_prevMacd = macdMain;
		_prevSignal = signal;
		_prevRsi = rsi;
		_hasPrev = true;
	}
}