Auf GitHub ansehen

Exp XPeriodCandle-Strategie

Diese Strategie ist ein StockSharp-Port des MQL5-Expertenberaters Exp_XPeriodCandle. Sie rekonstruiert den benutzerdefinierten XPeriodCandle-Indikator mit High-Level-API-Komponenten und verwendet Kerzenfarbübergänge zum Öffnen und Schließen von Positionen.

Konzept

  • Glättung von Eröffnung, Hoch, Tief und Schluss jeder abgeschlossenen Kerze mit einer konfigurierbaren gleitenden Durchschnittsnäherung.
  • Verfolgung der resultierenden "Kerzenfarbe" (bullisch, wenn der geglättete Schluss über der geglätteten Eröffnung liegt, sonst bärisch).
  • Verwendung der Farbe der letzten zwei abgeschlossenen Kerzen (konfigurierbarer Versatz) zur Erkennung von Umkehrungen und Ausgabe von Handelssignalen.
  • Optionales Schließen von Gegenpositionen bei Erscheinen eines neuen Signals und Anwendung von schützenden Stop-Loss/Take-Profit-Niveaus, ausgedrückt in Preispunkten.

Implementierungsdetails

  • Direkt unterstützte Glättungstypen: Simple, Exponential, Smoothed (RMA) und Linear Weighted. Alle anderen Optionen werden mit einem exponentiellen Glätter approximiert, da StockSharp keine direkten Äquivalente zu JJMA/JurX/Parabolic/T3/VIDYA/AMA enthält. Im Code dokumentiert, um das Verhalten transparent zu halten.
  • Gleitende Warteschlangen speichern die letzten Period geglätteten Hochs und Tiefs, um den Preisbereich konsistent mit dem ursprünglichen Indikator zu halten.
  • Die Strategie wartet, bis genug Historie verfügbar ist, bevor BuyMarket/SellMarket aufgerufen wird, und markiert sich als geformt, um mit StockSharp-Backtesting-Filtern zu arbeiten.
  • Optionale Slippage-, Stop-Loss- und Take-Profit-Konvertierungen basieren auf dem Kursschrittpreis des Wertpapiers. Wenn der Schritt unbekannt ist, werden die rohen Punktwerte verwendet.

Parameter

Parameter Beschreibung
CandleType Zeitrahmen der verarbeiteten Kerzen.
Period Tiefe des Glättungsfensters (entspricht dem Indikatorperiodenwert).
SmoothingMethods Gleitende Durchschnittsnäherung für alle OHLC-Reihen. Nicht unterstützte Methoden fallen auf EMA zurück.
SmoothingLength Längenparameter für den Glätter.
SmoothingPhase Zusätzliche Phaseneingabe (für Vollständigkeit beibehalten; nur in der ursprünglichen MQL-JJMA-Familie aktiv).
SignalBar Welche abgeschlossene Kerze ausgewertet werden soll (1 = vorherige Kerze, repliziert den MQL-Expertenstandard).
EnableLongEntry / EnableShortEntry Öffnen von Positionen in der entsprechenden Richtung erlauben.
EnableLongExit / EnableShortExit Vorhandene Positionen schließen, wenn ein entgegengesetztes Signal erkannt wird.
StopLossPoints / TakeProfitPoints Schützende Ausstiege in Preispunkten. Auf null setzen zum Deaktivieren.
SlippagePoints Erlaubter Slippage in Preispunkten für Marktaufträge.

Handelsregeln

  1. Die letzte abgeschlossene Kerze glätten und ihre Farbe zum rollierenden Verlauf hinzufügen.
  2. Wenn SignalBar- und ältere Farben vorhanden sind:
    • Wenn die ältere Kerze bullisch war (Farbe < 1) und die neuere Kerze nicht bullisch ist (Farbe > 0), Long-Position eröffnen (wenn erlaubt) und optional Shorts schließen.
    • Wenn die ältere Kerze bärisch war (Farbe > 1) und die neuere Kerze nicht bärisch ist (Farbe < 2), Short-Position eröffnen (wenn erlaubt) und optional Longs schließen.
  3. Die Positionsgröße folgt der Volume-Einstellung der Strategie; gegenläufiges Engagement wird vor Umkehrung aufgelöst.
  4. Das Risikomanagement wird von StartProtection mit den angegebenen Punktabständen übernommen.

