在 GitHub 上查看

Macd Secrets 策略

概述

Macd Secrets 策略 源自 MetaTrader 平台的 "Macd Secrets I" 智能交易系统,本移植版本使用 StockSharp 高级 API,并通过三重时间框架的 MACD 趋势确认、线性加权移动平均线 (LWMA) 滤波以及动量偏移过滤来确定入场信号。与原版 EA 可以叠加多个仓位不同,移植版始终只保持一个净仓位,以便更直观地管理风险。

信号逻辑

多头信号

  1. 在执行时间框架上,快速 LWMA 低于慢速 LWMA,表明价格位于趋势通道的下沿附近(原始 EA 采用相同过滤条件)。
  2. 执行、趋势以及月度三个时间框架上的 MACD 线均高于信号线,确保多时间框架方向一致。
  3. 趋势时间框架最近三根已完成 K 线上至少有一次动量读数与 100 的偏差达到设定阈值(默认 0.3),该偏差计算方式与原 EA 的 MathAbs(100 - Momentum) 完全一致。
  4. 当前没有持仓。

满足条件时,将以设定手数发送市价买单。

空头信号

  1. 执行、趋势以及月度时间框架上的 MACD 线均低于信号线。
  2. 趋势时间框架最近三根已完成 K 线的动量偏差至少有一次超过空头阈值。
  3. 当前没有持仓(移植版不进行对冲或加仓)。

满足条件时,将以设定手数发送市价卖单。

交易管理

  • 策略可以根据点值距离自动设置止损和止盈,距离会乘以交易品种的最小价格变动以转换为实际价格。
  • 原 EA 中的移动止损、保本、资金止损等逻辑未被移植;StockSharp 保护机制只在启动时设置一次。
  • 所有判断都基于已完成的 K 线,以避免盘中噪音。

多时间框架

  • 主时间框架:执行频率(默认 15 分钟),在此计算 MACD 以及两条 LWMA。
  • 趋势时间框架:用于确认的高一级时间框架(默认 1 小时),同时运行 MACD 与动量指标,并记录最近三次动量偏差。
  • 月度时间框架:长期趋势确认(默认 30 天,近似一个日历月)。

策略重写了 GetWorkingSecurities,以便在启动时向连接器一次性申请三个时间框架的数据。

参数

名称 描述 默认值
OrderVolume 下单手数(正数)。 0.1
TakeProfitPoints 止盈距离(点)。0 表示禁用。 50
StopLossPoints 止损距离(点)。0 表示禁用。 20
FastMaPeriod 主时间框架上快速 LWMA 的周期。 6
SlowMaPeriod 主时间框架上慢速 LWMA 的周期。 85
MacdFastPeriod MACD 快速 EMA 的周期。 12
MacdSlowPeriod MACD 慢速 EMA 的周期。 26
MacdSignalPeriod MACD 信号线的 EMA 周期。 9
MomentumPeriod 趋势时间框架上的动量回溯周期。 14
MomentumBuyThreshold 允许做多的最小动量偏差。 0.3
MomentumSellThreshold 允许做空的最小动量偏差。 0.3
PrimaryCandleType 执行时间框架,默认 15 分钟。 15m
TrendCandleType 确认时间框架,默认 1 小时。 1h
MonthlyCandleType 长期确认时间框架,默认 30 天。 30d

使用提示

  • LWMA 过滤具有非对称性:仅多头信号需要快速 LWMA 低于慢速 LWMA,此行为与原始 EA 保持一致。
  • 移植版采用固定手数,不包含原始代码中的 LotsOptimized 递增逻辑。如果需要加仓,可在成交回报中自行跟踪总仓位。
  • 请确保所连接的交易商或数据源能够提供所有三个所需的时间框架,否则指标无法形成,策略将保持等待状态。
  • 若交易标的无法提供 30 天 K 线,可通过参数传入自定义的 DataType 来替代。
  • 策略完全基于收盘数据,不直接读取指标历史缓存,符合 StockSharp 关于指标使用的最佳实践。

与原始 EA 的差异

  • 未移植移动止损、保本、资金保护等模块,替代方案是一次性设置固定的止损/止盈保护。
  • 未移植多单叠加和马丁格尔式加仓,所有下单量保持恒定。
  • 未实现提示音、邮件或推送等通知功能。

免责声明

量化交易存在较高风险,请务必在历史数据和模拟环境中充分测试后再投入真实资金。

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 MacdSecretsStrategy : 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 MacdSecretsStrategy()
	{
		_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;
	}
}