Открыть на GitHub

Стратегия Color XMUV с торговым окном

Стратегия переносит эксперт Exp_ColorXMUV_Tm из MetaTrader в StockSharp, повторяя построение линии Color XMUV и фильтр по времени, но используя высокоуровневый API StockSharp для управления сделками. Цвет линии определяет направление: переход в бирюзовый цвет (рост) управляет длинными позициями, переход в пурпурный (падение) — короткими.

Логика работы

  • Для каждого завершённого бара рассчитывается составная цена, как в исходном MQL: (High + Close)/2 для бычьей свечи, (Low + Close)/2 для медвежьей и Close для дожи.
  • Составная цена подаётся в выбранный метод сглаживания. Встроенные в StockSharp индикаторы обеспечивают SMA, EMA, SMMA/RMA, LWMA и Jurik. Экзотические варианты (T3, VIDYA, ParMA) заменены на EMA из-за отсутствия прямых аналогов. Параметр Phase сохранён ради совместимости, даже если конкретный индикатор его игнорирует.
  • Цвет линии восстанавливается сравнением текущего сглаженного значения с предыдущим: рост — бычий цвет, падение — медвежий, без изменения — нейтральный.
  • SignalBar задаёт количество полностью закрытых баров, которые нужно пропустить перед обработкой сигнала (по умолчанию 1 — анализируется бар перед последним, что соответствует логике исходного советника).
  • Бычий разворот (предыдущий цвет не был бычьим, текущий стал бычьим) закрывает открытую короткую позицию и при необходимости открывает/добавляет длинную. Медвежий разворот действует зеркально.
  • Фильтр по времени полностью повторяет MQL-версию: вне торгового окна позиции закрываются немедленно, новые сигналы игнорируются. Если время начала больше времени окончания, окно считается ночным и проходит через полночь.
  • StopLossPoints и TakeProfitPoints трактуются как расстояние в пунктах и переводятся в абсолютную цену через шаг цены инструмента. Затем защита регистрируется через StartProtection, чтобы по возможности передать сопровождение на уровень торгового движка StockSharp.

Управление рисками и позициями

  • OrderVolume задаёт базовый объём рыночных заявок. При развороте стратегия добавляет модуль текущей позиции, чтобы одной заявкой закрыть старую и открыть новую позицию.
  • При значении параметров стоп-лосса или тейк-профита, равном нулю, соответствующий защитный уровень отключается. Пересчёт из пунктов выполняется автоматически.
  • Закрытие по сигналу цвета учитывает флаги EnableBuyExits и EnableSellExits, что позволяет независимо управлять выходом из длинных и коротких позиций.

Параметры

  • Candle Type – тип свечей для расчёта (по умолчанию 4-часовые).
  • Order Volume – базовый объём рыночных сделок.
  • Enable Long Entries / Enable Short Entries – разрешение на открытие длинных/коротких позиций при смене цвета.
  • Close Longs / Close Shorts – автоматическое закрытие позиций при противоположном сигнале.
  • Use Time Filter – включение фильтра по времени торговли.
  • Start Hour / Start Minute / End Hour / End Minute – границы торговой сессии; если начало больше конца, окно считается ночным и продолжается через полночь.
  • Smoothing Method – метод сглаживания линии Color XMUV. Для отсутствующих в StockSharp реализаций используется EMA с отметкой в документации.
  • Length – длина окна сглаживания (положительное значение).
  • Phase – дополнительный параметр фазы, сохранённый для совместимости с исходным советником.
  • Signal Bar – количество завершённых баров, на которое задерживается обработка сигналов; 0 означает работу по последнему бару.
  • Stop Loss (pts) / Take Profit (pts) – расстояние стоп-лосса и тейк-профита в пунктах; ноль отключает уровень.