Hinweise

  • Der ursprüngliche Experte verwendet das proprietäre SmoothAlgorithms.mqh. Da StockSharp keine direkten JJMA/JurX/T3-Implementierungen hat, approximiert die C#-Konvertierung diese Modi mit exponentieller Glättung. Dieses Verhalten ist in Code-Kommentaren und der README dokumentiert, damit Optimierer die Parameter bei Bedarf anpassen können.
  • Eingaben und Standardwerte spiegeln die MQL-Version wider und ermöglichen ähnliche Optimierungsbereiche.
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;

	public class ExpXPeriodCandleStrategy : Strategy
	{
		public enum SmoothingMethods
		{
			Simple,
			Exponential,
			Smoothed,
			LinearWeighted,
			JurikLike,
			JurxLike,
			ParabolicLike,
			TillsonT3Like,
			VidyaLike,
			AdaptiveLike
		}

		private readonly StrategyParam<DataType> _candleType;
		private readonly StrategyParam<int> _period;
		private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
		private readonly StrategyParam<int> _smoothingLength;
		private readonly StrategyParam<int> _smoothingPhase;
		private readonly StrategyParam<int> _signalBar;
		private readonly StrategyParam<bool> _enableLongEntry;
		private readonly StrategyParam<bool> _enableShortEntry;
		private readonly StrategyParam<bool> _enableLongExit;
		private readonly StrategyParam<bool> _enableShortExit;
		private readonly StrategyParam<int> _stopLossPoints;
		private readonly StrategyParam<int> _takeProfitPoints;
		private readonly StrategyParam<int> _slippagePoints;

		private Smoother _openSmoother;
		private Smoother _highSmoother;
		private Smoother _lowSmoother;
		private Smoother _closeSmoother;

		private readonly List<int> _colorHistory = new();
		private readonly Queue<decimal> _smoothedHighs = new();
		private readonly Queue<decimal> _smoothedLows = new();

		public ExpXPeriodCandleStrategy()
		{
			_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
				.SetDisplay("Candle Type", "Time frame used for calculations", "General");

			_period = Param(nameof(Period), 5)
				.SetGreaterThanZero()
				.SetDisplay("Smoothing Window", "Depth of the price smoothing window", "Indicator")
				;

			_smoothingMethod = Param(nameof(SmoothingMethods), SmoothingMethods.JurikLike)
				.SetDisplay("Smoothing Method", "Type of moving average approximation", "Indicator");

			_smoothingLength = Param(nameof(SmoothingLength), 3)
				.SetGreaterThanZero()
				.SetDisplay("Smoothing Length", "Length used by the smoother", "Indicator")
				;

			_smoothingPhase = Param(nameof(SmoothingPhase), 100)
				.SetDisplay("Smoothing Phase", "Phase parameter for adaptive smoothers", "Indicator");

			_signalBar = Param(nameof(SignalBar), 1)
				.SetGreaterThanZero()
				.SetDisplay("Signal Shift", "Which completed candle to evaluate", "Trading");

			_enableLongEntry = Param(nameof(EnableLongEntry), true)
				.SetDisplay("Enable Long Entry", "Allow opening buy positions", "Trading");

			_enableShortEntry = Param(nameof(EnableShortEntry), true)
				.SetDisplay("Enable Short Entry", "Allow opening sell positions", "Trading");

			_enableLongExit = Param(nameof(EnableLongExit), true)
				.SetDisplay("Close Longs On Opposite", "Close long positions on opposite signals", "Trading");

			_enableShortExit = Param(nameof(EnableShortExit), true)
				.SetDisplay("Close Shorts On Opposite", "Close short positions on opposite signals", "Trading");

			_stopLossPoints = Param(nameof(StopLossPoints), 1000)
				.SetNotNegative()
				.SetDisplay("Stop Loss (pts)", "Protective stop loss in price points", "Risk");

			_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
				.SetNotNegative()
				.SetDisplay("Take Profit (pts)", "Protective take profit in price points", "Risk");

			_slippagePoints = Param(nameof(SlippagePoints), 10)
				.SetNotNegative()
				.SetDisplay("Slippage (pts)", "Allowed slippage in price points", "Trading");
		}

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

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

		public SmoothingMethods Smoothing
		{
			get => _smoothingMethod.Value;
			set => _smoothingMethod.Value = value;
		}

		public int SmoothingLength
		{
			get => _smoothingLength.Value;
			set => _smoothingLength.Value = value;
		}

		public int SmoothingPhase
		{
			get => _smoothingPhase.Value;
			set => _smoothingPhase.Value = value;
		}

		public int SignalBar
		{
			get => _signalBar.Value;
			set => _signalBar.Value = value;
		}

		public bool EnableLongEntry
		{
			get => _enableLongEntry.Value;
			set => _enableLongEntry.Value = value;
		}

		public bool EnableShortEntry
		{
			get => _enableShortEntry.Value;
			set => _enableShortEntry.Value = value;
		}

		public bool EnableLongExit
		{
			get => _enableLongExit.Value;
			set => _enableLongExit.Value = value;
		}

		public bool EnableShortExit
		{
			get => _enableShortExit.Value;
			set => _enableShortExit.Value = value;
		}

		public int StopLossPoints
		{
			get => _stopLossPoints.Value;
			set => _stopLossPoints.Value = value;
		}

		public int TakeProfitPoints
		{
			get => _takeProfitPoints.Value;
			set => _takeProfitPoints.Value = value;
		}

		public int SlippagePoints
		{
			get => _slippagePoints.Value;
			set => _slippagePoints.Value = value;
		}

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

		protected override void OnReseted()
		{
			base.OnReseted();

			_colorHistory.Clear();
			_smoothedHighs.Clear();
			_smoothedLows.Clear();
			_openSmoother = null;
			_highSmoother = null;
			_lowSmoother = null;
			_closeSmoother = null;
		}

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

			_openSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
			_highSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
			_lowSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
			_closeSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);

			_colorHistory.Clear();
			_smoothedHighs.Clear();
			_smoothedLows.Clear();

			// Protection and slippage removed (forbidden APIs)

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

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

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

			var openValue = _openSmoother?.Process(candle.OpenPrice);
			var highValue = _highSmoother?.Process(candle.HighPrice);
			var lowValue = _lowSmoother?.Process(candle.LowPrice);
			var closeValue = _closeSmoother?.Process(candle.ClosePrice);

			if (openValue is null || highValue is null || lowValue is null || closeValue is null)
				return;

			UpdateQueue(_smoothedHighs, highValue.Value, Period);
			UpdateQueue(_smoothedLows, lowValue.Value, Period);

			if (_smoothedHighs.Count < Period || _smoothedLows.Count < Period)
				return;


			var color = openValue.Value <= closeValue.Value ? 0 : 2;
			_colorHistory.Add(color);
			var maxHistory = Math.Max(Period * 4, SignalBar + 4);
			if (_colorHistory.Count > maxHistory)
				_colorHistory.RemoveAt(0);

			if (_colorHistory.Count < SignalBar + 1)
				return;

			if (!IsFormedAndOnlineAndAllowTrading())
				return;

			if (_colorHistory.Count <= SignalBar)
				return;

			var index0 = _colorHistory.Count - SignalBar;
			if (index0 >= _colorHistory.Count)
				index0 = _colorHistory.Count - 1;
			var index1 = index0 - 1;
			if (index1 < 0)
				return;

			var value0 = _colorHistory[index0];
			var value1 = _colorHistory[index1];

			var baseLongCondition = value1 < 1;
			var baseShortCondition = value1 > 1;
			var openLong = EnableLongEntry && baseLongCondition && value0 > 0;
			var openShort = EnableShortEntry && baseShortCondition && value0 < 2;
			var closeShort = EnableShortExit && baseLongCondition;
			var closeLong = EnableLongExit && baseShortCondition;

			if (closeLong && Position > 0)
				SellMarket(Position);

			if (closeShort && Position < 0)
				BuyMarket(-Position);

			if (openLong && Position <= 0)
			{
				var volume = Volume + (Position < 0 ? -Position : 0m);
				BuyMarket(volume);
			}
			else if (openShort && Position >= 0)
			{
				var volume = Volume + (Position > 0 ? Position : 0m);
				SellMarket(volume);
			}
		}

	
		private static void UpdateQueue(Queue<decimal> queue, decimal value, int maxCount)
		{
			queue.Enqueue(value);
			if (queue.Count > maxCount)
				queue.Dequeue();
		}

		private static decimal GetMax(IEnumerable<decimal> source)
		{
			var max = decimal.MinValue;
			foreach (var value in source)
			{
				if (value > max)
					max = value;
			}
			return max;
		}

		private static decimal GetMin(IEnumerable<decimal> source)
		{
			var min = decimal.MaxValue;
			foreach (var value in source)
			{
				if (value < min)
					min = value;
			}
			return min;
		}

		private static Smoother CreateSmoother(SmoothingMethods method, int length, int phase)
		{
			switch (method)
			{
				case SmoothingMethods.Simple:
					return new SmaSmoother(length);
				case SmoothingMethods.Exponential:
					return new EmaSmoother(length);
				case SmoothingMethods.Smoothed:
					return new SmmaSmoother(length);
				case SmoothingMethods.LinearWeighted:
					return new LwmaSmoother(length);
				default:
					// Approximate advanced smoothing modes (JJMA, JurX, Parabolic, T3, VIDYA, AMA) with EMA.
					return new EmaSmoother(length);
			}
		}

		private abstract class Smoother
		{
			protected Smoother(int length)
			{
				Length = Math.Max(1, length);
			}

			protected int Length { get; }

			public abstract decimal? Process(decimal value);
		}

		private sealed class SmaSmoother : Smoother
		{
			private readonly Queue<decimal> _values = new();
			private decimal _sum;

			public SmaSmoother(int length)
				: base(length)
			{
			}

			public override decimal? Process(decimal value)
			{
				_values.Enqueue(value);
				_sum += value;

				if (_values.Count > Length)
				{
					_sum -= _values.Dequeue();
				}

				if (_values.Count < Length)
					return null;

				return _sum / _values.Count;
			}
		}

		private sealed class EmaSmoother : Smoother
		{
			private decimal? _ema;
			private readonly decimal _alpha;

			public EmaSmoother(int length)
				: base(length)
			{
				_alpha = 2m / (Length + 1m);
			}

			public override decimal? Process(decimal value)
			{
				if (_ema is null)
					_ema = value;
				else
					_ema += _alpha * (value - _ema.Value);

				return _ema;
			}
		}

		private sealed class SmmaSmoother : Smoother
		{
			private decimal? _smma;

			public SmmaSmoother(int length)
				: base(length)
			{
			}

			public override decimal? Process(decimal value)
			{
				if (_smma is null)
					_smma = value;
				else
					_smma = ((_smma.Value * (Length - 1)) + value) / Length;

				return _smma;
			}
		}

		private sealed class LwmaSmoother : Smoother
		{
			private readonly Queue<decimal> _values = new();

			public LwmaSmoother(int length)
				: base(length)
			{
			}

			public override decimal? Process(decimal value)
			{
				_values.Enqueue(value);
				if (_values.Count > Length)
					_values.Dequeue();

				if (_values.Count < Length)
					return null;

				var weightSum = 0m;
				var weightedTotal = 0m;
				var weight = 1m;

				foreach (var item in _values)
				{
					weightedTotal += item * weight;
					weightSum += weight;
					weight += 1m;
				}

				return weightedTotal / weightSum;
			}
		}
	}