Стратегия AH HM MFI торгует свечными моделями «молот» и «повешенный», подтверждёнными индикатором Money Flow Index (MFI). Если после локального нисходящего движения появляется «молот», а MFI остаётся ниже порога перепроданности, открывается длинная позиция. Когда в восходящем тренде формируется «повешенный» и MFI превышает порог перекупленности, стратегия открывает короткую позицию. Выходы выполняются при пересечении MFI заранее заданных границ.
Логика работы
Подписка на свечи выбранного таймфрейма и расчёт двух индикаторов:
Money Flow Index с настраиваемым периодом (по умолчанию 47).
Простая скользящая средняя по ценам закрытия для оценки тренда (период по умолчанию 5).
Поиск моделей молот и повешенный:
Тело свечи расположено в верхней трети диапазона.
Длинная нижняя тень относительно тела.
Разрыв в сторону тренда относительно предыдущей свечи.
Подтверждение тренда через сравнение средней точки предыдущей свечи и значения скользящей средней.
Подтверждение входов через уровни MFI:
Покупка, если найден «молот» и MFI не превышает порог перепроданности (по умолчанию 40).
Продажа, если обнаружен «повешенный» и MFI не опускается ниже порога перекупленности (по умолчанию 60).
Управление выходами по пересечениям MFI:
Короткие позиции закрываются при пробое MFI вверх выше нижней или верхней границ (по умолчанию 30 и 70).
Длинные позиции закрываются при пробое MFI вверх выше верхней границы или при падении ниже нижней.
Запуск встроенной защиты StartProtection для обработки аварийных ситуаций.
Параметры
Имя
Описание
Значение по умолчанию
CandleType
Тип и таймфрейм свечей для анализа паттернов.
30-минутные свечи
MfiPeriod
Период расчёта индикатора MFI.
47
MaPeriod
Период SMA по ценам закрытия для фильтра тренда.
5
HammerEntryThreshold
Максимальное значение MFI для входа по сигналу «молот».
40
HangingEntryThreshold
Минимальное значение MFI для входа по сигналу «повешенный».
60
MfiUpperExitLevel
Верхняя граница MFI, пробой которой закрывает позицию.
70
MfiLowerExitLevel
Нижняя граница MFI: пробой вниз закрывает лонг, пробой вверх — шорт.
30
Примечания
В расчёт принимаются только завершённые свечи, чтобы не реагировать на незавершённые данные.
Проверка паттернов требует и длинной тени, и расположения тела в верхней части свечи, что снижает количество ложных сигналов.
Скользящая средняя заменяет фильтр CloseAvg из оригинального советника MetaTrader 5 и обеспечивает согласованность сигналов с направлением тренда.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Hammer/Hanging Man + MFI strategy.
/// Buys on hammer with low MFI (oversold), sells on hanging man with high MFI (overbought).
/// </summary>
public class AhHmMfiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _mfiLow;
private readonly StrategyParam<decimal> _mfiHigh;
private readonly StrategyParam<int> _signalCooldownCandles;
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 MfiLow { get => _mfiLow.Value; set => _mfiLow.Value = value; }
public decimal MfiHigh { get => _mfiHigh.Value; set => _mfiHigh.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public AhHmMfiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("MFI Period", "MFI period", "Indicators");
_mfiLow = Param(nameof(MfiLow), 35m)
.SetDisplay("MFI Low", "MFI oversold threshold for buy", "Signals");
_mfiHigh = Param(nameof(MfiHigh), 65m)
.SetDisplay("MFI High", "MFI overbought threshold for sell", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candlesSinceTrade = SignalCooldownCandles;
var rsi = new RelativeStrengthIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var range = candle.HighPrice - candle.LowPrice;
if (range <= 0 || body <= 0) return;
var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
var isHammer = lowerShadow > body * 2.5m && upperShadow < body * 0.5m;
var isHangingMan = upperShadow > body * 2.5m && lowerShadow < body * 0.5m;
if (isHammer && mfiValue < MfiLow && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (isHangingMan && mfiValue > MfiHigh && 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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class ah_hm_mfi_strategy(Strategy):
def __init__(self):
super(ah_hm_mfi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._mfi_period = self.Param("MfiPeriod", 14)
self._mfi_low = self.Param("MfiLow", 35.0)
self._mfi_high = self.Param("MfiHigh", 65.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 8)
self._candles_since_trade = 8
@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 MfiLow(self):
return self._mfi_low.Value
@MfiLow.setter
def MfiLow(self, value):
self._mfi_low.Value = value
@property
def MfiHigh(self):
return self._mfi_high.Value
@MfiHigh.setter
def MfiHigh(self, value):
self._mfi_high.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(ah_hm_mfi_strategy, self).OnReseted()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(ah_hm_mfi_strategy, self).OnStarted2(time)
self._candles_since_trade = self.SignalCooldownCandles
rsi = RelativeStrengthIndex()
rsi.Length = self.MfiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, 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)
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
rng = float(candle.HighPrice) - float(candle.LowPrice)
if rng <= 0 or body <= 0:
return
upper_shadow = float(candle.HighPrice) - max(float(candle.OpenPrice), float(candle.ClosePrice))
lower_shadow = min(float(candle.OpenPrice), float(candle.ClosePrice)) - float(candle.LowPrice)
is_hammer = lower_shadow > body * 2.5 and upper_shadow < body * 0.5
is_hanging_man = upper_shadow > body * 2.5 and lower_shadow < body * 0.5
if is_hammer and mfi_val < self.MfiLow and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif is_hanging_man and mfi_val > self.MfiHigh and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return ah_hm_mfi_strategy()