在 GitHub 上查看

MACD信号线交叉策略

概述

本示例将 MetaTrader 4 专家顾问 MACD_v1.mq4 转换为 StockSharp 的高级策略。算法跟踪移动平均收敛散度(MACD)与其信号线的交叉情况,并按新趋势方向进行交易。同时保留原始脚本中的风险控制:止损、远距离止盈以及在达到首个目标时对仓位进行部分了结。

交易逻辑

  1. 数据来源:策略订阅所选的K线序列(默认使用5分钟K线),并绑定 MovingAverageConvergenceDivergenceSignal 指标。
  2. 入场条件
    • 当MACD线从下向上突破信号线时建立多头。如果存在空头仓位,会先平掉空头再开多。
    • 当MACD线从上向下跌破信号线时建立空头。如果存在多头仓位,会先平掉多头再开空。
  3. 离场条件
    • 反向交叉会关闭当前仓位并在新方向开仓。
    • StartProtection 根据设定的点数距离管理止盈与止损,与原始参数一致。
    • 价格距入场价达到指定点数后,策略会只在该仓位上执行一次部分平仓(默认平掉一半手数)。

参数

名称 默认值 说明
Fast Period 23 MACD 快速EMA长度,对应MQL参数 a = 2300
Slow Period 40 MACD 慢速EMA长度,对应MQL参数 b = 4000
Signal Period 8 MACD 信号线长度,对应MQL参数 c = 800
Take Profit 500 止盈距离(点)。设为 0 可禁用。
Stop Loss 80 止损距离(点)。设为 0 可禁用。
Partial Profit 70 达到该距离后平掉半仓。设为 0 可禁用部分平仓。
Candle Type 5分钟周期 用于计算指标的K线类型。

说明

  • 原脚本的MACD参数以百分之一表示(2300/4000/800),因此在移植时转换为23/40/8,更符合常见设定。
  • 每当仓位增加(加仓或反向开仓)时,部分平仓标志会自动重置,确保新的仓位仍然可以触发首个目标。
  • 如果界面支持图表,策略会绘制K线、MACD指标值以及自身成交点位,便于回放。
  • 策略使用基础 Volume 属性控制下单量,启动前请根据品种调整手数。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class MacdSignalCrossoverStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private bool _prevMacdAboveSignal;
	private bool _hasPrev;

	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 DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MacdSignalCrossoverStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 23);
		_slowPeriod = Param(nameof(SlowPeriod), 40);
		_signalPeriod = Param(nameof(SignalPeriod), 8);
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame());
	}

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

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

		_prevMacdAboveSignal = false;
		_hasPrev = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		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;

		var macdTyped = macdValue as MovingAverageConvergenceDivergenceSignalValue;
		if (macdTyped == null)
			return;

		var macdLine = macdTyped.Macd;
		var signalLine = macdTyped.Signal;

		if (macdLine == null || signalLine == null)
			return;

		var isMacdAboveSignal = macdLine.Value > signalLine.Value;

		if (!_hasPrev)
		{
			_hasPrev = true;
			_prevMacdAboveSignal = isMacdAboveSignal;
			return;
		}

		var crossedAbove = isMacdAboveSignal && !_prevMacdAboveSignal;
		var crossedBelow = !isMacdAboveSignal && _prevMacdAboveSignal;

		if (crossedAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (crossedBelow)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevMacdAboveSignal = isMacdAboveSignal;
	}
}