Примечания

  • Исходный индикатор опирался на внешние библиотеки сглаживания. При отсутствии аналога (ParMA, VIDYA, T3) стратегия применяет EMA. Об этом следует уведомлять пользователей.
  • Стратегия хранит только минимальную историю цветов, необходимую для SignalBar, что соответствует требованию репозитория не создавать избыточные коллекции данных.
  • Все комментарии в коде выполнены на английском языке, как было оговорено.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy that replicates the Color XMUV expert advisor with a trading session filter.
/// </summary>
public class ColorXmuvTimeStrategy : Strategy
{
	private readonly StrategyParam<int> _maxColorHistory;

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _enableBuyEntries;
	private readonly StrategyParam<bool> _enableSellEntries;
	private readonly StrategyParam<bool> _enableBuyExits;
	private readonly StrategyParam<bool> _enableSellExits;
	private readonly StrategyParam<bool> _useTimeFilter;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _startMinute;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _endMinute;
	private readonly StrategyParam<SmoothMethods> _xmaMethod;
	private readonly StrategyParam<int> _xLength;
	private readonly StrategyParam<int> _xPhase;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private readonly List<TrendColors> _colorHistory = new();

	private IIndicator _xma = null!;
	private decimal? _previousXmuv;

	/// <summary>
	/// Type of candles for the indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Volume for new market orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool EnableBuyEntries
	{
		get => _enableBuyEntries.Value;
		set => _enableBuyEntries.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool EnableSellEntries
	{
		get => _enableSellEntries.Value;
		set => _enableSellEntries.Value = value;
	}

	/// <summary>
	/// Allow closing long positions on bearish signals.
	/// </summary>
	public bool EnableBuyExits
	{
		get => _enableBuyExits.Value;
		set => _enableBuyExits.Value = value;
	}

	/// <summary>
	/// Allow closing short positions on bullish signals.
	/// </summary>
	public bool EnableSellExits
	{
		get => _enableSellExits.Value;
		set => _enableSellExits.Value = value;
	}

	/// <summary>
	/// Enable restriction of trading by time window.
	/// </summary>
	public bool UseTimeFilter
	{
		get => _useTimeFilter.Value;
		set => _useTimeFilter.Value = value;
	}

	/// <summary>
	/// Start hour for the trading session (00-23).
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Start minute for the trading session (00-59).
	/// </summary>
	public int StartMinute
	{
		get => _startMinute.Value;
		set => _startMinute.Value = value;
	}

	/// <summary>
	/// End hour for the trading session (00-23).
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// End minute for the trading session (00-59).
	/// </summary>
	public int EndMinute
	{
		get => _endMinute.Value;
		set => _endMinute.Value = value;
	}

	/// <summary>
	/// Smoothing method used by the Color XMUV line.
	/// </summary>
	public SmoothMethods XmaMethod
	{
		get => _xmaMethod.Value;
		set => _xmaMethod.Value = value;
	}

	/// <summary>
	/// Length of the smoothing window.
	/// </summary>
	public int XLength
	{
		get => _xLength.Value;
		set => _xLength.Value = value;
	}

	/// <summary>
	/// Auxiliary phase parameter retained from the original expert advisor.
	/// </summary>
	public int XPhase
	{
		get => _xPhase.Value;
		set => _xPhase.Value = value;
	}

	/// <summary>
	/// Number of completed bars to delay signal confirmation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Maximum number of stored trend color values.
	/// </summary>
	public int MaxColorHistory
	{
		get => _maxColorHistory.Value;
		set
		{
			_maxColorHistory.Value = value;
			TrimColorHistory();
		}
	}

	/// <summary>
	/// Stop loss size in points (converted to absolute price using the instrument price step).
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit size in points (converted to absolute price using the instrument price step).
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="ColorXmuvTimeStrategy"/>.
	/// </summary>
	public ColorXmuvTimeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Source candles for the Color XMUV line", "General");

		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Order Volume", "Size of market orders", "Trading");

		_enableBuyEntries = Param(nameof(EnableBuyEntries), true)
		.SetDisplay("Enable Long Entries", "Allow entering long positions", "Trading");

		_enableSellEntries = Param(nameof(EnableSellEntries), true)
		.SetDisplay("Enable Short Entries", "Allow entering short positions", "Trading");

		_enableBuyExits = Param(nameof(EnableBuyExits), true)
		.SetDisplay("Close Longs", "Close long positions on bearish flips", "Trading");

		_enableSellExits = Param(nameof(EnableSellExits), true)
		.SetDisplay("Close Shorts", "Close short positions on bullish flips", "Trading");

		_useTimeFilter = Param(nameof(UseTimeFilter), false)
		.SetDisplay("Use Time Filter", "Restrict trading to the specified session", "Time Filter");

		_startHour = Param(nameof(StartHour), 0)
		.SetRange(0, 23)
		.SetDisplay("Start Hour", "Trading session start hour", "Time Filter");

		_startMinute = Param(nameof(StartMinute), 0)
		.SetRange(0, 59)
		.SetDisplay("Start Minute", "Trading session start minute", "Time Filter");

		_endHour = Param(nameof(EndHour), 23)
		.SetRange(0, 23)
		.SetDisplay("End Hour", "Trading session end hour", "Time Filter");

		_endMinute = Param(nameof(EndMinute), 59)
		.SetRange(0, 59)
		.SetDisplay("End Minute", "Trading session end minute", "Time Filter");

		_xmaMethod = Param(nameof(XmaMethod), SmoothMethods.Sma)
		.SetDisplay("Smoothing Method", "Algorithm for the Color XMUV line", "Indicator");

		_xLength = Param(nameof(XLength), 14)
		.SetGreaterThanZero()
		.SetDisplay("Length", "Smoothing length", "Indicator");

		_xPhase = Param(nameof(XPhase), 15)
		.SetDisplay("Phase", "Additional phase parameter for exotic smoothers", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetRange(0, 10)
		.SetDisplay("Signal Bar", "Number of completed bars to delay signals", "Indicator");

		_maxColorHistory = Param(nameof(MaxColorHistory), 64)
		.SetRange(2, 512)
		.SetDisplay("Max Color History", "Maximum stored trend color values", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 0m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pts)", "Stop loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pts)", "Take profit distance in points", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_colorHistory.Clear();
		_previousXmuv = null;
		_xma = null!;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_xma = CreateMovingAverage(XmaMethod, XLength);

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

		StartProtection(CreateTakeProfitUnit(), CreateStopLossUnit());
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		{
			return;
		}

		var price = CalculateSignalPrice(candle);
		var indicatorValue = _xma.Process(new DecimalIndicatorValue(_xma, price, candle.OpenTime) { IsFinal = true });

		if (!_xma.IsFormed)
		{
			_previousXmuv = indicatorValue.ToDecimal();
			return;
		}

		var xmuv = indicatorValue.ToDecimal();
		var color = DetermineColor(xmuv);
		StoreColor(color);
		_previousXmuv = xmuv;

		if (!TryGetSignalColors(SignalBar, out var currentColor, out var previousColor))
		{
			return;
		}

		// No bound indicators via .Bind, always allow.

		var inSession = !UseTimeFilter || IsInsideSession(candle.CloseTime);

		if (!inSession)
		{
			ForceExitIfNeeded();
			return;
		}

		var bullishFlip = currentColor == TrendColors.Bullish && previousColor != TrendColors.Bullish;
		var bearishFlip = currentColor == TrendColors.Bearish && previousColor != TrendColors.Bearish;

		if (bullishFlip)
		{
			if (Position < 0 && EnableSellExits)
			{
				// Close short position
				BuyMarket();
			}
			else if (Position == 0 && EnableBuyEntries)
			{
				// Open new long
				BuyMarket();
			}
		}
		else if (bearishFlip)
		{
			if (Position > 0 && EnableBuyExits)
			{
				// Close long position
				SellMarket();
			}
			else if (Position == 0 && EnableSellEntries)
			{
				// Open new short
				SellMarket();
			}
		}
	}

