在 GitHub 上查看

MACD Sample Trend Filter 策略

该策略是 MetaTrader 5 经典 MACD Sample 专家顾问的移植版本。它结合 MACD 指标交叉与 EMA 趋势过滤器。交易手数由策略的 Volume 属性控制,风险管理依赖可配置的 MACD 阈值、止盈距离以及跟踪止损距离(以点数计)。

核心逻辑

  • 指标
    • MovingAverageConvergenceDivergenceSignal,周期 (12, 26, 9),用于生成 MACD 线与信号线。
    • ExponentialMovingAverage,周期 26,用于判断趋势方向。
  • 入场规则
    • 做多:MACD 低于零轴并向上穿越信号线,绝对值超过 MACD Open Level 阈值,同时 EMA 向上。
    • 做空:MACD 高于零轴并向下穿越信号线,数值超过 MACD Open Level 阈值,同时 EMA 向下。
  • 离场规则
    • MACD 以大于 MACD Close Level 的幅度反向穿越信号线。
    • 价格触达入场价的止盈距离。
    • 激活后的跟踪止损被触发。
  • 跟踪止损
    • 做多:当最高价超过入场价并达到跟踪距离后,激活跟踪止损并将其更新为 最高价 − 跟踪距离
    • 做空:当最低价跌破入场价并达到跟踪距离后,激活跟踪止损并将其更新为 最低价 + 跟踪距离

参数说明

参数 默认值 说明
FastPeriod 12 MACD 内部的快速 EMA 周期。
SlowPeriod 26 MACD 内部的慢速 EMA 周期。
SignalPeriod 9 MACD 信号线周期。
TrendPeriod 26 EMA 趋势过滤器周期。
MacdOpenLevelPips 3 允许开仓的 MACD 最小幅度(点数)。
MacdCloseLevelPips 2 允许平仓的 MACD 最小幅度(点数)。
TakeProfitPips 50 止盈距离(点数)。
TrailingStopPips 30 跟踪止损距离(点数),设置为 0 可禁用。
CandleType 15 分钟 K 线 指标计算所使用的蜡烛类型。

点值换算

原始 EA 会针对 3/5 位报价将点值放大 10 倍。移植版本通过 Security.PriceStep 进行同样的判断:

  • 若最小价位单位拥有 3 或 5 位小数,则点值 = PriceStep * 10
  • 其他情况下一点等于 PriceStep
  • 若无法获取价格步长,则参数值会被视为绝对价格差,需要手动调节。

行为说明

  • 平仓逻辑会先于新的入场信号执行,保持与 MT5 版本一致。
  • 通过 LogInfo 输出入场、平仓与跟踪止损调整,便于排查问题。
  • 策略内部自行管理止盈与跟踪止损,不会自动提交保护性订单。
  • 使用 Volume 设置基础交易手数,反向信号会自动对冲现有仓位。

与 MQL5 版本的差异

  • 该实现基于收盘 K 线进行处理,而非逐笔 tick,可减少重复信号并保持确定性。
  • 跟踪止损与止盈使用蜡烛的高低价近似原 EA 中的买卖报价触发。
  • Security.PriceStep 未提供,点数参数会被视为价格差,需结合品种特性重新校准。

在不同市场(例如期货、股票)中使用时,请根据实际最小变动价调整点数阈值与时间框架。

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD strategy with EMA trend filter.
/// Buys when MACD histogram is positive and price is above trend EMA.
/// Sells when MACD histogram is negative and price is below trend EMA.
/// </summary>
public class MacdSampleTrendFilterStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _trendPeriod;

	private int _prevSignal;

	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 TrendPeriod
	{
		get => _trendPeriod.Value;
		set => _trendPeriod.Value = value;
	}

	public MacdSampleTrendFilterStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA for MACD", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA for MACD", "Indicators");

		_trendPeriod = Param(nameof(TrendPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Trend Period", "Trend EMA period", "Indicators");
	}

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

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

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

		_prevSignal = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var trendEma = new ExponentialMovingAverage { Length = TrendPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, trendEma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawIndicator(area, trendEma);
			DrawOwnTrades(area);
		}
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var close = candle.ClosePrice;
		var macdLine = fast - slow;

		// Signal: MACD positive + price above trend = bullish; MACD negative + price below trend = bearish
		var signal = 0;
		if (macdLine > 0 && close > trend)
			signal = 1;
		else if (macdLine < 0 && close < trend)
			signal = -1;

		if (signal == _prevSignal)
			return;

		var oldSignal = _prevSignal;
		_prevSignal = signal;

		if (signal == 1 && oldSignal <= 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal == -1 && oldSignal >= 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}