Открыть на GitHub

Стратегия Omzdwwi Pending Manager

Общее описание

Omzdwwi Pending Manager — это портированная на StockSharp версия советника MetaTrader 4 omzdwwi7739cyjayvs_1_65.mq4. Алгоритм поддерживает «кольцо» отложенных заявок вокруг текущей цены, по расписанию выставляет рыночные сделки и сопровождает позиции трейлинг-стопами. В C#-реализации используются только высокоуровневые методы Strategy: подписка на Level1-данные, BuyStop/SellLimit, ReRegisterOrder и стандартные логи.

Основные функции:

  • Поддерживает до четырёх отложенных заявок (buy stop, sell stop, buy limit, sell limit) на заданном расстоянии от Bid/Ask.
  • По заданному времени (SignalHour:SignalMinute) может открыть рыночную покупку и/или продажу, если это разрешено параметрами WaitClose и MaxMarketOrders.
  • Для активных позиций рассчитывает классические тейк-профит и стоп-лосс, дополнительную цель ExitProfitPoints, а также трейлинг-стоп, повторяющий MQL-функцию TrailingPositions().
  • Для отложенных заявок реализует «подтягивание» к рынку в духе TrailingOtlozh(): как только цена проходит дистанцию offset + step, заявка переставляется ближе.
  • Контролирует глобальные пороги прибыли/убытка по счёту и пишет информационные/предупреждающие сообщения, аналогичные Alert() в терминале MT4.

Источники данных

  • Используется подписка SubscribeLevel1(). Каждое обновление котировки запускает проверки времени, установку/перестановку заявок и логику выхода. Свечи и индикаторы не применяются.
  • GetWorkingSecurities() возвращает пару (Security, DataType.Level1), что обеспечивает корректную работу как в реале, так и в тестере.

Логика входа

  1. Рыночные заявки по расписанию. При наступлении указанного часа и минуты включаются внутренние флаги (TimeBuySignal, TimeSellSignal и т.д.). Следующее обновление Level1 проверяет ограничения и вызывает BuyMarket() либо SellMarket(). После успешного вызова флаги сбрасываются.
  2. Отложенные заявки. Для каждой включённой галочкой заявки стратегия проверяет, существует ли активный ордер. Если ордера нет — выставляет его на расстоянии StepPoints * PriceStep. При наличии ордера контролирует цену и при необходимости переставляет его через ReRegisterOrder.

Логика выхода для рыночных позиций

  • Фиксированные стоп/профит настраиваются параметрами MarketStopLossPoints и MarketTakeProfitPoints. Пересечение Bid/Ask с этими уровнями закрывает позицию рыночной сделкой.
  • Дополнительная цель ExitProfitPoints воспроизводит опцию PipsProfit в оригинале и закрывает позицию при достижении заданного профита даже без тейк-профита.
  • Трейлинг-стоп повторяет схему из MQL: если прибыль достаточна (или RequireProfitBeforeTrailing=false), внутренняя цена стопа переносится на Bid - MarketTrailingOffsetPoints * PriceStep для лонга и Ask + MarketTrailingOffsetPoints * PriceStep для шорта, с минимальным шагом MarketTrailingStepPoints.

Трейлинг отложенных ордеров

  • Для stop-заявок используется пара StopTrailingOffsetPoints/StopTrailingStepPoints. Как только Ask или Bid проходит соответствующее расстояние, заявка переносится ближе к рынку.
  • Для limit-заявок аналогично применяются LimitTrailingOffsetPoints и LimitTrailingStepPoints.

Контроль рисков

  • MaxMarketOrders ограничивает количество одновременно открытых лотов (кратно OrderVolume) при отключённом WaitClose.
  • UseGlobalLevels, GlobalTakeProfitPercent, GlobalStopLossPercent отслеживают изменение текущей стоимости портфеля и выводят сообщения о достижении порога прибыли/просадки.

Параметры

