Открыть на GitHub

Стратегия Universum 3.0 Original

Стратегия полностью повторяет советник Universum_3_0 из MQL4, используя высокоуровневый API StockSharp. Она объединяет простую логику порогов по индикатору DeMarker и адаптивное управление объёмом на основе мартингейла.

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

  • Индикатор: классический DeMarker с настраиваемым периодом.
  • Правила входа:
    • Длинная позиция открывается при DeMarker > 0.5 на закрытии завершённой свечи.
    • Короткая позиция открывается при DeMarker < 0.5 на закрытии завершённой свечи.
    • Одновременно может быть только одна позиция; сигналы игнорируются, пока сделка не закрыта.
  • Управление выходом:
    • К позиции автоматически привязываются тейк-профит и стоп-лосс в абсолютных пунктах.
    • Закрытие происходит по защитным уровням, стратегия не переворачивается мгновенно.
  • Управление капиталом:
    • После прибыльной сделки объём сбрасывается к базовому лоту.
    • После убыточной сделки объём умножается на (TakeProfitPoints + StopLossPoints) / (TakeProfitPoints - SpreadPoints).
    • Значение спрэда берётся из Level1 (лучшая покупка/продажа) и переводится в пункты с учётом точности инструмента.
    • Счётчик последовательных убытков ведётся и при достижении лимита стратегия останавливается, как в оригинале.
    • Параметр FastOptimize = true отключает адаптивное увеличение объёма, что ускоряет черновую оптимизацию.

Параметры

Параметр Описание Значение по умолчанию
CandleType Таймфрейм свечей для расчёта DeMarker. Свечи 1 минута
DemarkerPeriod Период индикатора DeMarker. 10
TakeProfitPoints Размер тейк-профита в пунктах (переводится в абсолютное значение). 50
StopLossPoints Размер стоп-лосса в пунктах. 50
BaseVolume Исходный объём после прибыльных сделок. 1
LossesLimit Максимальное число подряд идущих убытков до остановки стратегии. 1 000 000
FastOptimize При true отключает адаптивный объём для быстрой оптимизации. true

Особенности реализации

  • Для вычисления мультипликатора объёма необходимы данные Level1 со спредом.
  • Нормализация объёма учитывает минимальный объём инструмента, шаг и максимально допустимое значение.
  • Стопы автоматически подстраиваются под инструменты с 3/5 знаками за счёт корректировки пункта.
  • На график выводятся свечи, индикатор DeMarker и совершённые сделки для визуального контроля.

Практические советы

  1. Подайте вместе со свечами котировки Level1, чтобы корректно рассчитывался спред.
  2. Используйте FastOptimize = true на этапе грубой оптимизации, а затем отключайте для точных прогонов и реальной торговли.
  3. Следите за счётчиком убытков при агрессивных настройках, чтобы не выйти за лимиты брокера.
  4. Перед реальной торговлей адаптируйте TakeProfitPoints и StopLossPoints под конкретный инструмент и стиль управления риском.
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;
	}
}