Ultra MFI MMRec — перенос эксперта MetaTrader 5 Exp_UltraMFI_MMRec. Стратегия использует многоступенчатое сглаживание индикатора Money Flow Index и адаптивный манименеджмент по серии убыточных сделок. Два внутренних счётчика показывают, сколько ступеней сглаживания указывают вверх или вниз. Пересечения этих счётчиков формируют торговые сигналы, а результат последних сделок определяет объём следующей позиции.
Логика работы
Базовый индикатор. Рассчитывается Money Flow Index с настраиваемым периодом на выбранном типе свечей.
Лестница сглаживания. Значение MFI проходит через набор средних, где каждая ступень увеличивает длину на фиксированный шаг. Доступны методы Simple, Exponential, Smoothed, Linear Weighted и Jurik (прочие варианты MT5 недоступны в StockSharp).
Направленные счётчики. Для каждой свечи сравниваются текущее и предыдущее значение каждой ступени. Если ступень растёт — увеличивается бычий счётчик, иначе медвежий. Затем оба счётчика дополнительно сглаживаются финальной средней.
Смещение сигнала. Стратегия обрабатывает только закрытые свечи. Параметр SignalShift задаёт, сколько завершённых баров учитывать при сравнении счётчиков, повторяя поведение MT5 с SignalBar=1.
Сигналы входа и выхода.
Если на предыдущем баре бычий счётчик был выше медвежьего и на последнем баре произошло пересечение вниз (bulls < bears), стратегия закрывает короткие позиции и при отсутствии позиции открывает длинную.
Если на предыдущем баре преобладали медведи и на последнем баре счётчики пересеклись вверх (bulls > bears), закрываются длинные позиции и при пустом портфеле открывается шорт.
При необходимости можно задать процентные уровни тейк-профита и стоп-лосса через StartProtection.
Манименеджмент. После закрытия каждой позиции анализируется реализованная прибыль/убыток:
Хранятся последние BuyTotalTrigger сделок в лонг. Если количество убыточных сделок достигает BuyLossTrigger, следующий вход в покупку выполняется объёмом ReducedVolume, иначе — NormalVolume.
Для коротких позиций используется независимый набор параметров SellTotalTrigger и SellLossTrigger.
Параметры
CandleType — тип свечей/таймфрейм для расчёта сигналов.
MfiPeriod — период индикатора Money Flow Index.
StepSmoothing / FinalSmoothing — тип средних для ступеней и финальных счётчиков.
StartLength / StepSize / StepsTotal — начальная длина, шаг и количество ступеней сглаживания.
FinalSmoothingLength — длина итогового сглаживания счётчиков.
SignalShift — количество закрытых баров, используемых при сравнении счётчиков.
NormalVolume / ReducedVolume — стандартный и уменьшенный торговый объём.
BuyTotalTrigger / BuyLossTrigger — глубина истории и порог убыточных сделок для снижения объёма в лонг.
SellTotalTrigger / SellLossTrigger — аналогичные настройки для шортов.
AllowLongEntries / AllowShortEntries / AllowLongExits / AllowShortExits — включение/отключение входов и выходов по направлениям.
TakeProfitPercent / StopLossPercent — необязательные процентные уровни тейк-профита и стоп-лосса.
Практические советы
Для корректной работы лестницы сглаживания требуется достаточный объём истории. Дождитесь формирования всех индикаторов перед анализом сигналов.
В StockSharp отсутствуют специфические режимы MT5 (JurX, Parabolic, VIDYA, AMA), поэтому используются ближайшие доступные аналоги. Jurik по умолчанию лучше всего воспроизводит оригинальный индикатор UltraMFI.
Манименеджмент опирается на реализованный PnL. В тестах необходимо исполнять заявки до конца, чтобы после закрытия позиции фиксировался результат.
Стратегия открывает новую позицию только после выхода в «ноль». При появлении сигнала разворота сначала закрывается текущая позиция, и только затем на следующем подходящем баре рассматривается вход в противоположную сторону.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Ultra MFI MMRec strategy. Uses EMA crossover as signal generator.
/// </summary>
public class UltraMfiMmRecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _prevSlow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public UltraMfiMmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fastVal > slowVal;
_prevFast = fastVal;
_prevSlow = slowVal;
if (!prevAbove && currAbove && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (prevAbove && !currAbove && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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 ultra_mfi_mm_rec_strategy(Strategy):
def __init__(self):
super(ultra_mfi_mm_rec_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 8) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(ultra_mfi_mm_rec_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(ultra_mfi_mm_rec_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return ultra_mfi_mm_rec_strategy()