在 GitHub 上查看

MACD Cleaner 策略

概览

MACD Cleaner 策略由 MetaTrader 5 的 "MACD Cleaner" 智能交易系统移植而来。策略在指定周期上仅分析收盘完成的 K 线,当 MACD 主线连续三个收盘柱单调上升或下降时触发交易信号。系统任何时刻只持有一个方向的仓位,当动量反转时立即反向。

交易逻辑

  • 每根完成的 K 线都会重新计算一次 MACD,使用可配置的快、慢、信号周期。
  • 如果最近三个 MACD 数值保持非递减(单调上升),策略准备做多:若存在空头仓位则先行平仓,再按设定手数开多。
  • 如果最近三个 MACD 数值保持非递增(单调下降),策略准备做空:若存在多头仓位则先行平仓,再开空仓。
  • 止损与止盈按照点数偏移,在每根蜡烛的最高价和最低价上进行检查。
  • 当启用移动止损时,只要价格顺势推进至少一个 TrailingStepPips 的距离,就会将止损向盈利方向移动 TrailingStopPips 点。
  • 所有离场均使用市价单,数量等于当前净仓位,以确保一次性平掉全部头寸。

参数

名称 默认值 说明
CandleType 1 小时 K 线 用于 MACD 计算和交易判定的时间框架。
TradeVolume 1 新开仓的基础手数。若需要反向,则会加上当前持仓量以先平旧仓。
StopLossPips 35 距离开仓价的止损点数,设为 0 表示禁用。
TakeProfitPips 30 距离开仓价的止盈点数,设为 0 表示禁用。
TrailingStopPips 0 移动止损基础距离,0 表示关闭移动止损。
TrailingStepPips 5 更新移动止损所需的最小顺势点数,关闭移动止损时忽略。
MacdFastPeriod 15 MACD 快速 EMA 周期。
MacdSlowPeriod 33 MACD 慢速 EMA 周期。
MacdSignalPeriod 11 MACD 信号线 EMA 周期。

仓位管理

  • 多头离场:当价格触及止损、止盈或移动止损时发送市价卖单平仓。
  • 空头离场:触发条件相同,使用市价买单平仓。
  • 仓位全部平掉后,所有移动止损相关的状态会被重置,以便下一笔交易从零开始跟踪。

备注

  • 点值会根据合约自动计算。如果品种价格有 3 或 5 位小数,点值会等于最小报价单位的 10 倍,与原始 MT5 版本一致。
  • 策略仅在收盘后做出决策,不会响应未完成的 K 线波动。
  • 将点数参数设为 0 可以关闭对应的风险控制。要启用移动止损,需要同时设置 TrailingStopPips 和正值的 TrailingStepPips
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 MacdCleanerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MacdCleanerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}