在 GitHub 上查看

Simple MACD

Simple MACD 策略将 MQL5 顾问 Simple_MACD.mq5 迁移到 StockSharp。算法在每根完成的 K 线后评估 MACD 主线的斜率,只要斜率保持不变就持续在该方向上加仓。

概览

  • 适用市场:所有提供稳定 K 线数据的市场(外汇、股票、期货等)。
  • 核心指标:MACD,使用 12/26 的 EMA 以及 9 周期的信号线。
  • 交易思想:比较最近两个完成柱的 MACD 主线值。主线抬升视为动量增强,主线下降视为动量减弱。
  • 下单方式:全部为市价单。每次信号都会在反向仓位的平仓量基础上再增加 TradeVolume,完全复刻原始顾问的行为。

转换说明

  • MQL5 版本在新柱开始时比较 MACD(1)MACD(2)。StockSharp 版本在柱子收盘后立即执行同样的比较,因此信号会在下一根柱开启前形成。
  • 原代码通过遍历仓位并检查保证金。现在改为使用策略参数和 BuyMarket/SellMarket 方法,由平台自动处理仓位与资金。
  • StockSharp 使用净仓位,因此不再需要区分多头和空头持仓列表,也无需检测对冲模式。

交易规则

入场与加仓

  1. 每根完成的蜡烛计算一次 MACD 主线值。
  2. 保存最近两个主线读数。
  3. MACD(1) > MACD(2)
    • 说明斜率向上。下达买单,数量为 TradeVolume + max(0, -Position),即先平掉现有空头,再追加新的多头。
  4. MACD(1) < MACD(2)
    • 说明斜率向下。下达卖单,数量为 TradeVolume + max(0, Position),先平掉现有多头,再追加新的空头。
  5. 若两个值相等,则跳过本次信号。

仓位管理

  • 只要 MACD 斜率保持同一方向,每根满足条件的柱子都会再发送一次同向订单。
  • 方向发生变化时,会先平仓再建立反向头寸,避免持仓方向相冲突。
  • 策略没有内置止损/止盈,风险管理需通过资金控制或额外模块实现。

保护机制

  • 在 MACD 指标形成之前不会进行交易。
  • 仅处理状态为 CandleStates.Finished 的蜡烛,避免在未完成数据上决策。
  • 每次交易都会写入日志,记录触发信号的两个 MACD 值,方便回测分析。

参数

参数 默认值 说明
FastPeriod 12 MACD 快速 EMA 周期。
SlowPeriod 26 MACD 慢速 EMA 周期。
SignalPeriod 9 信号线周期,保留以兼容原策略设置。
TradeVolume 0.1 每次信号额外加仓的数量。
CandleType 1 分钟 指标使用的蜡烛类型,可按需要调整。

所有参数都通过 Param() 暴露,可用于优化。

可视化

  • 策略在有图表的环境中会自动创建价格区域,并绘制 MACD 指标与策略成交点。
  • 通过图表可以清晰看到趋势阶段内多次加仓的节奏。

使用建议

  • 更适合有明显趋势的市场环境;在震荡行情中可能频繁换向,导致交易成本上升。
  • 建议结合资金管理或风险控制模块使用,以弥补缺乏硬性止损的不足。
  • 可针对目标品种与周期优化 MACD 周期和 TradeVolume 参数。

文件结构

  • CS/SimpleMacdStrategy.cs —— 策略的 C# 实现。
  • README.md, README_ru.md, README_zh.md —— 三种语言的详细文档。
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>
/// Simple MACD slope-following strategy converted from MQL5 Simple_MACD.mq5.
/// The strategy evaluates the MACD main line on completed candles and builds positions accordingly.
/// </summary>
public class SimpleMacdStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergence _macd = null!;
	private decimal? _previousMacdValue;
	private decimal? _prePreviousMacdValue;
	private int? _previousSlope;

	/// <summary>
	/// Fast EMA period used for the MACD main line.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period used for the MACD main line.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Signal EMA period used by the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Trading volume applied when new orders are sent.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type used to feed the MACD indicator.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize Simple MACD strategy with default parameters.
	/// </summary>
	public SimpleMacdStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("MACD Fast Period", "Fast EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(6, 18, 2);

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("MACD Slow Period", "Slow EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(20, 40, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetDisplay("MACD Signal Period", "Signal EMA length maintained for compatibility", "Indicators")
			
			.SetOptimize(6, 18, 1);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetDisplay("Trade Volume", "Order volume used for each signal", "Risk")
			
			.SetOptimize(0.1m, 1m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
	}

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

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

		_previousMacdValue = null;
		_prePreviousMacdValue = null;
		_previousSlope = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Configure MACD indicator to match the source MQL strategy settings.
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		};

		// Subscribe to candle data and bind the MACD indicator.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_macd, ProcessCandle)
			.Start();

		// Prepare visual elements when charts are available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		// React only to completed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the indicator produced a valid value.
		if (!_macd.IsFormed || !macdValue.IsFinal)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var macdLine = macdValue.ToDecimal();

		// Accumulate historical MACD values for slope calculations.
		if (_previousMacdValue is null)
		{
			_previousMacdValue = macdLine;
			return;
		}

		if (_prePreviousMacdValue is null)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		var macdPrev = _previousMacdValue.Value;
		var macdPrevPrev = _prePreviousMacdValue.Value;

		var currentSlope = macdPrev > macdPrevPrev ? 1 : macdPrev < macdPrevPrev ? -1 : 0;

		if (currentSlope == 0)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (_previousSlope == currentSlope)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (currentSlope > 0)
		{
			// Close shorts and open (or add to) longs when the MACD slope turns positive.
			var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
			if (volumeToBuy > 0m)
			{
				BuyMarket(volumeToBuy);
				LogInfo($"Bullish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Buying {volumeToBuy}.");
			}
		}
		else
		{
			// Close longs and open (or add to) shorts when the MACD slope turns negative.
			var volumeToSell = TradeVolume + Math.Max(0m, Position);
			if (volumeToSell > 0m)
			{
				SellMarket(volumeToSell);
				LogInfo($"Bearish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Selling {volumeToSell}.");
			}
		}

		// Update stored values so the next candle compares the two previous MACD readings.
		_previousSlope = currentSlope;
		_prePreviousMacdValue = _previousMacdValue;
		_previousMacdValue = macdLine;
	}
}