在 GitHub 上查看

Mc Valute Cloud 策略

本目录提供 MetaTrader 专家顾问 “Mc_valute” 的 StockSharp 版本。原始 EA 通过一条短周期 EMA、三条平滑移动平均线、 Ichimoku 云以及多组 MACD 共同判断趋势,并在行情加速时逐步加仓。移植到 StockSharp 后保留了主要的趋势确认 模块,同时把持仓管理简化为每个方向只保留一笔仓位,使策略能够充分利用高层 API。

交易逻辑

  1. EMA 趋势过滤器FilterMaLength 指定的 EMA 需要位于两条平滑均线(BlueMaLengthLimeMaLength)之上 才允许做多,位于其下才允许做空,这两条均线对应 MT4 模板中的蓝色和绿色线。
  2. Ichimoku 云过滤 – EMA 还必须突破 Ichimoku 云。做多时 EMA 要高于 Senkou Span A、B;做空时 EMA 要低于云层 底部。
  3. MACD 动量确认 – 只有在 MACD 主线高于信号线时才开多,主线低于信号线时才开空。原 EA 中保留的第一组 MACD 参数仍然生效,其余已在 MQL 源码中注释掉,因此移植版本也不再重复。
  4. 单一仓位管理 – 产生新信号时,策略会平掉反向仓位,并以 Volume 指定的手数重新开仓,同时立即更新 止盈和止损。
  5. 仅使用已完成的 K 线 – 所有指标都运行在 CandleType 指定的周期上。交易决策只在 K 线收盘后执行,与 MT4 start() 函数仅处理完结柱的行为一致。

风险控制

  • TakeProfitStopLoss 以价格点数表示。下单后会调用 SetTakeProfitSetStopLoss,并按预期仓位规模设置 防护水平,这与 MT4 中每笔订单分别设置止盈/止损的做法一致。
  • 原版 EA 会按 Step 距离逐级加仓至三单。StockSharp 版本保持单一仓位,以便使用高层下单接口。如需加仓, 可以调大 Volume 或并行运行多份策略实例。

参数

参数 说明
Volume 通过 BuyMarket/SellMarket 下单时使用的基础手数。
CandleType 计算指标与生成信号所使用的主时间框架。
FilterMaLength 趋势过滤 EMA 的周期。
BlueMaLength, LimeMaLength 两条平滑移动平均的周期。
MacdFastLength, MacdSlowLength, MacdSignalLength MACD 主线与信号线的 EMA 周期。
TenkanLength, KijunLength, SenkouLength Ichimoku 云的 Tenkan、Kijun、Senkou 参数。
TakeProfit, StopLoss 止盈止损的点数距离。

使用说明

  1. 移动平均的位移 – MT4 允许为平滑移动平均设置正负位移。StockSharp 的指标在当前柱上计算,因此忽略位移 参数,仅保留原有周期。
  2. MACD 组合 – 源码声明了三组 MACD,但只有第一组参与信号。移植版本沿用该行为;若需要更多过滤条件, 可以另外绑定新的 MACD 指标。
  3. 分批加仓 – 原 EA 会按照 Step 距离加至最多三笔仓位。该差异已在文档记录,但在高层策略中刻意省略, 因为系统只跟踪净头寸。
  4. 保护模块 – 启动时调用 StartProtection(),确保在断线重连后止盈止损仍由框架自动维护。

文件结构

  • CS/McValuteCloudStrategy.cs – 使用高层 Strategy API 编写的 C# 实现,并包含英文注释。
  • README.md – 英文文档。
  • README_zh.md – 中文文档(本文件)。
  • README_ru.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>
