Открыть на GitHub

Universal Signal Demo

Стратегия повторяет эксперта MetaTrader 5 «Universal Signal», используя высокоуровневые API StockSharp. Восемь взвешенных рыночных паттернов объединяются в единый интегральный балл. Когда балл превышает заданные пороги, стратегия открывает или закрывает длинные и короткие позиции, при необходимости размещая отложенные лимитные заявки с ограниченным временем жизни.

Параметры стратегии

  • CandleType – тип свечей, используемых в расчётах.
  • SignalThresholdOpen – минимальный интегральный балл для открытия позиции.
  • SignalThresholdClose – противоположный балл, необходимый для закрытия позиции.
  • PriceLevel – смещение цены для отложенных лимитных заявок (0 – исполнение по рынку).
  • StopLevel / TakeLevel – абсолютные расстояния стоп-лосса и тейк-профита, применяемые модулем защиты.
  • SignalExpiration – количество баров, после которых неисполненные заявки отменяются.
  • Pattern0WeightPattern7Weight – веса каждого паттерна перед суммированием.
  • UniversalWeight – глобальный множитель, применяемый к сумме всех вкладов.
  • ShortMaPeriod, LongMaPeriod, RsiPeriod, BollingerPeriod, BollingerWidth, TrendSmaPeriod, VolumeSmaPeriod – настройки индикаторов для проверок паттернов.

Логика торговли

  1. Подписка на поток выбранных свечей и привязка EMA, RSI, MACD Signal, полос Боллинджера и дополнительных SMA.
  2. После формирования каждой свечи вычисляются восемь булевых паттернов: направленность тренда, импульс RSI, гистограмма MACD, расположение относительно средней полосы, направление свечи и расширение объёма.
  3. Каждый паттерн умножается на свой вес, затем суммы масштабируются глобальным весом.
  4. Позиции закрываются, когда балл пересекает порог закрытия в противоположную сторону.
  5. Новые позиции открываются при превышении порога открытия. Если PriceLevel больше нуля, подаётся лимитная заявка со смещением и автоматической отменой через SignalExpiration баров.
  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;
	}
}