Открыть на GitHub

Утренняя стратегия отката в ценовом коридоре

Обзор

Утренняя стратегия отката в ценовом коридоре воспроизводит работу эксперта MetaTrader 4 «3_Otkat_Sys_v1_2». Она анализирует импульс, возникший за ночь, и торгует только в первые минуты утренней сессии, оценивая коррекцию относительно коридора, построенного по свечам с интервалом в 29 баров. Для длинных и коротких позиций задаются разные уровни фиксации прибыли.

Торговая логика

  1. Фильтр по времени — сигналы рассматриваются лишь в заданный час (по умолчанию 05:00 по времени терминала) и только в первые минуты этого часа. Как и в оригинале, понедельник и пятница исключаются.
  2. Расчёт ценового коридора — стратегия хранит скользящее окно последних свечей и сравнивает:
    • открытие 29 баров назад с закрытием предыдущей свечи (Open[29] - Close[1]),
    • закрытие предыдущей свечи с открытием 29 баров назад (Close[1] - Open[29]),
    • расстояние от предыдущего закрытия до минимального минимума за последние 29 баров,
    • расстояние от максимума за тот же период до предыдущего закрытия.
  3. Условия входа — если ночной импульс превосходит порог CorridorOpenClosePoints, а текущий откат попадает в диапазон PullbackPoints ± CorridorPullbackPoints, открывается рыночная позиция:
    • Для покупок требуется либо ночное падение с неглубокой коррекцией, либо продолжение роста выше коридора.
    • Для продаж используется зеркальный набор условий.
  4. Управление позицией — при открытии сделки устанавливаются:
    • стоп-лосс на расстоянии StopLossPoints * PriceStep от цены входа,
    • тейк-профит на уровне TakeProfitPoints * PriceStep для коротких и (TakeProfitPoints + LongTakeProfitExtraPoints) * PriceStep для длинных позиций.
  5. Закрытие в конце дня — если к моменту, превышающему заданный порог (по умолчанию после 22:45), позиция остаётся открытой, она закрывается принудительно.

Параметры

Параметр Описание
TakeProfitPoints Базовая дистанция тейк-профита в пунктах. Для покупок добавляется LongTakeProfitExtraPoints.
StopLossPoints Дистанция защитного стопа в пунктах.
PullbackPoints Целевая глубина отката, вокруг которой анализируются условия входа.
CorridorOpenClosePoints Минимальная разница между ценами, разделёнными 29 барами, подтверждающая ночной импульс.
CorridorPullbackPoints Допуск, добавляемый к целевому откату для формирования коридора.
LongTakeProfitExtraPoints Дополнительные пункты тейк-профита для длинных позиций.
TradeHour Час (0–23), в течение которого разрешено открывать новые позиции.
TradeMinuteLimit Максимальная минута внутри торгового часа, допускающая появление сигнала.
CloseHour Час, начиная с которого проверяется условие принудительного закрытия.
CloseMinuteThreshold Минута внутри CloseHour, после которой позиция закрывается.
CandleType Используемый таймфрейм свечей (по умолчанию 1 минута).

Особенности реализации

  • Для перевода точечных значений в абсолютные цены используется Security.PriceStep. При отсутствии корректного шага цена по умолчанию принимается равной 1.0.
  • Стоп-лосс и тейк-профит контролируются на каждой завершённой свече: при пробое уровня сделка закрывается рыночным ордером.
  • Список хранит последние 60 свечей, что покрывает требуемые 29 баров и имитирует функции Lowest/Highest из MQL.
  • При наличии графической области настраиваемый компонент автоматически рисует свечи и сделки стратегии.

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

  • Перед запуском задайте величину Volume — советник не меняет размер позиции динамически.
  • Согласуйте часовой пояс данных с тем, который использовался в оригинальной системе, чтобы временные фильтры срабатывали корректно.
  • Пороговые значения, выраженные в пунктах, необходимо оптимизировать для инструментов с другой волатильностью.
using System;

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

namespace StockSharp.Samples.Strategies;

public class MorningPullbackCorridorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MorningPullbackCorridorStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 100).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();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

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

		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 (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast;
		_prevSlow = slow;
	}
}