Auf GitHub ansehen

KDJ Experten-Advisor-Strategie

Überblick

Diese Strategie repliziert den MetaTrader 5-Experten «KDJ Expert Advisor» von senlin ge. Sie handelt ein einzelnes Symbol mit Signalen des KDJ-Oszillators, einer Weiterentwicklung des Stochastik-Oszillators, bei dem die %K-Linie zweimal geglättet wird. Die Strategie beobachtet die Differenz zwischen den %K- und %D-Linien (oft als J-Linie bezeichnet), um Momentum-Umkehrungen zu identifizieren, und öffnet immer nur eine Position. Das Trade-Management spiegelt den ursprünglichen Experten wider: Jeder Trade erhält sofort einen festen Stop-Loss und Take-Profit, ausgedrückt in Pips und in Preisabstand über die Instrumenteneinstellungen übersetzt.

Die Implementierung verwendet StockSharp's High-Level-API mit einem Kerzenabonnement und dem integrierten Stochastic-Indikator, konfiguriert, um die KDJ-Parameter der MQL5-Version zu entsprechen. Der Code erkennt automatisch 3- oder 5-stellige Forex-Symbole und passt den Pip-Wert entsprechend an.

Indikatorlogik

Der zugrunde liegende Indikator arbeitet in drei Stufen:

  1. RSV-Berechnung – Für jede fertige Kerze wird der Raw Stochastic Value über KDJ Length Kerzen berechnet: [ RSV = \frac{Close - LowestLow}{HighestHigh - LowestLow} \times 100 ]
  2. %K-Glättung – Berechnung des Durchschnitts der letzten Smooth %K RSV-Werte zur Erhaltung der %K-Linie.
  3. %D-Glättung – Berechnung des Durchschnitts der letzten Smooth %D %K-Werte zur Erhaltung der %D-Linie.

Die Strategie analysiert dann K - D (in der Originalquelle als KDC bezeichnet) und die Steigung von %K, um Umkehrungen zu erkennen.

Einstiegskriterien

Eine Marktposition wird nur geöffnet, wenn keine bestehende Position für das Symbol vorhanden ist. Signale werden auf abgeschlossenen Kerzen bewertet:

  • Kauf wenn eine der folgenden Bedingungen erfüllt ist:
    • K - D kreuzt über null (von negativ zu positiv); oder
    • K - D ist über null und die %K-Linie steigt (K_current > K_previous).
  • Verkauf wenn eine der folgenden Bedingungen erfüllt ist:
    • K - D kreuzt unter null (von positiv zu negativ); oder
    • K - D ist unter null und die %K-Linie fällt (K_current < K_previous).

Dies entspricht der booleschen Struktur des ursprünglichen MQL5-Experten und gewährleistet identisches Trade-Timing.

Risikomanagement

  • Jede ausgeführte Order erhält einen schützenden Stop-Loss und Take-Profit, gemessen in Pips und in Preisabstand über die Tick-Größe des Instruments umgerechnet. Ein Wert von null deaktiviert das entsprechende Schutz-Bein.
  • Die Strategie pyramidisiert nicht und mittelt keine Positionen. Sie bleibt flat, bis die aktuelle Position durch die Schutzorders oder manuelle Intervention geschlossen wird.

Parameter

Parameter Beschreibung Standard
Candle Type Datentyp/Zeitrahmen der Eingabekerzen. 15-Minuten-Zeitrahmen
KDJ Length Anzahl der Kerzen für die RSV-Berechnung. 30
Smooth %K Anzahl der RSV-Werte zur Glättung der %K-Linie. 3
Smooth %D Anzahl der %K-Werte zur Glättung der %D-Linie. 6
Stop Loss (pips) Abstand des schützenden Stop-Loss. Auf 0 setzen zum Deaktivieren. 25
Take Profit (pips) Abstand des schützenden Take-Profit. Auf 0 setzen zum Deaktivieren. 45
Order Volume Mit Marktorders gesendete Menge. 1

Alle Parameter unterstützen Optimierungsbereiche, die den Inputs des ursprünglichen Experten entsprechen.

Verwendungshinweise

  1. Konfigurieren Sie das gewünschte Instrument und den Konnektor im Tester oder in der Live-Umgebung.
  2. Passen Sie den Kerzentyp an den Chartzeitrahmen an, den Sie von MetaTrader emulieren möchten.
  3. Optimieren Sie optional die KDJ-Parameter, Stop-Loss, Take-Profit oder Order-Volumen.
  4. Starten Sie die Strategie. Orders werden nur auf vollständig geformten Kerzen generiert.
  5. Der Chart zeigt automatisch Kerzen, den KDJ-Indikator und ausgeführte Trades zur visuellen Bestätigung.

