Открыть на GitHub

Стратегия HBS System (версия StockSharp)

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

HBS System — это конверсия советника MetaTrader 4 «HBS system.mq4» (ForTrader.ru) на высокоуровневый API StockSharp. Оригинальная система использует экспоненциальную скользящую среднюю и набор стоп-ордеров, которые привязываются к округлённым ценовым уровням. В направлении тренда выставляются два стоп-ордера: первый фиксирует прибыль у ближайшего округлённого уровня, второй нацелен на дальнейший пробой. Оба ордера имеют общий защитный стоп и общую схему трейлинг-стопа.

Перенос на StockSharp сохраняет многозвенную структуру входов и управляет рисками через стандартные методы BuyStop, SellStop, BuyLimit и SellLimit. Все комментарии и пояснения в коде приведены на английском языке, как требуют правила репозитория.

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

  1. Фильтр тренда. Экспоненциальная скользящая средняя рассчитывается по медианной цене завершённых свечей ((High + Low) / 2). Обрабатываются только завершённые свечи — это повторяет вызовы iMA(..., shift=1) из MT4.

  2. Округление уровней. Цена закрытия предыдущей свечи округляется вверх и вниз с помощью настраиваемого множителя (по умолчанию 100, то есть две цифры после запятой). Такое поведение имитирует MathCeil/MathFloor из оригинала.

  3. Построение входа. Если предыдущая свеча открылась и закрылась выше EMA, выставляются два отложенных ордера Buy Stop:

    • Первичный ордер размещается на уровне округление − entryOffset, а take-profit совпадает с округлённым уровнем.
    • Второй ордер использует тот же вход, но take-profit смещается на secondaryTakeProfitPoints пунктов дальше.
    • Оба ордера разделяют единый stop-loss (entry − stopLossPoints).

    Для коротких позиций логика зеркальна: используются Sell Stop, stop-loss выше входа и take-profit ниже округлённого уровня. Противоположные отложенные ордера отменяются, чтобы избежать конфликтов.

  4. Управление позицией. При срабатывании отложенного ордера создаётся отдельный лимитный ордер take-profit и обновляется общий стоп. Трейлинг-стоп подтягивает защитный ордер только после движения цены на заданное число пунктов.

  5. Очистка состояний. Выполненные и отменённые ордера удаляются из внутренних коллекций. При возврате позиции в ноль все защитные ордера снимаются, и стратегия полностью перезагружается.

Параметры

Параметр Описание Значение по умолчанию
EMA Period Период EMA, определяющий тренд. 200
Buy Stop-Loss (points) Расстояние в пунктах между входом в лонг и защитным стопом. 50
Buy Trailing (points) Дистанция трейлинг-стопа для длинных позиций. 10
Sell Stop-Loss (points) Расстояние в пунктах между входом в шорт и защитным стопом. 50
Sell Trailing (points) Дистанция трейлинг-стопа для коротких позиций. 10
Order Volume Объём каждого отложенного ордера. Максимальная экспозиция равна удвоенному объёму. 0.1
Entry Offset (points) Смещение (в пунктах) от округлённого уровня до цены стоп-ордера. 15
Second Take-Profit (points) Дополнительное смещение для удалённого take-profit. 15
Rounding Factor Множитель, определяющий точность округления цены. 100
Candle Type Тип свечных данных (по умолчанию часовой таймфрейм). TimeFrame(1h)

Практические замечания

  • Убедитесь, что у инструмента задан PriceStep или Decimals. При отсутствии данных используется запасное значение 0.0001.
  • Каждый отложенный ордер закрывается своим take-profit, поэтому выход происходит в две ступени.
  • Трейлинг активируется только после продвижения цены на заданную дистанцию (TrailingStop{Buy/Sell}Points).
  • Если инструмент требует другого формата округления, измените параметр RoundingFactor.
  • Стратегия не содержит встроенного манименеджмента — задавайте OrderVolume с учётом собственных рисков.

Особенности конверсии

  • Структура кода соответствует требованиям репозитория: табуляция, пространство имён StockSharp.Samples.Strategies, английские комментарии.
  • Используются высокоуровневые подписки на свечи и стандартные методы управления ордерами и защитой позиции.
  • Логика двухступенчатых take-profit и общего стопа полностью повторяет оригинальный алгоритм.

Источники

using System;



using StockSharp.Algo.Indicators;

using StockSharp.Algo.Strategies;

using StockSharp.BusinessEntities;

using StockSharp.Messages;



namespace StockSharp.Samples.Strategies;



public class HbsSystemStrategy : Strategy

{

	private readonly StrategyParam<int> _fastPeriod;

	private readonly StrategyParam<int> _slowPeriod;

	private readonly StrategyParam<DataType> _candleType;



	private decimal _prevFast; private decimal _prevSlow; private bool _hasPrev;

	private int _cooldown;



	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }

	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }



	public HbsSystemStrategy()

	{

		_fastPeriod = Param(nameof(FastPeriod), 5).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 13).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");

	}



	/// <inheritdoc />

	protected override void OnReseted()

	{

		base.OnReseted();

		_prevFast = default;

		_prevSlow = default;

		_hasPrev = default;

		_cooldown = default;

	}



	/// <inheritdoc />

	protected override void OnStarted2(DateTime time)

	{

		base.OnStarted2(time);

		_hasPrev = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };

		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription.Bind(fast, slow, ProcessCandle).Start();

	}



	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)

	{

		if (candle.State != CandleStates.Finished) return;

		if (!IsFormedAndOnlineAndAllowTrading()) return;

		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldown > 0)

		{

			_cooldown--;

			_prevFast = fast; _prevSlow = slow;

			return;

		}



		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)

		{

			var volume = Volume + Math.Abs(Position);

			BuyMarket(volume);

			_cooldown = 2;

		}

		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)

		{

			var volume = Volume + Math.Abs(Position);

			SellMarket(volume);

			_cooldown = 2;

		}

		_prevFast = fast; _prevSlow = slow;

	}

}