Открыть на GitHub

Стратегия AnyRange Cloud Tail System Tm Plus

Стратегия переносит логику эксперта Exp_i-AnyRangeCldTail_System_Tm_Plus.mq5 в экосистему StockSharp. Она строит пользовательский внутридневной диапазон между двумя заданными моментами времени, отслеживает пробои этого диапазона и выставляет сделки через заданное число баров так, чтобы синхронизироваться с оригинальной MQL-реализацией.

Поддерживаются как длинные, так и короткие позиции. Пользователь может управлять разрешением на открытие/закрытие сделок, расстояниями до стоп‑приказов в шагах цены, временем удержания позиции и окнами расчёта индикатора. Дополнительно реализован принудительный выход по времени, аналогичный циклу, который в исходном эксперте закрывал сделки по истечении nTime минут.

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

  1. Построение диапазона

    • Параметры RangeStartTime и RangeEndTime задают часовое окно, внутри которого вычисляется опорный диапазон.
    • Для каждого завершённого дня стратегия запоминает максимум и минимум внутри этого окна. Если время начала больше времени окончания, диапазон автоматически переносится через полночь, как и в MQL-индикаторе.
    • Пока новый диапазон не сформирован, используется последняя завершённая величина.
  2. Определение пробоев

    • Каждая закрытая свеча сравнивается с текущим диапазоном.
    • Свечи, закрывшиеся выше верхней границы, получают цветовой код 2 или 3, свечи ниже нижней границы — код 0 или 1, а свечи внутри диапазона — код 4 (нет сигнала).
    • Параметр SignalBar задаёт смещение: анализируется свеча, которая находится на SignalBar + 1 бар назад, при этом более новая свеча (смещение SignalBar) не должна повторять тот же цвет. Такой подход воспроизводит задержку, используемую экспертом перед выставлением ордеров.
  3. Открытие позиций

    • Покупка: допускается, если AllowBuyEntry = true, на сигнале обнаружен цвет 2 или 3, а следующая свеча его не повторяет.
    • Продажа: допускается, если AllowSellEntry = true, на сигнале обнаружен цвет 0 или 1, а следующая свеча его не повторяет.
    • При наличии встречной позиции её объём добавляется к новому рыночному ордеру, что позволяет мгновенно перевернуть позицию, как это делали функции из TradeAlgorithms.mqh.
  4. Закрытие позиций

    • Обратный сигнал: если AllowBuyExit = true, появление цвета 0 или 1 закрывает длинную позицию; если AllowSellExit = true, цвета 2 или 3 закрывают короткую позицию.
    • Выход по времени: при UseTimeExit = true позиции ликвидируются по истечении ExitAfterMinutes минут удержания, что соответствует оригинальному ограничению nTime.
    • Стоп‑приказы: параметры StopLossPoints и TakeProfitPoints задаются в шагах цены; значение 0 отключает соответствующую защиту.
  5. Управление рисками

    • Все заявки используют объём OrderVolume. При развороте позиция закрывается и открывается заново с учётом текущего размера.
    • Метод StartProtection регистрирует OCO‑стопы и тейк‑профиты сразу после запуска стратегии.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Базовый объём ордера. 0.1
AllowBuyEntry Разрешить вход в лонг при пробое вверх. true
AllowSellEntry Разрешить вход в шорт при пробое вниз. true
AllowBuyExit Разрешить закрывать лонг при пробое вниз. true
AllowSellExit Разрешить закрывать шорт при пробое вверх. true
UseTimeExit Включить выход по времени. true
ExitAfterMinutes Максимальное время удержания в минутах. 1500
StopLossPoints Стоп‑лосс в шагах цены (0 = выключен). 1000
TakeProfitPoints Тейк‑профит в шагах цены (0 = выключен). 2000
SignalBar Смещение по барам при анализе индикатора (аналог MQL). 1
RangeLookbackDays Максимальное количество прошлых сессий, в которых ищется завершённый диапазон; 0 — только текущий. 1
RangeStartTime Время начала окна для диапазона. 02:00
RangeEndTime Время окончания окна для диапазона. 07:00
CandleType Тип/таймфрейм свечей для расчётов. 30 минут

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

  • Используется подписка SubscribeCandles и обработчик WhenNew, поэтому решения принимаются только по завершённым свечам, как и в исходном эксперте.
  • Диапазон хранится в компактных структурах, а максимум/минимум вычисляются вручную без LINQ, что соответствует требованиям проекта.
  • Выход по времени запоминает момент открытия длинной и короткой позиции по отдельности, полностью повторяя MQL-логику перебора открытых сделок.
  • OrderVolume синхронизируется с базовым свойством Strategy.Volume, поэтому заданный объём корректно отображается в интерфейсе.
  • Все комментарии в коде оставлены на английском языке, что упрощает дальнейшую поддержку и адаптацию.

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

  • Убедитесь, что поставщик данных передаёт свечи того же таймфрейма, что указан в CandleType, иначе сигналы пробоя будут смещены.
  • При работе с инструментами, имеющими сложную сессию, измените RangeStartTime/RangeEndTime, чтобы диапазон формировался в релевантный период накопления.
  • Для инструментов с нестандартным шагом цены проверяйте реальные уровни стоп‑приказов в журнале заявок или на графике, чтобы убедиться в корректности пересчёта точек.
  • При переходе на более быстрые таймфреймы сократите ExitAfterMinutes, чтобы ограничить время удержания позиции.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AnyRange Cloud Tail System TM Plus strategy. Uses Highest/Lowest channel midline crossover.
/// </summary>
public class AnyRangeCloudTailSystemTmPlusStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;

	private decimal? _prevMid;

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

	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	public AnyRangeCloudTailSystemTmPlusStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Channel lookback period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMid = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevMid = null;

		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, highest);
			DrawIndicator(area, lowest);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var mid = (high + low) / 2m;
		var close = candle.ClosePrice;

		if (_prevMid == null)
		{
			_prevMid = mid;
			return;
		}

		// Close crosses above midline → buy
		if (close > mid && candle.OpenPrice <= _prevMid.Value && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Close crosses below midline → sell
		else if (close < mid && candle.OpenPrice >= _prevMid.Value && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMid = mid;
	}
}