Auf GitHub ansehen

Universal Signal Demo

This strategy replicates the MetaTrader 5 "Universal Signal" expert using StockSharp high-level APIs. It evaluates eight weighted market patterns and aggregates them into a single composite score. When the score crosses configurable thresholds the strategy opens or closes long and short positions, optionally using pending limit orders that expire after a set number of bars.

Strategy Parameters

  • CandleType – candle data used for the analysis.
  • SignalThresholdOpen – minimum composite score required to open a position.
  • SignalThresholdClose – opposing score required to exit an existing position.
  • PriceLevel – price offset for placing pending limit entries (0 means market execution).
  • StopLevel / TakeLevel – absolute stop-loss and take-profit distances used by the built-in protection module.
  • SignalExpiration – number of bars after which still-active pending entries are cancelled.
  • Pattern0WeightPattern7Weight – weight applied to each pattern before aggregation.
  • UniversalWeight – final multiplier applied to the sum of all pattern contributions.
  • ShortMaPeriod, LongMaPeriod, RsiPeriod, BollingerPeriod, BollingerWidth, TrendSmaPeriod, VolumeSmaPeriod – indicator settings used inside the pattern checks.

Trading Logic

  1. Subscribe to the configured candle stream and bind EMA, RSI, MACD Signal, Bollinger Bands, and supporting SMAs.
  2. After every finished candle, compute eight boolean patterns (trend alignment, RSI momentum, MACD histogram, Bollinger positioning, candle direction, and volume expansion).
  3. Multiply each pattern by its weight, sum the contributions, and apply the global weight to obtain the final score.
  4. Close open positions when the score crosses the closing threshold in the opposite direction.
  5. Open new long or short positions when the score exceeds the opening threshold. If PriceLevel is positive, submit a limit order offset by the configured distance and cancel it automatically after SignalExpiration bars.
  6. StartProtection sets fixed stop-loss and take-profit levels for all positions using StockSharp's risk management helpers.

The conversion keeps the flexible weighting workflow of the original MQL5 expert while following StockSharp coding conventions and indicator-based processing.

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;
	}
}