/// Trend-following strategy inspired by the "Mc_valute" MetaTrader expert advisor.
/// Combines smoothed moving averages, an Ichimoku cloud filter and a MACD confirmation.
/// </summary>
public class McValuteCloudStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _filterMaLength;
	private readonly StrategyParam<int> _blueMaLength;
	private readonly StrategyParam<int> _limeMaLength;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<int> _tenkanLength;
	private readonly StrategyParam<int> _kijunLength;
	private readonly StrategyParam<int> _senkouLength;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;

	private ExponentialMovingAverage _filterMa;
	private SmoothedMovingAverage _blueMa;
	private SmoothedMovingAverage _limeMa;
	private MovingAverageConvergenceDivergenceSignal _macd;
	private Ichimoku _ichimoku;

	private decimal? _filterValue;
	private decimal? _blueValue;
	private decimal? _limeValue;
	private decimal? _senkouAValue;
	private decimal? _senkouBValue;
	private decimal? _macdMainValue;
	private decimal? _macdSignalValue;

	private DateTimeOffset _lastProcessedTime;

	/// <summary>
	/// Initializes a new instance of the <see cref="McValuteCloudStrategy"/> class.
	/// </summary>
	public McValuteCloudStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe used for signals", "General");

		_filterMaLength = Param(nameof(FilterMaLength), 3)
		.SetDisplay("Filter EMA", "Length of the trend filter EMA", "Trend")
		
		.SetOptimize(2, 20, 1);

		_blueMaLength = Param(nameof(BlueMaLength), 13)
		.SetDisplay("Blue SMMA", "Length of the slower smoothed MA", "Trend")
		
		.SetOptimize(5, 60, 1);

		_limeMaLength = Param(nameof(LimeMaLength), 5)
		.SetDisplay("Lime SMMA", "Length of the faster smoothed MA", "Trend")
		
		.SetOptimize(3, 40, 1);

		_macdFastLength = Param(nameof(MacdFastLength), 12)
		.SetDisplay("MACD Fast", "Short EMA length for the MACD", "Momentum")
		
		.SetOptimize(5, 30, 1);

		_macdSlowLength = Param(nameof(MacdSlowLength), 26)
		.SetDisplay("MACD Slow", "Long EMA length for the MACD", "Momentum")
		
		.SetOptimize(10, 80, 1);

		_macdSignalLength = Param(nameof(MacdSignalLength), 9)
		.SetDisplay("MACD Signal", "Signal EMA length for the MACD", "Momentum")
		
		.SetOptimize(3, 30, 1);

		_tenkanLength = Param(nameof(TenkanLength), 12)
		.SetDisplay("Tenkan", "Tenkan-sen length for the Ichimoku cloud", "Ichimoku")
		
		.SetOptimize(5, 30, 1);

		_kijunLength = Param(nameof(KijunLength), 20)
		.SetDisplay("Kijun", "Kijun-sen length for the Ichimoku cloud", "Ichimoku")
		
		.SetOptimize(10, 60, 1);

		_senkouLength = Param(nameof(SenkouLength), 40)
		.SetDisplay("Senkou Span B", "Span B length for the Ichimoku cloud", "Ichimoku")
		
		.SetOptimize(20, 120, 1);

		_takeProfit = Param(nameof(TakeProfit), 30)
		.SetDisplay("Take Profit", "Take profit distance in points", "Risk")
		
		.SetOptimize(10, 200, 5);

		_stopLoss = Param(nameof(StopLoss), 350)
		.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk")
		
		.SetOptimize(50, 600, 10);

		_lastProcessedTime = DateTimeOffset.MinValue;
	}

	/// <summary>
	/// Timeframe used for the working candles.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the EMA filter.
	/// </summary>
	public int FilterMaLength
	{
		get => _filterMaLength.Value;
		set => _filterMaLength.Value = value;
	}

	/// <summary>
	/// Length of the slower smoothed moving average.
	/// </summary>
	public int BlueMaLength
	{
		get => _blueMaLength.Value;
		set => _blueMaLength.Value = value;
	}

	/// <summary>
	/// Length of the faster smoothed moving average.
	/// </summary>
	public int LimeMaLength
	{
		get => _limeMaLength.Value;
		set => _limeMaLength.Value = value;
	}

	/// <summary>
	/// Fast EMA length used by the MACD.
	/// </summary>
	public int MacdFastLength
	{
		get => _macdFastLength.Value;
		set => _macdFastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length used by the MACD.
	/// </summary>
	public int MacdSlowLength
	{
		get => _macdSlowLength.Value;
		set => _macdSlowLength.Value = value;
	}

	/// <summary>
	/// Signal EMA length used by the MACD.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Tenkan-sen length for the Ichimoku indicator.
	/// </summary>
	public int TenkanLength
	{
		get => _tenkanLength.Value;
		set => _tenkanLength.Value = value;
	}

	/// <summary>
	/// Kijun-sen length for the Ichimoku indicator.
	/// </summary>
	public int KijunLength
	{
		get => _kijunLength.Value;
		set => _kijunLength.Value = value;
	}

	/// <summary>
	/// Senkou Span B length for the Ichimoku indicator.
	/// </summary>
	public int SenkouLength
	{
		get => _senkouLength.Value;
		set => _senkouLength.Value = value;
	}

	/// <summary>
	/// Take profit distance in points.
	/// </summary>
	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss distance in points.
	/// </summary>
	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

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

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

		_filterMa = null;
		_blueMa = null;
		_limeMa = null;
		_macd = null;
		_ichimoku = null;
		_filterValue = null;
		_blueValue = null;
		_limeValue = null;
		_senkouAValue = null;
		_senkouBValue = null;
		_macdMainValue = null;
		_macdSignalValue = null;
		_lastProcessedTime = DateTimeOffset.MinValue;
	}

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

		_filterMa = new ExponentialMovingAverage { Length = FilterMaLength };
		_blueMa = new SmoothedMovingAverage { Length = BlueMaLength };
		_limeMa = new SmoothedMovingAverage { Length = LimeMaLength };

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastLength },
				LongMa = { Length = MacdSlowLength },
			},
			SignalMa = { Length = MacdSignalLength }
		};

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanLength },
			Kijun = { Length = KijunLength },
			SenkouB = { Length = SenkouLength }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_filterMa, _blueMa, _limeMa, ProcessMovingAverages)
		.BindEx(_macd, ProcessMacd)
		.BindEx(_ichimoku, ProcessIchimoku)
		.Start();

		StartProtection(null, null);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _filterMa);
			DrawIndicator(area, _blueMa);
			DrawIndicator(area, _limeMa);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessMovingAverages(ICandleMessage candle, decimal filter, decimal blue, decimal lime)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_filterValue = filter;
		_blueValue = blue;
		_limeValue = lime;

		TryTrade(candle);
	}

	private void ProcessMacd(ICandleMessage candle, IIndicatorValue macdValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var typed = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		if (typed.Macd is not decimal macd || typed.Signal is not decimal signal)
		return;

		_macdMainValue = macd;
		_macdSignalValue = signal;

		TryTrade(candle);
	}

	private void ProcessIchimoku(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var ichimokuValue = (IchimokuValue)value;
		if (ichimokuValue.SenkouA is not decimal spanA || ichimokuValue.SenkouB is not decimal spanB)
		return;

		_senkouAValue = spanA;
		_senkouBValue = spanB;

		TryTrade(candle);
	}

	private void TryTrade(ICandleMessage candle)
	{
		// indicators formed check removed

		if (candle.State != CandleStates.Finished)
		return;

		if (_lastProcessedTime == candle.OpenTime)
		return;

		if (_filterValue is not decimal filter ||
		_blueValue is not decimal blue ||
		_limeValue is not decimal lime ||
		_senkouAValue is not decimal spanA ||
		_senkouBValue is not decimal spanB ||
		_macdMainValue is not decimal macd ||
		_macdSignalValue is not decimal signal)
		{
			return;
		}

		_lastProcessedTime = candle.OpenTime;

		var cloudTop = Math.Max(spanA, spanB);
		var cloudBottom = Math.Min(spanA, spanB);
		var maUpper = Math.Max(blue, lime);
		var maLower = Math.Min(blue, lime);

		var allowLong = filter > maUpper && filter > cloudTop && macd > signal;
		var allowShort = filter < maLower && filter < cloudBottom && macd < signal;

		var closePrice = candle.ClosePrice;

		if (allowLong && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (allowShort && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}