Auf GitHub ansehen

Color XMUV Zeit-Strategie

Diese Strategie portiert den MetaTrader Expert Advisor Exp_ColorXMUV_Tm zu StockSharp. Sie recreiert die ursprüngliche Color XMUV geglättete Linie und den Zeitfensterfilter, während die High-Level-Handels-API von StockSharp verwendet wird. Die Strategie folgt der Farbe der geglätteten Linie: Ein Übergang zu Blaugrün (steigend) löst das Long-Management aus, während ein Übergang zu Magenta (fallend) das Short-Management antreibt.

Kernlogik

  • Für jede abgeschlossene Kerze baut die Strategie einen zusammengesetzten Preis ähnlich der MQL-Version auf ((H + Close)/2 bei bullischen Kerzen, (L + Close)/2 bei bärischen Kerzen oder Close für Doji-Kerzen).
  • Der zusammengesetzte Preis wird durch die angeforderte Glättungsmethode geleitet. Gängige Methoden (SMA, EMA, SMMA/RMA, LWMA und Jurik) sind mit StockSharp-Indikatoren implementiert. Exotische Optionen wie T3 oder VIDYA fallen auf eine EMA zurück, da StockSharp keine direkten Äquivalente bietet. Der Phase-Parameter wird für Konfigurationskompatibilität beibehalten, auch wenn der zugrunde liegende Indikator ihn ignoriert.
  • Die Color XMUV "Farbe" wird durch Vergleich des neuesten geglätteten Werts mit dem vorherigen rekonstruiert. Steigende Steigungen werden der bullischen Farbe zugeordnet, fallende Steigungen der bärischen Farbe und unveränderte Werte der neutralen Farbe.
  • SignalBar definiert, wie viele vollständig abgeschlossene Balken beim Auswerten eines Signals zurückgeschaut wird (z.B. bedeutet der Standardwert 1, dass die Logik auf Bestätigung auf dem Balken vor dem jüngsten wartet).
  • Ein bullischer Flip (vorherige Farbe nicht bullisch, aktuelle Farbe bullisch) schließt jede Short-Position und öffnet oder ergänzt optional eine Long-Position. Ein bärischer Flip führt die symmetrischen Aktionen für Short-Trades aus.
  • Der Zeitfilter imitiert den ursprünglichen EA: Außerhalb des Handelsfensters schließt die Strategie sofort bestehende Positionen und ignoriert neue Einstiege. Der Filter unterstützt Übernacht-Sitzungen (Startzeit nach Endzeit).
  • StopLossPoints und TakeProfitPoints werden unter Verwendung des Preisschritts des Instruments in absolute Abstände umgerechnet und mit StartProtection registriert, damit StockSharp Ausstiege serverseitig verwalten kann, wo möglich.

Risiko- und Positionsverwaltung

  • Aufträge werden mit dem Parameter OrderVolume dimensioniert. Beim Richtungswechsel addiert die Strategie den Absolutwert der aktuellen Position, sodass die Umkehrung den alten Trade schließt und einen neuen in einer einzigen Transaktion eröffnet.
  • Optionaler Stop-Loss und Take-Profit werden von Punktwerten in absolute Preisabstände umgerechnet. Setzen Sie einen der Parameter auf null, um die jeweilige Schutzschicht zu deaktivieren.
  • Positionsausstiege, die durch den Farbflip ausgelöst werden, respektieren die Schalter EnableBuyExits und EnableSellExits, was eine unabhängige Steuerung des Long- und Short-Managements ermöglicht.

Parameter

  • Candle Type – Für Berechnungen verwendete Kerzenserie (Standard 4-Stunden-Kerzen).
  • Order Volume – Basisgröße der Marktorder.
  • Enable Long Entries / Enable Short Entries – Öffnung von Positionen bei bullischen/bärischen Flips erlauben.
  • Close Longs / Close Shorts – Automatische Ausstiege bei entgegengesetzten Farbübergängen aktivieren.
  • Use Time Filter – Handel auf die konfigurierte Sitzung beschränken.
  • Start Hour / Start Minute / End Hour / End Minute – Handelssitzungsgrenzen. Wenn der Start nach dem Ende liegt, reicht die Sitzung über Mitternacht.
  • Smoothing Method – Gleitender-Durchschnitt-Algorithmus für die Color XMUV-Linie. Optionen ohne native StockSharp-Implementierung werden durch EMA ersetzt und sind oben dokumentiert.
  • Length – Glättungslänge (muss positiv sein).
  • Phase – Hilfsphaseparameter für Konfigurationskompatibilität beibehalten.
  • Signal Bar – Anzahl abgeschlossener Balken zum Verzögern der Signalprüfung. Auf null setzen, um auf dem jüngsten geschlossenen Balken zu agieren.
  • Stop Loss (pts) / Take Profit (pts) – Offsets in Preispunkten ausgedrückt; null deaktiviert die jeweilige Schicht.

Hinweise

  • Der MQL-Experte stützte sich auf externe Glättungsbibliotheken. Wenn solche Glättungsmodi in StockSharp nicht verfügbar sind (ParMA, VIDYA, T3) substituiert die Implementierung eine EMA. Dokumentieren Sie diese Fallbacks, wenn Sie die Strategie mit Benutzern teilen.
  • Die Strategie speichert nur den minimalen Farbverlauf, der von SignalBar benötigt wird, und hält sich damit an die Repository-Richtlinie, die vom Aufbau benutzerdefinierter Datencaches abrät.
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
	}
}