	private decimal CalculateSignalPrice(ICandleMessage candle)
	{
		if (candle.ClosePrice < candle.OpenPrice)
		{
			return (candle.LowPrice + candle.ClosePrice) / 2m;
		}

		if (candle.ClosePrice > candle.OpenPrice)
		{
			return (candle.HighPrice + candle.ClosePrice) / 2m;
		}

		return candle.ClosePrice;
	}

	private TrendColors DetermineColor(decimal currentXmuv)
	{
		if (_previousXmuv is not decimal previous)
		{
			return TrendColors.Neutral;
		}

		if (currentXmuv > previous)
		{
			return TrendColors.Bullish;
		}

		if (currentXmuv < previous)
		{
			return TrendColors.Bearish;
		}

		return TrendColors.Neutral;
	}

	private void StoreColor(TrendColors color)
	{
		var maxSize = Math.Clamp(SignalBar + 2, 2, MaxColorHistory);
		_colorHistory.Add(color);

		if (_colorHistory.Count > maxSize)
		{
			_colorHistory.RemoveAt(0);
		}
	}

	private void TrimColorHistory()
	{
		var limit = Math.Max(2, MaxColorHistory);

		while (_colorHistory.Count > limit)
		{
			_colorHistory.RemoveAt(0);
		}
	}

