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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pendulum_strategy(Strategy):
def __init__(self):
super(pendulum_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 80).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._sl_points = self.Param("StopLossPoints", 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pendulum_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
def OnStarted2(self, time):
super(pendulum_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
fast = ExponentialMovingAverage()
fast.Length = self._fast_period.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
def _get_step(self):
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
return float(self.Security.PriceStep)
return 1.0
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if self._prev_fast == 0 or self._prev_slow == 0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = candle.ClosePrice
step = self._get_step()
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return pendulum_strategy()