Bullish & Bearish Harami Stochastic Strategy — порт StockSharp эксперта MetaTrader expert_abh_bh_stoch.mq5 из каталога MQL/310. Оригинальный робот распознаёт свечные комбинации «бычье харами» и «медвежье харами» и допускает сделку только при подтверждении осциллятором Stochastic. Реализация на C# полностью повторяет логику, используя высокоуровневый API StockSharp, и добавляет подробный лог с визуализацией на графике.
Ключевые идеи
Анализ двух последних завершённых свечей для поиска фигур Bullish Harami и Bearish Harami.
Подтверждение сигналов по линии %D стохастика: покупки — при значении ниже порога перепроданности, продажи — при значении выше порога перекупленности.
Закрытие коротких позиций при отскоке %D выше выходных уровней и закрытие длинных позиций при падении %D ниже этих уровней.
Параметры
Параметр
Описание
Значение по умолчанию
CandleType
Таймфрейм свечей для распознавания паттернов.
1 час
StochasticKPeriod
Период расчёта стохастика %K.
47
StochasticDPeriod
Период сглаживания линии %D.
9
StochasticSlowing
Дополнительное сглаживание %K (параметр Slowing).
13
MovingAveragePeriod
Число свечей для усреднения тел свечей.
5
OversoldLevel
Порог перепроданности для подтверждения покупок.
30
OverboughtLevel
Порог перекупленности для подтверждения продаж.
70
ExitLowerLevel
Нижний уровень стохастика для выхода.
20
ExitUpperLevel
Верхний уровень стохастика для выхода.
80
Правила торговли
Вход в длинную позицию
На двух последних свечах обнаружено бычье харами (малое белое тело внутри длинного чёрного тела в нисходящем движении).
Значение %D на подтверждающей свече не выше OversoldLevel.
Открытых длинных позиций нет (Position <= 0).
Стратегия покупает по рынку на объём Volume, при необходимости закрывая шорт.
Вход в короткую позицию
Обнаружено медвежье харами (малое чёрное тело внутри длинной белой свечи в восходящем тренде).
%D находится не ниже OverboughtLevel.
Нет открытых шортов (Position >= 0).
Стратегия продаёт по рынку, переворачивая позицию при необходимости.
Выходы
Покрытие шортов: Стохастик %D пробивает вверх ExitLowerLevel или ExitUpperLevel — позиция закрывается.
Закрытие лонгов: %D опускается ниже ExitUpperLevel или ExitLowerLevel — длинная позиция закрывается полностью.
Файлы
CS/BullishBearishHaramiStochasticStrategy.cs — реализация стратегии на высокоуровневом API StockSharp.
README.md — документация на английском языке.
README_ru.md — описание на русском языке (текущий файл).
README_zh.md — документация на китайском языке.
Важно: Python-версия не создавалась согласно требованию задачи.
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>
/// Harami + Stochastic strategy: Bullish/bearish harami patterns with stochastic confirmation.
/// </summary>
public class BullishBearishHaramiStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _signalCooldownCandles;
private readonly List<ICandleMessage> _candles = new();
private decimal _prevK;
private bool _hasPrevK;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.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 BullishBearishHaramiStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
_oversold = Param(nameof(Oversold), 30m)
.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
_overbought = Param(nameof(Overbought), 70m)
.SetDisplay("Overbought", "Stochastic 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();
_prevK = 0m;
_hasPrevK = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_hasPrevK = false;
_candlesSinceTrade = SignalCooldownCandles;
var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(stoch, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var stochTyped = stochValue as StochasticOscillatorValue;
if (stochTyped?.K is not decimal kValue) return;
_candles.Add(candle);
if (_candles.Count > 5) _candles.RemoveAt(0);
if (_candles.Count >= 2)
{
var curr = _candles[^1];
var prev = _candles[^2];
// Bullish harami: prev bearish, curr bullish, curr body inside prev body
var bullishHarami = prev.OpenPrice > prev.ClosePrice
&& curr.ClosePrice > curr.OpenPrice
&& curr.OpenPrice > prev.ClosePrice
&& curr.ClosePrice < prev.OpenPrice;
// Bearish harami: prev bullish, curr bearish, curr body inside prev body
var bearishHarami = prev.ClosePrice > prev.OpenPrice
&& curr.OpenPrice > curr.ClosePrice
&& curr.ClosePrice > prev.OpenPrice
&& curr.OpenPrice < prev.ClosePrice;
if (bullishHarami && kValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (bearishHarami && kValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
if (_hasPrevK)
{
if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
}
_prevK = kValue;
_hasPrevK = true;
}
}
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 StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class bullish_bearish_harami_stochastic_strategy(Strategy):
def __init__(self):
super(bullish_bearish_harami_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._stoch_period = self.Param("StochPeriod", 14)
self._oversold = self.Param("Oversold", 30.0)
self._overbought = self.Param("Overbought", 70.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._prev_k = 0.0
self._has_prev_k = False
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 StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_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(bullish_bearish_harami_stochastic_strategy, self).OnReseted()
self._candles.clear()
self._prev_k = 0.0
self._has_prev_k = False
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(bullish_bearish_harami_stochastic_strategy, self).OnStarted2(time)
self._candles.clear()
self._has_prev_k = False
self._candles_since_trade = self.SignalCooldownCandles
stoch = StochasticOscillator()
stoch.K.Length = self.StochPeriod
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stoch, self._process_candle).Start()
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
k_val = stoch_value.K
if k_val is None:
return
k_value = float(k_val)
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: prev bearish, curr bullish, curr body inside prev body
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: prev bullish, curr bearish, curr body inside prev body
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 k_value < self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif bearish_harami and k_value > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
if self._has_prev_k:
if self.Position > 0 and self._prev_k >= self.Overbought and k_value < self.Overbought and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
elif self.Position < 0 and self._prev_k <= self.Oversold and k_value > self.Oversold and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
self._prev_k = k_value
self._has_prev_k = True
def CreateClone(self):
return bullish_bearish_harami_stochastic_strategy()