View on GitHub

Universum 3.0 Original Strategy

This strategy reproduces the original Universum_3_0 MQL4 expert advisor using the StockSharp high-level API. It combines a simple DeMarker threshold entry model with a martingale-like position sizing rule that adapts lot size after losing trades.

Trading Logic

  • Indicator: classic DeMarker oscillator with configurable period.
  • Signal Generation:
    • Open a long position when DeMarker > 0.5 at the close of a finished candle.
    • Open a short position when DeMarker < 0.5 at the close of a finished candle.
    • Only one position can be active at a time; new signals are ignored while a trade is open.
  • Exit Management:
    • Protective stop-loss and take-profit levels are attached using absolute price offsets measured in points.
    • Positions are closed automatically by these protective levels; the strategy does not flip immediately.
  • Money Management:
    • After a profitable trade, volume resets to the base lot.
    • After a losing trade, volume is multiplied by (TakeProfitPoints + StopLossPoints) / (TakeProfitPoints - SpreadPoints).
    • The spread value is taken from live Level1 quotes and converted to "points" using symbol precision.
    • Consecutive losses are counted; reaching the limit stops the strategy to emulate the original loss protection.
    • Setting FastOptimize = true disables the adaptive sizing rule and always uses the base lot, which speeds up optimisations.

Parameters

Parameter Description Default
CandleType Time frame used for DeMarker calculations. 1-minute time frame
DemarkerPeriod Look-back period of the DeMarker oscillator. 10
TakeProfitPoints Take-profit distance expressed in points (converted to absolute price internally). 50
StopLossPoints Stop-loss distance expressed in points. 50
BaseVolume Initial trading volume used after each profitable trade. 1
LossesLimit Maximum number of consecutive losses before the strategy stops. 1,000,000
FastOptimize When true disables adaptive sizing for fast optimisation passes. true

Implementation Notes

  • Level1 data is required to estimate the current spread and replicate the original lot multiplier.
  • Volume normalisation honours the instrument's minimum volume, maximum volume and step size.
  • Stop-loss and take-profit offsets automatically adapt to 3/5 digit instruments by adjusting the point size.
  • The chart visualisation plots candles, the DeMarker indicator and executed trades for easier validation.

Usage Tips

  1. Provide Level1 bid/ask data in addition to candles to ensure the spread-based multiplier works correctly.
  2. Use FastOptimize = true during coarse parameter searches, then disable it for precise backtests and live trading.
  3. Monitor the consecutive loss counter when running with aggressive multipliers to avoid exceeding broker limits.
  4. Adjust TakeProfitPoints and StopLossPoints to match the original symbol or your risk profile before trading live.
using System;

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

namespace StockSharp.Samples.Strategies;

public class Universum30OriginalStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRsi;
	private bool _hasPrev;
	private int _cooldownRemaining;

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

	public Universum30OriginalStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI lookback", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA trend", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 30).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		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 rsi, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevRsi = rsi;
			return;
		}

		if (_prevRsi <= 30 && rsi > 30 && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevRsi >= 70 && rsi < 70 && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevRsi = rsi;
	}
}