	private bool TryGetSignalColors(int offset, out TrendColors current, out TrendColors previous)
	{
		current = TrendColors.Neutral;
		previous = TrendColors.Neutral;

		var count = _colorHistory.Count;
		if (count <= offset)
		{
			return false;
		}

		var index = count - 1 - offset;
		if (index <= 0)
		{
			return false;
		}

		current = _colorHistory[index];
		previous = _colorHistory[index - 1];
		return true;
	}

	private bool IsInsideSession(DateTimeOffset time)
	{
		var start = new TimeSpan(StartHour, StartMinute, 0);
		var end = new TimeSpan(EndHour, EndMinute, 0);
		var moment = time.TimeOfDay;

		if (start == end)
		{
			return moment >= start && moment < end;
		}

		if (start < end)
		{
			return moment >= start && moment <= end;
		}

		return moment >= start || moment <= end;
	}

	private void ForceExitIfNeeded()
	{
		if (Position > 0 && EnableBuyExits)
		{
			SellMarket();
		}
		else if (Position < 0 && EnableSellExits)
		{
			BuyMarket();
		}
	}

	private Unit CreateStopLossUnit()
	{
		if (StopLossPoints <= 0 || Security?.PriceStep is not decimal step || step <= 0)
		{
			return default;
		}

		return new Unit(step * StopLossPoints, UnitTypes.Absolute);
	}

	private Unit CreateTakeProfitUnit()
	{
		if (TakeProfitPoints <= 0 || Security?.PriceStep is not decimal step || step <= 0)
		{
			return default;
		}

		return new Unit(step * TakeProfitPoints, UnitTypes.Absolute);
	}

	private IIndicator CreateMovingAverage(SmoothMethods method, int length)
	{
		return method switch
		{
			SmoothMethods.Sma => new SMA { Length = length },
			SmoothMethods.Ema => new EMA { Length = length },
			SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothMethods.Lwma => new WeightedMovingAverage { Length = length },
			SmoothMethods.Jjma => new EMA { Length = length },
			SmoothMethods.Jurx => new EMA { Length = length },
			SmoothMethods.Parma => new WeightedMovingAverage { Length = length },
			SmoothMethods.T3 => new EMA { Length = length },
			SmoothMethods.Vidya => new EMA { Length = length },
			SmoothMethods.Ama => new KaufmanAdaptiveMovingAverage { Length = length },
			_ => new EMA { Length = length },
		};
	}

	private enum TrendColors
	{
		Bearish = 0,
		Neutral = 1,
		Bullish = 2
	}

	/// <summary>
	/// Smoothing methods supported by the Color XMUV indicator.
	/// </summary>
	public enum SmoothMethods
	{
		Sma,
		Ema,
		Smma,
		Lwma,
		Jjma,
		Jurx,
		Parma,
		T3,
		Vidya,
		Ama
	}
}