Группа Параметр Назначение
Общие OrderVolume Объём сделки (лот), применяемый для всех ордеров.
Исполнение WaitClose Запрет на новые входы до полного закрытия позиции.
Исполнение MaxMarketOrders Максимум одновременно открытых лотов в одном направлении.
Отложенные EnableBuyStop / EnableSellStop / EnableBuyLimit / EnableSellLimit Включение конкретных типов заявок.
Отложенные StopStepPoints, LimitStepPoints Дистанция в пунктах до цены размещения stop/limit ордеров.
Отложенные StopTakeProfitPoints, StopStopLossPoints, LimitTakeProfitPoints, LimitStopLossPoints Защитные уровни после срабатывания отложенного ордера.
Отложенные StopTrailingOffsetPoints, StopTrailingStepPoints, LimitTrailingOffsetPoints, LimitTrailingStepPoints Параметры «подтягивания» отложенных ордеров.
Риск MarketTakeProfitPoints, MarketStopLossPoints Тейк-профит и стоп-лосс для рыночных позиций.
Риск MarketTrailingOffsetPoints, MarketTrailingStepPoints, RequireProfitBeforeTrailing Настройки трейлинг-стопа для позиций.
Риск ExitProfitPoints Дополнительная цель по прибыли.
Время UseTimeSignals, SignalHour, SignalMinute Расписание запусков.
Время TimeBuySignal, TimeSellSignal, TimeBuyStopSignal, TimeSellStopSignal, TimeBuyLimitSignal, TimeSellLimitSignal Какие ордера активируются по таймеру.
Мониторинг UseGlobalLevels, GlobalTakeProfitPercent, GlobalStopLossPercent Пороговые значения для контроля счёта.
Прочее SlippagePoints Резервный параметр для совместимости с оригиналом.

Особенности переноса

  • В MT4 тейк/стоп задавались прямо у отложенного ордера. В StockSharp они реализованы через стратегию, которая отслеживает достижение уровней и закрывает позицию рыночной сделкой.
  • Звуковые оповещения заменены на записи в журнал (AddInfoLog, AddWarningLog).
  • Значение MODE_STOPLEVEL брокера не доступно из StockSharp, поэтому пользователь должен сам проверять, что выбранные дистанции разрешены площадкой.

Как использовать

  1. Выберите Security и Portfolio, где задан корректный шаг цены.
  2. Настройте дистанции в пунктах — стратегия автоматически преобразует их в абсолютные цены через Security.ShrinkPrice.
  3. Запустите стратегию. Она подпишется на поток котировок и начнёт сопровождать ордера в режиме, аналогичном оригинальному советнику.

Совет: при тестировании убедитесь, что подаются данные Level1. Именно они приводят в действие таймер и трейлинг, как это происходило в MetaTrader 4.

using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Omzdwwi Pending Manager strategy - RSI reversal with SMA filter.
/// Buys when RSI crosses below oversold and close is above SMA.
/// Sells when RSI crosses above overbought and close is below SMA.
/// </summary>
public class OmzdwwiPendingManagerStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRsi;
	private bool _hasPrev;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public OmzdwwiPendingManagerStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI lookback", "Indicators");
		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetDisplay("SMA Period", "SMA lookback", "Indicators");
		_oversold = Param(nameof(Oversold), 40m)
			.SetDisplay("Oversold", "RSI oversold level", "Indicators");
		_overbought = Param(nameof(Overbought), 60m)
			.SetDisplay("Overbought", "RSI overbought level", "Indicators");
		_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;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

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

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var sma = new SimpleMovingAverage { Length = SmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, sma, ProcessCandle).Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal sma)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;

		if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }

		var oversold = Oversold;
		var overbought = Overbought;

		if (_prevRsi < oversold && rsi >= oversold && close > sma && Position == 0)
		{
			BuyMarket();
		}
		else if (_prevRsi > overbought && rsi <= overbought && close < sma && Position == 0)
		{
			SellMarket();
		}
		_prevRsi = rsi;
	}
}