Auf GitHub ansehen

Pendulum-Strategie

Grid-basiertes Martingale-System, das zwischen zwei Preisgrenzen pendelt. Die Strategie eröffnet eine Long-Position, wenn der Preis die obere Grenze des Grids erreicht, und wechselt zu einer Short-Position mit erhöhtem Volumen, wenn der Preis zur unteren Grenze wechselt. Sie wechselt weiterhin die Richtungen (bis zu einer konfigurierbaren Anzahl von Schichten) und erweitert dabei die Ziele und reduziert die Schutzabstände gemäß dem ursprünglichen Pendulum-Expertenberater. Nach der Gewinnmitnahme setzt die Engine das Grid zurück und plant einen neuen Einstieg auf demselben Niveau, um die Pendelbewegung aufrechtzuerhalten.

Details

  • Einstiegslogik
    • Richtet das Grid am Kerzen-Schlusskurs unter Verwendung der konfigurierten StepSize aus.
    • Oberes Trigger-Niveau erreicht → eröffnet eine Long-Position mit dem Basisvolumen.
    • Unteres Trigger-Niveau erreicht → eröffnet eine Short-Position mit dem Basisvolumen.
    • Wenn die aktive Position zum entgegengesetzten Trigger wechselt, dreht die Strategie die Richtung um, multipliziert das absolute Volumen mit Multiplier und aktualisiert die Take-Profit/Stop-Loss-Abstände wie die MQL-Version.
    • Wiedereinstiege werden nach profitablen Ausstiegen geplant, damit die nächste Kerze sofort auf demselben Grid-Niveau wieder eröffnen kann, sobald die Schließtrades verarbeitet sind.
  • Ausstiegslogik
    • Jede Schicht definiert einen dedizierten Take-Profit: ein Schritt für die erste Schicht, Multiplier Schritte für jede nachfolgende Schicht.
    • Schutz-Stops spiegeln die MQL-Logik wider: Die erste Schicht verwendet einen weiten Stop (StepSize * Multiplier), nachfolgende Schichten verwenden einen Ein-Schritt-Stop gegen die neue Richtung.
    • Wenn die maximale Anzahl an Schichten erreicht ist, wartet die Strategie auf Take-Profit oder Stop-Loss, bevor sie zurückgesetzt wird.
  • Positionsmanagement
    • Verwendet Netting: Der StockSharp-Port schließt und kehrt die aggregierte Position um, anstatt abgesicherte Longs und Shorts zu halten. Dies bewahrt die Exposition des ursprünglichen Experten und bleibt dabei mit StockSharp-Portfolios kompatibel.
    • Das Volumen wird auf den Instrumenten-Volumenschritt gerundet, wenn verfügbar.
  • Daten
    • Funktioniert mit jedem Symbol und Zeitrahmen. Das Standard-Abonnement verwendet 1-Minuten-Kerzen und verlässt sich auf Kerzen-Schlusskurse für die Grid-Prüfungen.
  • Eingebauter Schutz
    • StartProtection() ist aktiviert, um unerwartete Positionen nach Verbindungsabbrüchen oder manuellen Eingriffen zu schützen.

Parameter

Parameter Standard Beschreibung
StepSize 0.001 Abstand zwischen Grid-Levels. Das Grid rastet immer auf Vielfache dieses Wertes ein.
Multiplier 2 Multipliziert sowohl das Trade-Volumen als auch die erweiterten Ziele, wenn die Richtung auf eine neue Schicht wechselt. Muss größer als 1 sein.
MaxLayers 3 Maximale Anzahl von Martingale-Schichten, bevor die Strategie aufhört, neue Umkehrungen hinzuzufügen.
BaseVolume 1 Basis-Handelsgröße für die erste Schicht. Spätere Schichten skalieren mit Multiplier.
CandleType 1 Minute TimeFrame Kerzentyp für das Abonnement. Kann auf jeden anderen vom Datenprovider unterstützten Zeitrahmen geändert werden.

Hinweise

  • Die Strategie reproduziert das Verhalten von Pendulum.mq5 ohne Abhängigkeit von abgesicherten Positionen. Da StockSharp die Exposition konsolidiert, wird die Nettoposition umgekehrt, um die MQL-Grids zu emulieren.
  • Take-Profit-Abschlüsse lösen eine verzögerte Order aus, damit die nächste Kerze sofort auf demselben Preisniveau wieder eröffnen kann, sobald der Schließtrade verarbeitet ist.
  • Die konfigurierte Schrittgröße mit dem Instrumenten-Preisschritt ausrichten, um übermäßiges Runden der Grid-Levels zu vermeiden.
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;

/// <summary>
/// Pendulum strategy using SMA crossover with mean reversion.
/// Buys when fast SMA crosses above slow SMA, sells on reverse.
/// </summary>
public class PendulumStrategy : 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 PendulumStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 80).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 = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

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

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}