在 GitHub 上查看

日内开盘 MACD 直方图策略

概览

本策略还原 MetaTrader 专家顾问“2 1000 1 0.7% 0.5 500lev st”的逻辑:在每个新交易日的开始入场,并通过 MACD 直方图的斜率决定方向。系统以小时级别的 K 线为基础,并保留了原始 MQL 配置中的固定仓位管理参数。

交易逻辑

  • 策略监听小时 K 线,识别每天的第一根 K 线。
  • 使用上一交易日中最近两个收盘 K 线的 MACD 直方图值进行判断。
  • 若直方图在这两个柱之间下降,则在新交易日的第一根 K 线开多仓。
  • 若直方图上升,则改为开空仓。
  • 同一时间只允许持有一个方向的仓位,如出现反向信号会先平仓后再开新仓。

风险控制

  • 初始止损距离:875 点(按交易品种的最小跳动价乘以该数值得到价格距离)。
  • 止盈距离:510 点。
  • 跟踪止损距离:2172 点。跟踪止损会记录自入场以来的最高价(多头)或最低价(空头),并在移动到更紧的位置时覆盖初始止损。
  • 原策略的保本移动功能关闭,因此此处也未启用。

参数

名称 说明 默认值
CandleType 策略使用的 K 线序列(默认 1 小时)。 1 小时 K 线
MacdFastPeriod MACD 快速均线周期。 58
MacdSlowPeriod MACD 慢速均线周期。 195
MacdSignalPeriod MACD 信号线周期。 183
StopLossPoints 以点数表示的止损距离。 875
TakeProfitPoints 以点数表示的止盈距离。 510
TrailingStopPoints 以点数表示的跟踪止损距离。 2172

备注

  • 策略仅使用已收完的 K 线,模仿原专家中的“使用上一根柱”选项,避免了时间穿越问题。
  • 跟踪止损和固定止盈止损均在策略内部处理,建议不要额外启用组合层面的保护,以免出现重复的止损管理。
  • 参数基于标准的点值定义,如果交易品种的最小跳动价不同,请相应调整参数。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Day Opening MACD Histogram strategy: MACD histogram direction.
/// Buys when MACD histogram turns positive, sells when turns negative.
/// </summary>
public class DayOpeningMacdHistogramStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevHistogram;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public DayOpeningMacdHistogramStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "MACD fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "MACD slow EMA period", "Indicators");
		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Signal Period", "MACD signal period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevHistogram = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevHistogram = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd = { ShortMa = { Length = FastPeriod }, LongMa = { Length = SlowPeriod } },
			SignalMa = { Length = SignalPeriod }
		};
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(macd, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!macdValue.IsFinal) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue typed) return;
		if (typed.Macd is not decimal macdMain || typed.Signal is not decimal signal) return;

		var histogram = macdMain - signal;

		if (_hasPrev)
		{
			if (_prevHistogram <= 0 && histogram > 0 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevHistogram >= 0 && histogram < 0 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevHistogram = histogram;
		_hasPrev = true;
	}
}