Конвертация советника MetaTrader 5 Exp_VortexIndicator_MMRec_Duplex.mq5 (MQL ID 23180) на высокоуровневый API StockSharp.
Поддерживаются два независимых потока индикатора Vortex: длинный и короткий. Для каждого задаётся собственный таймфрейм, период и сдвиг анализируемой свечи, что позволяет по-разному настраивать логику покупок и продаж.
Реализован модуль восстановления манименеджмента (MMRec). Стратегия хранит историю результатов сделок отдельно для длинной и короткой сторон и при серии убытков временно снижает объём следующей заявки.
Логика сигналов
Для каждого потока открывается подписка на выбранный тип свечей и считается индикатор Vortex (VI+ и VI-).
Покупка: если на предыдущей свече VI+ был ниже либо равен VI-, а на текущей закрытой свече VI+ пересекает VI- снизу вверх, и параметр AllowLongEntries включён.
Закрытие покупок: когда VI- становится выше VI+ на анализируемой свече и AllowLongExits разрешён.
Продажа: зеркальное условие – VI+ пересекает VI- сверху вниз, при активном AllowShortEntries.
Закрытие продаж:VI+ поднимается выше VI-, если AllowShortExits включён.
Для каждой стороны рассчитываются уровни стоп-лосса и тейк-профита в шагах цены. Достижение любого уровня немедленно закрывает позицию и регистрирует результат в счётчиках 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vortex_indicator_mmrec_duplex_strategy(Strategy):
def __init__(self):
super(vortex_indicator_mmrec_duplex_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(vortex_indicator_mmrec_duplex_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(vortex_indicator_mmrec_duplex_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return vortex_indicator_mmrec_duplex_strategy()