ABH_BH_MFI — перенос советника MetaTrader «Expert_ABH_BH_MFI» на платформу StockSharp с использованием высокоуровневого API. Стратегия ищет бычьи и медвежьи паттерны «беременная» (Harami) и подтверждает их показаниями индикатора Money Flow Index (MFI). Покупка производится при формировании бычьей беременной на падающем рынке, когда MFI остаётся подавленным. Продажа выполняется при формировании медвежьей беременной на восходящем рынке с высоким значением MFI. В отличие от MQL-версии, все блоки сигналов, управления капиталом и сопровождения позиций реализованы средствами StockSharp.
Логика торговли
1. Определение паттернов Harami
В обработчике сохраняются две последние полностью сформированные свечи.
Бычий Harami:
Свеча два бара назад — длинная чёрная (медвежья), её тело превышает средний размер тела.
Предыдущая свеча — белая (бычья), её тело полностью помещается внутри тела предыдущей чёрной свечи.
Средняя точка (High+Low)/2 старой свечи ниже скользящей средней закрытий, что подтверждает нисходящий фон.
Медвежий Harami строится зеркально: длинная белая свеча в прошлом, новая чёрная свеча внутри её тела и средняя точка выше скользящей средней закрытий.
2. Подтверждение по MFI
Индикатор MFI использует параметр MfiPeriod (по умолчанию 37) из оригинального советника.
Для открытия длинной позиции MFI предыдущей свечи должен быть ниже BullishThreshold (по умолчанию 40).
Для открытия короткой позиции MFI должен быть выше BearishThreshold (по умолчанию 60).
3. Правила выхода
Длинная позиция закрывается, если MFI пересекает снизу вверх ExitLowerLevel (по умолчанию 30) или ExitUpperLevel (по умолчанию 70).
Короткая позиция закрывается, когда MFI пересекает сверху вниз ExitUpperLevel либо пробивает снизу вверх ExitLowerLevel. Эти условия один в один повторяют проверки MFI(1) и MFI(2) в MQL-коде.
4. Управление рисками
Метод StartProtection устанавливает фиксированные стоп-лосс и тейк-профит в шагах цены, если заданы StopLossPoints и TakeProfitPoints. Нулевые значения отключают защиту, как и в оригинале.
Размер позиции задаётся через свойство Volume. При реверсе стратегия автоматически добирает объём, чтобы закрыть обратную позицию и открыть новую.
Параметры
Параметр
Значение по умолчанию
Назначение
CandleType
Таймфрейм 1 час
Основная свечная серия для анализа.
MfiPeriod
37
Период Money Flow Index.
BodyAveragePeriod
11
Длина SMA для оценки средних размеров тел и тренда закрытий.
BullishThreshold
40
Максимальный MFI для подтверждения бычьего сигнала.
BearishThreshold
60
Минимальный MFI для подтверждения медвежьего сигнала.
ExitLowerLevel
30
Нижний уровень MFI для фиксации позиций.
ExitUpperLevel
70
Верхний уровень MFI для фиксации позиций.
StopLossPoints
0
Стоп-лосс в шагах цены (0 — без стопа).
TakeProfitPoints
0
Тейк-профит в шагах цены (0 — без цели).
Особенности реализации
Свечи подписываются через SubscribeCandles, и расчёты выполняются только для завершённых баров (CandleStates.Finished).
Индикатор MFI привязан с помощью .Bind(_mfi, ProcessCandle), что избавляет от прямого обращения к буферам значений.
Две простые скользящие средние воспроизводят функции AvgBody и CloseAvg из MQL и кэшируют результаты, исключая доступ к историческим данным.
Перед отправкой заявок вызывается IsFormedAndOnlineAndAllowTrading(), что соответствует рекомендациям StockSharp по контролю состояния стратегии.
Отличия от версии MetaTrader
Модуль фиксированного лота заменён использованием стандартного свойства Volume, поскольку дополнительная прослойка не требуется.
Блок TrailingNone не выполнял действий, поэтому в переносе отсутствует логика трейлинг-стопа; при необходимости можно добавить её отдельно.
Журнальные сообщения сведены к минимуму. При тестировании или отладке можно добавить дополнительные LogInfo.
Рекомендации по использованию
Перед стартом задайте инструмент и подходящий CandleType.
При необходимости оптимизируйте уровни MFI под актив и таймфрейм.
Установите ненулевые StopLossPoints и TakeProfitPoints, если брокер требует защитные заявки.
Следите за создаваемыми графиками: стратегия отображает свечи, индикатор MFI и совершённые сделки.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// ABH BH MFI strategy: Harami pattern with MFI confirmation.
/// </summary>
public class AbhBhMfiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _signalCooldownCandles;
private readonly List<ICandleMessage> _candles = new();
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public AbhBhMfiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("MFI Period", "Money Flow Index period", "Indicators");
_oversold = Param(nameof(Oversold), 40m)
.SetDisplay("Oversold", "MFI oversold level", "Signals");
_overbought = Param(nameof(Overbought), 60m)
.SetDisplay("Overbought", "MFI overbought level", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_candlesSinceTrade = SignalCooldownCandles;
var mfi = new MoneyFlowIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(mfi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
_candles.Add(candle);
if (_candles.Count > 5) _candles.RemoveAt(0);
if (_candles.Count >= 2)
{
var curr = _candles[^1];
var prev = _candles[^2];
var bullishHarami = prev.OpenPrice > prev.ClosePrice
&& curr.ClosePrice > curr.OpenPrice
&& curr.OpenPrice > prev.ClosePrice
&& curr.ClosePrice < prev.OpenPrice;
var bearishHarami = prev.ClosePrice > prev.OpenPrice
&& curr.OpenPrice > curr.ClosePrice
&& curr.ClosePrice > prev.OpenPrice
&& curr.OpenPrice < prev.ClosePrice;
if (bullishHarami && mfiValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (bearishHarami && mfiValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
}
}
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 MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class abh_bh_mfi_strategy(Strategy):
def __init__(self):
super(abh_bh_mfi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._mfi_period = self.Param("MfiPeriod", 14)
self._oversold = self.Param("Oversold", 40.0)
self._overbought = self.Param("Overbought", 60.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MfiPeriod(self):
return self._mfi_period.Value
@MfiPeriod.setter
def MfiPeriod(self, value):
self._mfi_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(abh_bh_mfi_strategy, self).OnReseted()
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(abh_bh_mfi_strategy, self).OnStarted2(time)
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
mfi = MoneyFlowIndex()
mfi.Length = self.MfiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mfi, self._process_candle).Start()
def _process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
mfi_val = float(mfi_value)
self._candles.append(candle)
if len(self._candles) > 5:
self._candles.pop(0)
if len(self._candles) >= 2:
curr = self._candles[-1]
prev = self._candles[-2]
bullish_harami = (float(prev.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) > float(curr.OpenPrice)
and float(curr.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) < float(prev.OpenPrice))
bearish_harami = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) < float(prev.ClosePrice))
if bullish_harami and mfi_val < self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif bearish_harami and mfi_val > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return abh_bh_mfi_strategy()