Открыть на GitHub

Стратегия Vortex Indicator MMRec Duplex

Обзор

  • Конвертация советника MetaTrader 5 Exp_VortexIndicator_MMRec_Duplex.mq5 (MQL ID 23180) на высокоуровневый API StockSharp.
  • Поддерживаются два независимых потока индикатора Vortex: длинный и короткий. Для каждого задаётся собственный таймфрейм, период и сдвиг анализируемой свечи, что позволяет по-разному настраивать логику покупок и продаж.
  • Реализован модуль восстановления манименеджмента (MMRec). Стратегия хранит историю результатов сделок отдельно для длинной и короткой сторон и при серии убытков временно снижает объём следующей заявки.

Логика сигналов

  1. Для каждого потока открывается подписка на выбранный тип свечей и считается индикатор Vortex (VI+ и VI-).
  2. Покупка: если на предыдущей свече VI+ был ниже либо равен VI-, а на текущей закрытой свече VI+ пересекает VI- снизу вверх, и параметр AllowLongEntries включён.
  3. Закрытие покупок: когда VI- становится выше VI+ на анализируемой свече и AllowLongExits разрешён.
  4. Продажа: зеркальное условие – VI+ пересекает VI- сверху вниз, при активном AllowShortEntries.
  5. Закрытие продаж: VI+ поднимается выше VI-, если AllowShortExits включён.
  6. Для каждой стороны рассчитываются уровни стоп-лосса и тейк-профита в шагах цены. Достижение любого уровня немедленно закрывает позицию и регистрирует результат в счётчиках MMRec.

Восстановление манименеджмента

  • Оригинальный советник анализирует скользящее окно последних сделок и при превышении порога убытков переходит на уменьшенный объём. Порт полностью повторяет этот алгоритм.
  • Для длинных сделок очередь хранит до LongTotalTrigger последних результатов. Если среди них не менее LongLossTrigger убыточных, следующая покупка использует LongSmallMoneyManagement, иначе — LongMoneyManagement.
  • Для коротких сделок используются параметры ShortTotalTrigger, ShortLossTrigger, ShortSmallMoneyManagement и ShortMoneyManagement.
  • При нулевых значениях триггеров очередь очищается, и стратегия всегда торгует базовым объёмом.

Режимы манименеджмента

Перечисление MarginModeOption определяет, как параметр MM преобразуется в объём заявки:

  • FreeMargin (0): доля капитала (аналог исходного режима "по свободной марже").
  • Balance (1): то же поведение, что и FreeMargin, используется текущая стоимость портфеля.
  • LossFreeMargin (2): риск определённой доли капитала с учётом дистанции стоп-лосса. При нулевом стопе используется цена инструмента.
  • LossBalance (3): аналогично LossFreeMargin в рамках данной реализации.
  • Lot (4): значение трактуется как объём в лотах без пересчёта.

Полученные объёмы нормализуются по шагу объёма инструмента и его ограничениям (MinVolume, MaxVolume).

Параметры

Параметр Значение по умолчанию Описание
LongCandleType H4 Таймфрейм для расчёта длинного индикатора Vortex.
ShortCandleType H4 Таймфрейм для короткого индикатора.
LongLength 14 Период Vortex для длинной стороны.
ShortLength 14 Период Vortex для короткой стороны.
LongSignalBar 1 Номер закрытой свечи для анализа длинных сигналов (0 — последняя закрытая).
ShortSignalBar 1 Сдвиг анализируемой свечи для коротких сигналов.
AllowLongEntries true Разрешение открытия длинных позиций при бычьем пересечении.
AllowLongExits true Разрешение закрытия длинных позиций при доминировании VI-.
AllowShortEntries true Разрешение открытия коротких позиций при медвежьем пересечении.
AllowShortExits true Разрешение закрытия коротких позиций при возврате VI+ выше VI-.
LongTotalTrigger 5 Количество последних длинных сделок в окне MMRec.
LongLossTrigger 3 Число убыточных длинных сделок для перехода на уменьшенный объём.
LongMoneyManagement 0.1 Базовый MM для покупок.
LongSmallMoneyManagement 0.01 Сниженный MM после серии убытков.
LongMarginMode Lot Режим интерпретации MM для покупок (см. список выше).
LongStopLossSteps 1000 Дистанция стоп-лосса ниже входа (в шагах цены).
LongTakeProfitSteps 2000 Дистанция тейк-профита выше входа (в шагах цены).
LongSlippageSteps 10 Информационный допуск проскальзывания для покупок.
ShortTotalTrigger 5 Размер окна сделок для короткой стороны.
ShortLossTrigger 3 Порог убыточных продаж для снижения объёма.
ShortMoneyManagement 0.1 Базовый MM для продаж.
ShortSmallMoneyManagement 0.01 Сниженный MM после серии убыточных продаж.
ShortMarginMode Lot Режим интерпретации MM для продаж.
ShortStopLossSteps 1000 Дистанция стоп-лосса выше точки входа (в шагах цены).
ShortTakeProfitSteps 2000 Дистанция тейк-профита ниже точки входа (в шагах цены).
ShortSlippageSteps 10 Информационный допуск проскальзывания для продаж.

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

  • Использован высокоуровневый API StockSharp. Подписки на свечи связываются с индикаторами через Bind, что обеспечивает обработку только завершённых свечей.
  • Очереди результатов сделок реализуют оригинальные функции BuyTradeMMRecounterS и SellTradeMMRecounterS. Для каждой стороны хранится отдельный список последних PnL.
  • Стоп-лосс и тейк-профит пересчитываются в абсолютные цены через шаг цены инструмента и проверяются на каждой поступающей свече.
  • Объёмы заявок нормализуются по VolumeStep, минимальному и максимальному объёму инструмента. Это предотвращает отправку некорректных заявок.
  • Параметры проскальзывания сохранены для полноты документации, но напрямую в расчётах объёма не участвуют.
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>
/// Vortex Indicator MMRec Duplex strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class VortexIndicatorMmrecDuplexStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public VortexIndicatorMmrecDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}