Auf GitHub ansehen

Pending-Stop-Grid-Strategie

Überblick

Die Pending-Stop-Grid-Strategie ist eine direkte Umwandlung des MetaTrader-4 Expert Advisors new.mq4. Die Strategie hält zwei symmetrische Leitern aus Pending Orders:

  • Eine Sequenz von Buy-Stop-Orders oberhalb des aktuellen Ask-Preises.
  • Eine Sequenz von Sell-Stop-Orders unterhalb des aktuellen Bid-Preises.

Jede zusätzliche Stufe erhöht sowohl die Orderdistanz als auch das gehandelte Volumen proportional zu ihrer Position innerhalb der Leiter. Stop-Loss- und Take-Profit-Ziele werden jeder Order einzeln zugewiesen.

Handelslogik

  1. Die Strategie abonniert Level-1-Daten und verfolgt kontinuierlich die neuesten besten Bid- und Ask-Preise.
  2. Sobald Marktdaten und Handelsberechtigungen verfügbar sind, berechnet sie die Pip-Größe anhand des Wertpapier-Preisschritts (wobei fünf- und dreistellige Symbole automatisch auf Standard-Pip-Werte normalisiert werden).
  3. Vor dem Platzieren von Orders validiert die Strategie, dass das konfigurierte Basisvolumen die Mindest- und Höchstvolumenbeschränkungen des Instruments einhält.
  4. Für jeden Index i von 1 bis NumberOfTrades:
    • Das Ordervolumen wird als BaseVolume * i berechnet und auf den nächsten erlaubten Schritt gerundet.
    • Ein Buy Stop wird bei Ask + DistancePips * i * pipSize mit optionalen Stop-Loss- und Take-Profit-Offsets platziert.
    • Ein Sell Stop wird bei Bid - DistancePips * i * pipSize mit gespiegelten Stop-Loss- und Take-Profit-Offsets platziert.
  5. Wenn eine Order ausgeführt, storniert oder abgelehnt wird, wird der entsprechende Platz in der Leiter geleert und sofort mit einer neuen Pending Order aufgefüllt, sobald Marktdaten dies erlauben.
  6. Das eingebaute StartProtection() wird beim Start aufgerufen, um die Plattform-Risikosteuerungen zu aktivieren.

Parameter

Name Beschreibung Standard
BaseVolume Volumen der ersten Pending Order. Jede nachfolgende Order multipliziert diese Basis mit ihrem Index. 0.1
NumberOfTrades Anzahl der gleichzeitig gehaltenen Buy-Stop- und Sell-Stop-Orders. 10
DistancePips Distanz (in Pips) zwischen Marktpreis und jeder Pending-Order-Stufe. 10
StopLossPips Stop-Loss-Distanz, die jeder Order zugewiesen wird. Auf null setzen, um Stop-Loss-Platzierung zu deaktivieren. 10
TakeProfitPips Take-Profit-Distanz, die jeder Order zugewiesen wird. Auf null setzen, um Take-Profit-Platzierung zu deaktivieren. 10

Alle Parameter werden als optimierbare Strategieparameter bereitgestellt und validiert, um negative oder Nullwerte zu vermeiden (wo zutreffend).

Zusätzliche Hinweise

  • Volumina werden auf den nächsten zulässigen Schritt gerundet und innerhalb der börsenseitig definierten Mindest- und Höchstgrenzen begrenzt.
  • Preise werden mit Security.ShrinkPrice normalisiert, um die Tickgröße des Instruments zu respektieren.
  • Die Strategie hält keinen historischen Zustand: Sie baut die gesamte Leiter neu auf, wenn das Wertpapier zurückgesetzt wird oder sich Handelsberechtigungen ändern.
  • Die Logik vermeidet manuelle Indikatorbuffer zugunsten der High-Level-API-Bindings von StockSharp und folgt damit den projektweiten Konvertierungsrichtlinien.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class PendingStopGridStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public PendingStopGridStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}