Открыть на GitHub

Стратегия Harami

Общее описание

HaramiStrategy — конвертированная версия советника MetaTrader «Harami» на платформу StockSharp. Стратегия объединяет поиск бычьих и медвежьих паттернов «беременная» на старшем таймфрейме с подтверждением через импульс и месячный MACD. Обрабатываются только закрытые свечи, а управление сделками выполняется встроенным модулем защиты StockSharp.

Данные и индикаторы

  • Базовый таймфрейм: настраиваемый (по умолчанию 15 минут) — используется для оценки тренда по скользящим средним.
  • Старший таймфрейм: настраиваемый (по умолчанию 1 час) — для распознавания паттерна и контроля импульса.
  • Таймфрейм MACD: настраиваемый (по умолчанию 30-дневные свечи), что имитирует месячный фильтр исходного алгоритма.
  • Набор индикаторов:
    • Линейно-взвешенное скользящее среднее (FastMaLength) на базовом таймфрейме.
    • Экспоненциальное скользящее среднее (SlowMaLength) на базовом таймфрейме.
    • Индикатор Momentum (MomentumPeriod) на старшем таймфрейме. В расчет берутся абсолютные отклонения от уровня 100 для последних трех свечей.
    • MACD (12/26/9) на выбранном таймфрейме MACD.

Условия для покупки

  1. На базовом таймфрейме медленное EMA выше быстрого LWMA — тренд восходящий.
  2. На старшем таймфрейме сформирован бычий Harami: позапрошлая свеча медвежья, прошлая свеча бычья, ее тело меньше предыдущего.
  3. Хотя бы одно из трех последних значений импульса превышает MomentumBuyThreshold.
  4. Линия MACD находится выше сигнальной линии.
  5. Чистая позиция не длинная (Position <= 0).
  6. Отправляется рыночная заявка Buy, которая закрывает шорты и добавляет объем Volume.

Условия для продажи

  1. На базовом таймфрейме медленное EMA ниже быстрого LWMA.
  2. На старшем таймфрейме сформирован медвежий Harami: позапрошлая свеча бычья, прошлая — медвежья и более узкая.
  3. Одно из трех последних значений импульса превышает MomentumSellThreshold.
  4. Линия MACD ниже сигнальной.
  5. Чистая позиция не короткая (Position >= 0).
  6. Отправляется рыночная заявка Sell, закрывающая лонги и открывающая новый шорт объемом Volume.

Управление рисками

Метод StartProtection выставляет стоп-лосс и тейк-профит в пунктах. Трейлинг, перевод в безубыток и денежное сопровождение из оригинального советника опущены ради простоты. При смене направления позиции противоположная сторона закрывается автоматически.

Параметры

Параметр Назначение Значение по умолчанию
CandleType Базовый таймфрейм для сигналов и скользящих средних. 15 минут
HigherCandleType Старший таймфрейм для паттерна Harami и импульса. 1 час
MacdCandleType Таймфрейм для MACD-фильтра. 30 дней
FastMaLength Длина быстрого LWMA. 6
SlowMaLength Длина медленного EMA. 85
MomentumPeriod Период Momentum на старшем таймфрейме. 14
MomentumBuyThreshold Порог импульса для покупок. 0.3
MomentumSellThreshold Порог импульса для продаж. 0.3
StopLossPoints Размер стоп-лосса в пунктах. 40
TakeProfitPoints Размер тейк-профита в пунктах. 100

Рекомендации по использованию

  • Согласуйте CandleType, HigherCandleType и MacdCandleType с доступными котировками, выбирая старший таймфрейм длиннее базового.
  • Подбирайте пороги импульса с учетом волатильности инструмента.
  • Используйте оптимизатор StockSharp в указанных диапазонах, чтобы настроить длины скользящих и пороги Momentum.
  • Перед запуском в реальную торговлю проведите полноценное тестирование с учетом комиссий и задержек.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class HaramiStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public HaramiStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}