Открыть на GitHub

Стратегия Bullish & Bearish Harami Stochastic

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

Правила торговли

Вход в длинную позицию

  1. На двух последних свечах обнаружено бычье харами (малое белое тело внутри длинного чёрного тела в нисходящем движении).
  2. Значение %D на подтверждающей свече не выше OversoldLevel.
  3. Открытых длинных позиций нет (Position <= 0).
  4. Стратегия покупает по рынку на объём Volume, при необходимости закрывая шорт.

Вход в короткую позицию

  1. Обнаружено медвежье харами (малое чёрное тело внутри длинной белой свечи в восходящем тренде).
  2. %D находится не ниже OverboughtLevel.
  3. Нет открытых шортов (Position >= 0).
  4. Стратегия продаёт по рынку, переворачивая позицию при необходимости.

Выходы

  • Покрытие шортов: Стохастик %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;
	}
}