在 GitHub 上查看

MAMACD 无波动策略

概述

MAMACD 无波动策略是 MetaTrader 4 专家顾问 MAMACD_novlt.mq4 的直接移植。策略把三条分别基于最低价和收盘价的均线与 MACD 动量过滤器结合起来:当快速 EMA 跌破(做多)或升破(做空)两条基于最低价的 LWMA 时,先进入准备状态,随后只有在 MACD 主线确认动量方向后才执行进场。

指标

  • 快速 EMA (FastEmaPeriod),基于收盘价。
  • LWMA 1 (FirstLowWmaPeriod),基于最低价。
  • LWMA 2 (SecondLowWmaPeriod),基于最低价。
  • MACD 主线,快速周期为 FastSignalEmaPeriod,慢速周期为 SlowEmaPeriod

所有指标都使用 CandleType 指定的时间框架(默认:5 分钟 K 线)。

参数

参数 说明 默认值
FirstLowWmaPeriod 第一条基于最低价的 LWMA 周期。 85
SecondLowWmaPeriod 第二条基于最低价的 LWMA 周期。 75
FastEmaPeriod 基于收盘价的快速 EMA 周期。 5
SlowEmaPeriod MACD 计算的慢速周期。 26
FastSignalEmaPeriod MACD 计算的快速周期。 15
StopLossPoints 止损距离(价格步长,0 表示不启用)。 15
TakeProfitPoints 止盈距离(价格步长,0 表示不启用)。 15
TradeVolume 每次进场的下单手数。 0.1
CandleType 所有指标使用的 K 线序列。 5 分钟

交易规则

  1. 准备做多:快速 EMA 位于两条 LWMA 之下。
  2. 准备做空:快速 EMA 位于两条 LWMA 之上。
  3. 开多仓
    • 快速 EMA 再次站上两条 LWMA;
    • 之前已经完成多头准备;
    • MACD 主线为正或高于上一值;
    • 当前净仓位不是多头。
  4. 开空仓
    • 快速 EMA 再次跌破两条 LWMA;
    • 之前已经完成空头准备;
    • MACD 主线为负或低于上一值;
    • 当前净仓位不是空头。
  5. 风险控制:通过策略的保护模块自动应用可选的止损与止盈。当参数为 0 时,对应的保护不会启动。

策略不包含额外的离场条件,仓位的退出依赖设定的止损/止盈或手动操作。

说明

  • MACD 的确认逻辑与原始 MQL 版本一致:做多时主线必须高于 0 或者比上一笔更高;做空时主线必须低于 0 或者比上一笔更低。
  • 两条 LWMA 使用最低价计算,与原始顾问保持一致。
  • TradeVolume 参数直接决定每次市价单的交易数量,从而复刻 MQL 版本的下单方式。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that replicates the MAMACD_novlt MetaTrader expert advisor.
/// It prepares long or short setups when a fast EMA is below or above two LWMA values
/// built from candle lows, and then confirms entries with the MACD main line momentum.
/// </summary>
public class MamacdNovltStrategy : Strategy
{
	private readonly StrategyParam<int> _firstLowWmaPeriod;
	private readonly StrategyParam<int> _secondLowWmaPeriod;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _fastSignalEmaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private bool _isLongSetupPrepared;
	private bool _isShortSetupPrepared;
	private decimal? _previousMacd;

	/// <summary>
	/// Period of the first LWMA calculated on low prices.
	/// </summary>
	public int FirstLowWmaPeriod
	{
		get => _firstLowWmaPeriod.Value;
		set => _firstLowWmaPeriod.Value = value;
	}

	/// <summary>
	/// Period of the second LWMA calculated on low prices.
	/// </summary>
	public int SecondLowWmaPeriod
	{
		get => _secondLowWmaPeriod.Value;
		set => _secondLowWmaPeriod.Value = value;
	}

	/// <summary>
	/// Period of the fast EMA calculated on close prices.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow period of the MACD indicator.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	/// <summary>
	/// Fast period of the MACD indicator.
	/// </summary>
	public int FastSignalEmaPeriod
	{
		get => _fastSignalEmaPeriod.Value;
		set => _fastSignalEmaPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in absolute price units.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in absolute price units.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters that mirror the original MetaTrader inputs.
	/// </summary>
	public MamacdNovltStrategy()
	{
		_firstLowWmaPeriod = Param(nameof(FirstLowWmaPeriod), 85)
			.SetGreaterThanZero()
			.SetDisplay("First LWMA Period", "First LWMA period on lows", "Indicators");

		_secondLowWmaPeriod = Param(nameof(SecondLowWmaPeriod), 75)
			.SetGreaterThanZero()
			.SetDisplay("Second LWMA Period", "Second LWMA period on lows", "Indicators");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Period", "Fast EMA period on closes", "Indicators");

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators");

		_fastSignalEmaPeriod = Param(nameof(FastSignalEmaPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators");

		_stopLossPoints = Param(nameof(StopLossPoints), 500)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance", "Risk");

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

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

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

		_isLongSetupPrepared = false;
		_isShortSetupPrepared = false;
		_previousMacd = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var fastCloseEma = new EMA { Length = FastEmaPeriod };
		var firstLowWma = new WeightedMovingAverage { Length = FirstLowWmaPeriod };
		var secondLowWma = new WeightedMovingAverage { Length = SecondLowWmaPeriod };

		var macd = new MovingAverageConvergenceDivergence();
		macd.ShortMa.Length = FastSignalEmaPeriod;
		macd.LongMa.Length = SlowEmaPeriod;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastCloseEma, firstLowWma, secondLowWma, macd, ProcessCandle)
			.Start();

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

		var takeProfitUnit = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var stopLossUnit = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		StartProtection(takeProfitUnit, stopLossUnit);

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal firstLwma, decimal secondLwma, decimal macdLine)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Track when the fast EMA moves below both LWMA values to arm the long setup.
		if (ema < firstLwma && ema < secondLwma)
		{
			_isLongSetupPrepared = true;
		}

		// Track when the fast EMA moves above both LWMA values to arm the short setup.
		if (ema > firstLwma && ema > secondLwma)
		{
			_isShortSetupPrepared = true;
		}

		var hasPreviousMacd = _previousMacd.HasValue;
		var macdPrev = _previousMacd ?? macdLine;

		var macdBullish = macdLine > 0m || (hasPreviousMacd && macdLine > macdPrev);
		var macdBearish = macdLine < 0m || (hasPreviousMacd && macdLine < macdPrev);

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousMacd = macdLine;
			return;
		}

		// Enter long when EMA crosses above both LWMAs after being below, with bullish MACD
		if (ema > firstLwma && ema > secondLwma && _isLongSetupPrepared && macdBullish && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_isLongSetupPrepared = false;
		}

		// Enter short when EMA crosses below both LWMAs after being above, with bearish MACD
		if (ema < firstLwma && ema < secondLwma && _isShortSetupPrepared && macdBearish && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
			_isShortSetupPrepared = false;
		}

		_previousMacd = macdLine;
	}
}