在 GitHub 上查看

通用信号演示

该策略将 MetaTrader 5 的 “Universal Signal” 专家顾问迁移到 StockSharp 的高级 API。它评估八个带权重的市场模式,将其合成为一个综合得分。当得分越过可配置的阈值时,策略会开仓或平仓多空头寸;如果需要,还会按设定距离提交限价挂单,并在若干根 K 线后自动取消。

策略参数

  • CandleType – 用于分析的 K 线类型。
  • SignalThresholdOpen – 开仓所需的最低综合得分。
  • SignalThresholdClose – 触发平仓的反向得分阈值。
  • PriceLevel – 挂单价差(0 表示按市价执行)。
  • StopLevel / TakeLevelStartProtection 使用的绝对止损与止盈距离。
  • SignalExpiration – 未成交挂单的保留条数。
  • Pattern0WeightPattern7Weight – 各个模式在总分中的权重。
  • UniversalWeight – 应用于所有模式总和的全局系数。
  • ShortMaPeriod, LongMaPeriod, RsiPeriod, BollingerPeriod, BollingerWidth, TrendSmaPeriod, VolumeSmaPeriod – 模式计算所依赖的指标参数。

交易逻辑

  1. 订阅所选 K 线并绑定 EMA、RSI、MACD Signal、布林带及辅助均线。
  2. 每根完成的 K 线都会计算八个布尔模式,例如趋势一致性、RSI 动能、MACD 柱体、布林带位置、K 线方向以及成交量放大等。
  3. 每个模式与其权重相乘后求和,再乘以 UniversalWeight 得到最终得分。
  4. 得分在反方向上越过平仓阈值时关闭已有头寸。
  5. 得分超过开仓阈值时建立新仓位;若 PriceLevel 大于 0,则按设定距离提交限价单,并在 SignalExpiration 根 K 线后取消未成交的挂单。
  6. 通过 StartProtection 为所有交易设置固定的止盈止损,沿用 StockSharp 的风险管理模块。

该移植版本保留了原始 MQL5 专家的灵活加权思想,同时遵循 StockSharp 的编码规范和指标驱动式流程。

namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Universal Signal Demo strategy: Multi-indicator scoring.
/// Combines RSI and EMA signals to generate aggregate score for entries.
/// </summary>
public class UniversalSignalDemoStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private int _prevScore;
	private int _candlesSinceTrade;
	private bool _hasPrevScore;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public UniversalSignalDemoStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevScore = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrevScore = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevScore = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrevScore = false;
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var close = candle.ClosePrice;
		var score = 0;

		// RSI signal
		if (rsiValue < 30) score += 2;
		else if (rsiValue < 45) score += 1;
		else if (rsiValue > 70) score -= 2;
		else if (rsiValue > 55) score -= 1;

		// EMA signal
		if (close > emaValue) score += 1;
		else if (close < emaValue) score -= 1;

		// Candle direction
		if (candle.ClosePrice > candle.OpenPrice) score += 1;
		else if (candle.ClosePrice < candle.OpenPrice) score -= 1;

		var crossedUp = (!_hasPrevScore || _prevScore < 2) && score >= 2;
		var crossedDown = (!_hasPrevScore || _prevScore > -2) && score <= -2;

		if (crossedUp && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (crossedDown && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}

		_prevScore = score;
		_hasPrevScore = true;
	}
}