Ver no GitHub

Color XMUV Time Strategy

This strategy ports the MetaTrader expert advisor Exp_ColorXMUV_Tm to StockSharp. It recreates the original Color XMUV smoothed line and the time-window filter while using StockSharp's high-level trading API. The strategy follows the color of the smoothed line: a transition to teal (rising) triggers long management while a transition to magenta (falling) drives short management.

Core Logic

  • For every finished candle the strategy builds a composite price similar to the MQL version ((H + Close)/2 on bullish bars, (L + Close)/2 on bearish bars, or Close for doji bars).
  • The composite price is passed through the requested smoothing method. Common methods (SMA, EMA, SMMA/RMA, LWMA and Jurik) are implemented with StockSharp indicators. Exotic options such as T3 or VIDYA fall back to an EMA because StockSharp does not expose direct equivalents. The phase parameter is kept for configuration parity even when the underlying indicator ignores it.
  • The Color XMUV "color" is reconstructed by comparing the latest smoothed value with the previous one. Rising slopes are mapped to bullish color, falling slopes to bearish color and unchanged values to neutral color.
  • SignalBar defines how many fully-completed bars to look back when evaluating a signal (e.g. the default value of 1 means the logic waits for confirmation on the bar before the most recent one).
  • A bullish flip (previous color not bullish, current color bullish) closes any short position and optionally opens or adds to a long position. A bearish flip performs the symmetric actions for short trades.
  • The time filter mimics the original EA: outside the trading window the strategy immediately closes existing positions and ignores new entries. The filter supports overnight sessions (start time after end time).
  • StopLossPoints and TakeProfitPoints are translated into absolute distances using the instrument's price step and are registered with StartProtection so that StockSharp manages exits server-side where possible.

Risk and Position Management

  • Orders are sized with the OrderVolume parameter. When flipping direction the strategy adds the absolute value of the current position so that the reversal closes the old trade and opens a new one in a single transaction.
  • Optional stop-loss and take-profit are converted from point values to absolute price distances. Set either parameter to zero to disable the respective protection layer.
  • Position exits triggered by the color flip respect the EnableBuyExits and EnableSellExits toggles, allowing independent control of long and short management.

Parameters

  • Candle Type – Candle series used for calculations (defaults to 4-hour candles).
  • Order Volume – Base market order size.
  • Enable Long Entries / Enable Short Entries – Allow opening positions on bullish/bearish flips.
  • Close Longs / Close Shorts – Enable automatic exits on opposite color transitions.
  • Use Time Filter – Restrict trading to the configured session.
  • Start Hour / Start Minute / End Hour / End Minute – Trading session bounds. When the start is later than the end the session wraps over midnight.
  • Smoothing Method – Moving-average algorithm for the Color XMUV line. Options without a native StockSharp implementation default to EMA and are documented above.
  • Length – Smoothing length (must be positive).
  • Phase – Auxiliary phase parameter retained for configuration compatibility.
  • Signal Bar – Number of completed bars to delay the signal check. Set to zero to act on the most recent closed bar.
  • Stop Loss (pts) / Take Profit (pts) – Offsets expressed in price points; zero disables the respective layer.

Notes

  • The MQL expert relies on external smoothing libraries. When such smoothing modes are unavailable in StockSharp (ParMA, VIDYA, T3) the implementation substitutes an EMA. Document these fallbacks when sharing the strategy with users.
  • The strategy stores only the minimal color history required by SignalBar, complying with the repository guideline that discourages building custom data caches.
  • All comments are provided in English as requested.
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
	}
}