Unterschiede zum Original-EA

  • Verwendet StockSharp's Stochastic-Indikator mit Glättungsperioden zur Replikation der MQL5-KDJ-Puffer; keine externe Indikatordatei erforderlich.
  • Schutzorders werden durch StartProtection verwaltet, das Marktausstiege sendet, wenn sie ausgelöst werden.
  • Volumen ist ein fester Parameter anstatt des MQL5-MoneyFixedMargin-Risikomodells, was die Implementierung prägnant und auf die Signallogik fokussiert hält.
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>
/// Strategy that replicates the MetaTrader KDJ Expert Advisor logic.
/// Uses the KDJ oscillator to detect momentum reversals and opens a single position with fixed take-profit and stop-loss levels.
/// </summary>
public class KdjExpertAdvisorStrategy : Strategy
{
	private readonly StrategyParam<int> _kdjPeriod;
	private readonly StrategyParam<int> _smoothK;
	private readonly StrategyParam<int> _smoothD;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _previousK;
	private decimal? _previousKdc;
	private decimal _pipSize;

	/// <summary>
	/// Main lookback period used to calculate RSV for the KDJ oscillator.
	/// </summary>
	public int KdjPeriod
	{
		get => _kdjPeriod.Value;
		set => _kdjPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing period applied to the %K line.
	/// </summary>
	public int SmoothK
	{
		get => _smoothK.Value;
		set => _smoothK.Value = value;
	}

	/// <summary>
	/// Smoothing period applied to the %D line.
	/// </summary>
	public int SmoothD
	{
		get => _smoothD.Value;
		set => _smoothD.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Volume applied to every market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="KdjExpertAdvisorStrategy"/> class.
	/// </summary>
	public KdjExpertAdvisorStrategy()
	{
		_kdjPeriod = Param(nameof(KdjPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("KDJ Length", "Lookback period for KDJ RSV calculation", "KDJ")
			
			.SetOptimize(10, 60, 5);

		_smoothK = Param(nameof(SmoothK), 3)
			.SetGreaterThanZero()
			.SetDisplay("Smooth %K", "Smoothing length for %K", "KDJ")
			
			.SetOptimize(1, 10, 1);

		_smoothD = Param(nameof(SmoothD), 6)
			.SetGreaterThanZero()
			.SetDisplay("Smooth %D", "Smoothing length for %D", "KDJ")
			
			.SetOptimize(1, 15, 1);

		_stopLossPips = Param(nameof(StopLossPips), 250)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk")

			.SetOptimize(0, 1000, 50);

		_takeProfitPips = Param(nameof(TakeProfitPips), 450)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk")

			.SetOptimize(0, 1500, 50);

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Quantity used for entries", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for KDJ calculation", "Data");
	}

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

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

		_previousK = null;
		_previousKdc = null;
		_pipSize = 0m;
	}

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

		_pipSize = CalculatePipSize();

		var stopLossUnit = StopLossPips > 0 ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : null;
		var takeProfitUnit = TakeProfitPips > 0 ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : null;

		StartProtection(
			takeProfit: takeProfitUnit,
			stopLoss: stopLossUnit,
			useMarketOrders: true);

		var kdj = new StochasticOscillator
		{
			K = { Length = KdjPeriod },
			D = { Length = SmoothD }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(kdj, ProcessCandle)
			.Start();

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

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

		var stochastic = (StochasticOscillatorValue)kdjValue;
		if (stochastic.K is not decimal k || stochastic.D is not decimal d)
			return;

		var kdc = k - d;

		var buySignal = false;
		var sellSignal = false;

		if (_previousKdc.HasValue)
		{
			buySignal |= _previousKdc.Value < 0m && kdc > 0m;
			sellSignal |= _previousKdc.Value > 0m && kdc < 0m;
		}

		if (_previousK.HasValue)
		{
			buySignal |= kdc > 0m && _previousK.Value < k;
			sellSignal |= kdc < 0m && _previousK.Value > k;
		}

		if (buySignal || sellSignal)
		{
			if (Position == 0)
			{
				if (buySignal)
				{
					LogInfo($"Buy signal at {candle.ClosePrice}: K={k:F2}, D={d:F2}, K-D={kdc:F2}");
					BuyMarket();
				}
				else if (sellSignal)
				{
					LogInfo($"Sell signal at {candle.ClosePrice}: K={k:F2}, D={d:F2}, K-D={kdc:F2}");
					SellMarket();
				}
			}
		}

		_previousK = k;
		_previousKdc = kdc;
	}

	private decimal CalculatePipSize()
	{
		var security = Security;
		if (security == null)
			return 1m;

		var step = security.PriceStep ?? 1m;
		var decimals = security.Decimals;
		var multiplier = (decimals == 3 || decimals == 5) ? 10m : 1m;

		return step * multiplier;
	}
}