Открыть на GitHub

Стратегия «Synchronized Hour Breakout»

Обзор

Synchronized Hour Breakout — это портирование в StockSharp экспертного советника MetaTrader 4 JK_sinkhro1. Стратегия подсчитывает соотношение бычьих и медвежьих свечей за последний период и ищет точки входа только в два заранее определённых «синхронизированных» часа (по умолчанию 19:00 и 22:00 плюс смещение). Такой подход позволяет отлавливать направленные импульсы, сохраняя строгие ограничения риска, заложенные в оригинальный MQL-советник.

Логика торговли

  • Работает с серией свечей, заданной параметром Candle Type (по умолчанию — часовые свечи).
  • Поддерживает скользящее окно из Analysis Period завершённых свечей и подсчитывает, сколько из них закрылись ростом и падением.
  • Если число бычьих свечей больше медвежьих, формируется условие для покупки в первый час синхронизации (22 + Hour Offset).
  • Если число медвежьих свечей больше бычьих, готовится условие для продажи во второй час синхронизации (19 + Hour Offset).
  • Сигнал действителен только в первые пять минут часа, чтобы открытие сделки совпадало с началом новой свечи, как в исходном коде.
  • Новые заявки не отправляются, если уже достигнут лимит Max Active Orders или по инструменту имеется открытая позиция.

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

  • Позиции открываются фиксированным объёмом (Fixed Volume) либо адаптивным объёмом, вычисленным на основе свободных средств портфеля и параметра Risk %. Расчёт повторяет логику исходного советника: допустимый риск делится на расстояние до стоп-лосса в шагах цены.
  • Для каждой сделки используется многоуровневое сопровождение:
    • основной тейк-профит на расстоянии Take Profit (pts) от цены входа;
    • быстрый тейк-профит Secondary TP (pts), имитирующий досрочное закрытие позиции при достижении первого целевого уровня;
    • стоп-лосс Stop Loss (pts) ниже/выше цены входа.
  • Дополнительный трейлинг: при движении цены на величину Trailing Stop (pts) экстремум фиксируется, и позиция закрывается при возврате цены на трейлинг-дистанцию.
  • После полного выхода из позиции состояние стратегии очищается и готовится к следующему окну синхронизации.

Параметры

Параметр Описание
Take Profit (pts) Основной тейк-профит в шагах цены инструмента.
Secondary TP (pts) Быстрый тейк-профит, срабатывающий до основного.
Stop Loss (pts) Стоп-лосс в шагах цены.
Trailing Stop (pts) Дистанция трейлинг-стопа (0 — отключено).
Analysis Period Количество свечей в анализируемом окне.
Hour Offset Смещение, добавляемое к исходным часам 19:00 и 22:00.
Max Active Orders Максимум одновременно активных заявок стратегии.
Fixed Volume Объём сделки при выключенном риск-менеджменте.
Use Risk Volume Включение динамического расчёта объёма.
Risk % Процент капитала, которым рискуем в одной сделке (для динамического объёма).
Candle Type Тип/таймфрейм свечей, по которым ведётся расчёт.

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

  • Значения по умолчанию рассчитаны под торговлю парами типа EURUSD в часовом формате; при необходимости скорректируйте Hour Offset под часовой пояс брокера.
  • Убедитесь, что инструмент в StockSharp содержит корректные PriceStep, VolumeStep и MinVolume, чтобы расчёт объёма и стопов соответствовал торговой площадке.
  • Стратегия использует данные закрытия свечей, поэтому необходим источник данных, выдающий выбранный таймфрейм без задержек.
  • Трейлинг-логика основана на закрытиях свечей: такое поведение максимально приближено к тиковому сопровождению исходного эксперта, но полностью совместимо с высокоуровневым API StockSharp.
using System;

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

namespace StockSharp.Samples.Strategies;

public class SynchronizedHourBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevEma;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public SynchronizedHourBreakoutStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14).SetDisplay("ATR Period", "ATR lookback", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevEma = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevEma = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal atr)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevEma = ema;
			return;
		}

		if (_prevClose <= _prevEma && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevEma && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevEma = ema;
	}
}