Auf GitHub ansehen

Strategie OpenPendingorderAfterPositionGetStopLoss

Überblick

Die OpenPendingorderAfterPositionGetStopLoss-Strategie portiert die MetaTrader 5 gleichnamigen Expertenberater in die StockSharp-Hochebene API. Es wertet kontinuierlich die Steigung der Stochastic %K-Linie im ausgewählten Zeitrahmen aus. Wenn %K nach unten geht, wird eine Verkaufsstopp-Order unter dem Markt platziert, und wenn %K steigt, wird eine Kauf-Stopp-Order über dem Markt platziert. Jeder ausgefüllte Eintrag erhält sofort eine schützende Stop-Loss- und Take-Profit-Order. Wenn ein Stop-Loss die Position schließt, installiert die Strategie automatisch die entsprechende ausstehende Order neu, sodass das Raster der Breakout-Trades wiederhergestellt wird, ohne auf die nächste Kerze warten zu müssen.

Handelsregeln

  • Abonnieren Sie fertige Kerzen des konfigurierten Zeitrahmens und berechnen Sie einen klassischen Stochastic-Oszillator (KPeriod, DPeriod, Slowing).
  • Vergleichen Sie den aktuellen %K-Wert mit dem Wert vor zwei Balken:
    • %K(current) < %K(two bars ago) → Erteilen Sie einen Verkaufsstopp unterhalb des besten Gebots.
    • %K(current) > %K(two bars ago) → Setzen Sie einen Kaufstopp über dem besten Briefkurs.
  • Ausstehende Aufträge werden vom Markt um den aktuellen Spread plus den benutzerdefinierten MinStopDistancePoints-Puffer ausgeglichen, der der ursprünglichen MQL-Logik entspricht.
  • Sobald eine ausstehende Order ausgeführt wird, sendet die Strategie einen schützenden Stop-Loss (Stop-Order) und einen optionalen Take-Profit (Limit-Order).
  • Wenn der schützende Stop-Loss ausgelöst wird, wird die entsprechende ausstehende Order sofort unter Verwendung der neuesten Marktpreise neu erstellt.
  • Schutzaufträge werden automatisch gelöscht, wenn die Position durch den Take-Profit geschlossen wird oder wenn die Strategie stoppt.

Parameter

Name Beschreibung
OrderVolume Handelsvolumen in Lots für jede ausstehende Order.
StopLossPoints Stop-Loss-Distanz in Symbolpunkten. Zum Deaktivieren auf 0 setzen.
TakeProfitPoints Take-Profit-Distanz in Symbolpunkten. Zum Deaktivieren auf 0 setzen.
MinStopDistancePoints Minimaler Preispuffer (in Punkten), der dem Spread hinzugefügt wird, bevor eine ausstehende Order platziert wird.
MaxPositions Maximale Anzahl gleichzeitiger Positionen pro Richtung (Netting-Konten verwenden effektiv 0 oder 1).
KPeriod Anzahl der Balken, die für die %K-Berechnung verwendet werden.
DPeriod Länge der Glättungslinie %D.
Slowing Zusätzlicher Glättungsfaktor, der vor dem Vergleich auf %K angewendet wird.
PendingExpiry Optionale Lebensdauer ausstehender Stop-Orders. Abgelaufene Aufträge werden bei der nächsten Kerze storniert.
CandleType Zeitrahmen für Kerzenabonnements und Indikatorberechnungen.

Hinweise zur Implementierung

  • Die gesamte Auftragsverwaltung basiert auf hochrangigen Hilfsprogrammen wie BuyStop, SellStop, SellLimit und BuyLimit, wie von AGENTS.md gefordert.
  • Indikatorwerte werden direkt im SubscribeCandles().BindEx(...)-Rückruf verbraucht, wodurch jegliche GetValue-Aufrufe vermieden werden.
  • Die Strategie überwacht MyTrade-Ereignisse, um Schutzanordnungen zu installieren und zu entfernen, und emuliert dabei die OnTradeTransaction-Logik des ursprünglichen Expert Advisors.
  • Kommentare im Code werden in Englisch verfasst und die Einrückung erfolgt mit Tabulatoren, entsprechend den Repository-Richtlinien.
namespace StockSharp.Samples.Strategies;

using System;

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

/// <summary>
/// Trades based on the slope of the Stochastic %K line.
/// Buys when %K is rising, sells when %K is falling.
/// Uses take-profit and stop-loss protection.
/// </summary>
public class OpenPendingorderAfterPositionGetStopLossStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<int> _slowing;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _lastK;
	private decimal? _prevK;
	private decimal _entryPrice;
	private int _candlesSinceTrade;

	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	public int Slowing
	{
		get => _slowing.Value;
		set => _slowing.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	public int SignalCooldownCandles
	{
		get => _signalCooldownCandles.Value;
		set => _signalCooldownCandles.Value = value;
	}

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

	public OpenPendingorderAfterPositionGetStopLossStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 22)
			.SetDisplay("%K Period", "Number of bars for %K", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 7)
			.SetDisplay("%D Period", "Smoothing period for %K", "Indicators");

		_slowing = Param(nameof(Slowing), 2)
			.SetDisplay("Slowing", "Additional smoothing factor", "Indicators");

		_stopLossPct = Param(nameof(StopLossPct), 2m)
			.SetDisplay("Stop Loss %", "Stop-loss as percentage of entry price", "Risk");

		_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
			.SetDisplay("Take Profit %", "Take-profit as percentage of entry price", "Risk");

		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for indicator", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastK = null;
		_prevK = null;
		_entryPrice = 0;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_lastK = null;
		_prevK = null;
		_entryPrice = 0;
		_candlesSinceTrade = SignalCooldownCandles;

		var rsi = new RelativeStrengthIndex { Length = KPeriod };

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

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

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

		var close = candle.ClosePrice;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		// Check stop-loss / take-profit on existing position
		if (Position != 0 && _entryPrice > 0)
		{
			if (Position > 0)
			{
				var pnlPct = (close - _entryPrice) / _entryPrice * 100m;
				if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
				{
					SellMarket();
					_entryPrice = 0;
					_prevK = currentK;
					_lastK = currentK;
					return;
				}
			}
			else if (Position < 0)
			{
				var pnlPct = (_entryPrice - close) / _entryPrice * 100m;
				if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
				{
					BuyMarket();
					_entryPrice = 0;
					_prevK = currentK;
					_lastK = currentK;
					return;
				}
			}
		}

		// Need at least 2 values to determine signal transition.
		if (_lastK is not decimal prevK)
		{
			_lastK = currentK;
			return;
		}

		_prevK = _lastK;
		_lastK = currentK;

		var crossedUp = prevK <= 45m && currentK > 45m;
		var crossedDown = prevK >= 55m && currentK < 55m;

		if (crossedUp && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_entryPrice = close;
			_candlesSinceTrade = 0;
		}
		else if (crossedDown && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_entryPrice = close;
			_candlesSinceTrade = 0;
		}
	}
}