Diese Strategie portiert die MetaTrader 4 Dateien, die sich in MQL/8550 befinden (den Pivots-Indikator und den begleitenden Pivots_test-Expertenberater), in die übergeordnete Strategy API von StockSharp. Es behält das ursprüngliche Verhalten der Berechnung täglicher Floor-Pivot-Levels bei, stellt ein Paar gegensätzlicher ausstehender Orders am zentralen Pivot bereit und verwaltet jede resultierende Position mit einem festen Stop-Loss, Take-Profit und Trailing-Stop.
Pivot-Berechnung
Die Strategie abonniert einen konfigurierbaren Pivot-Zeitrahmen (PivotCandleType, standardmäßig täglich).
Immer wenn eine Kerze dieses Zeitrahmens endet, werden klassische Floor-Pivot-Levels aus den OHLC-Preisen des Vortages abgeleitet:
R3 = 2 × Pivot + High − 2 × Low und S3 = 2 × Pivot − (2 × High − Low)
Die Level werden zu Beginn der nächsten Sitzung aktiv. In diesem Fall protokolliert die Strategie die Werte über AddInfoLog (zum Beispiel: Pivot levels for 2024-04-05: P=1.0924, R1=1.0956, …).
Workflow für ausstehende Orders
Sobald Pivot-Levels aktiv sind, stellt die Strategie kontinuierlich sicher, dass zwei ausstehende Orders zum Pivot-Preis vorhanden sind:
Kauflimit bei Pivot mit Post-Fill-Schutz SellStop (Stop-Loss) bei S2 und SellLimit (Take-Profit) bei R2.
Verkaufsstopp bei Pivot mit Post-Fill-Schutz BuyStop bei R2 und BuyLimit bei S2.
Alle Bestellungen werden über die High-Level-Hilfsmethoden BuyLimit, SellStop, SellLimit und BuyStop übermittelt. Wenn eine Order ausgeführt wird, berechnet der Code den durchschnittlichen Einstiegspreis für diese Richtung neu, storniert bestehende Schutzorders und sendet ein neues Stop/Limit-Paar, das das gesamte offene Volumen abdeckt (was das MetaTrader-Verhalten widerspiegelt, bei dem jede Position denselben S2/R2-Schutz erbt). Wenn der schützende Stop oder Take-Profit ausgeführt wird, werden die entsprechenden Helfer automatisch gelöscht.
Die Strategie verwendet eine einzige Nettoposition, sodass sich entgegengesetzte Füllungen gegenseitig ausgleichen (im Gegensatz zur Ticket-basierten Absicherung von MetaTrader). Dies ist die einzige bewusste Abweichung vom Originalgutachten.
Trailing-Stop-Logik
TrailingStopPoints definiert die Entfernung in Indikatorpunkten (multipliziert mit dem Instrument PriceStep).
Bei Long-Positionen wird der Trailing Stop aktiviert, sobald sich der Preis um mehr als diese Distanz über den durchschnittlichen Einstiegspunkt bewegt hat. Der schützende SellStop wird dann näher an den Markt herangeführt.
Für Short-Positionen gilt die Spiegellogik, die den BuyStop senkt, wenn sich der Preis günstig entwickelt.
Nachfolgende Aktualisierungen werden durch die über CandleType ausgewählte Intraday-Serie gesteuert (standardmäßig 15-Minuten-Kerzen).
Trailing-Stop-Distanz in Punkten. 0 deaktiviert die nachgestellte Logik.
30
CandleType
Intraday-Kerzenserien, die zum Nachlaufen und zum Einhalten des Sitzungsplans verwendet werden.
15m Zeitrahmen
PivotCandleType
Zeitrahmen, der zur Berechnung der täglichen Pivot-Levels verwendet wird.
1D Zeitrahmen
LogPivotUpdates
Bei true werden die Pivot-Ebenen bei jeder Änderung in das Strategieprotokoll geschrieben.
true
Alle numerischen Parameter werden über StrategyParam<T> verfügbar gemacht, sodass sie innerhalb der StockSharp-Infrastruktur optimiert werden können.
Protokollierung und Diagnose
Pivot-Aktualisierungen werden über AddInfoLog weitergeleitet, was die Ausgabe von MetaTrader Comment/ObjectSetText ersetzt.
Schutzauftragsverwaltung, Positionsverwaltung und Trailing-Logik basieren ausschließlich auf den High-Level-Helfern von StockSharp. Es werden keine Low-Level-Auftragsregistrierung oder Indikatorpuffer verwendet.
Nutzungshinweise
Hängen Sie die Strategie an einen Connector an, der sowohl Tages- als auch Intraday-Kerzen für das ausgewählte Wertpapier bereitstellt.
Passen Sie bei Bedarf den Schritt des Instruments an (PriceStep wird automatisch erkannt; der Fallback ist 0.0001).
Passen Sie optional OrderVolume, TrailingStopPoints oder die Kerzentypen an, um sie an das ursprüngliche MT4-Setup anzupassen.
Wie gewünscht wird für diesen Port keine Python-Version bereitgestellt.
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 calculates classic floor pivot levels from daily candles and trades
/// breakouts around the central pivot. Goes long on close above pivot, short on close below.
/// Uses S2/R2 as stop/target levels.
/// </summary>
public class PivotsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private decimal _pivotLevel;
private decimal _r1, _r2, _s1, _s2;
private decimal? _previousClose;
private decimal? _entryPrice;
private bool _pivotReady;
private readonly List<decimal> _dailyHighs = new();
private readonly List<decimal> _dailyLows = new();
private readonly List<decimal> _dailyCloses = new();
private DateTime _currentDay;
private decimal _dayHigh;
private decimal _dayLow;
private decimal _dayClose;
public PivotsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pivotLevel = 0m;
_r1 = _r2 = _s1 = _s2 = 0m;
_previousClose = null;
_entryPrice = null;
_pivotReady = false;
_dailyHighs.Clear();
_dailyLows.Clear();
_dailyCloses.Clear();
_currentDay = DateTime.MinValue;
_dayHigh = 0m;
_dayLow = decimal.MaxValue;
_dayClose = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var candleDay = candle.OpenTime.Date;
// Track daily OHLC
if (candleDay != _currentDay)
{
if (_currentDay != DateTime.MinValue && _dayHigh > 0m)
{
// Previous day completed, calculate pivots
var high = _dayHigh;
var low = _dayLow;
var close = _dayClose;
_pivotLevel = (high + low + close) / 3m;
_r1 = 2m * _pivotLevel - low;
_s1 = 2m * _pivotLevel - high;
_r2 = _pivotLevel + (high - low);
_s2 = _pivotLevel - (high - low);
_pivotReady = true;
}
_currentDay = candleDay;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
else
{
if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
if (!_pivotReady)
{
_previousClose = candle.ClosePrice;
return;
}
if (_previousClose is null)
{
_previousClose = candle.ClosePrice;
return;
}
// Manage open positions
if (Position > 0)
{
// Exit long at R2 (take profit) or S1 (stop loss)
if (candle.HighPrice >= _r2 || candle.LowPrice <= _s1)
{
SellMarket(Math.Abs(Position));
_entryPrice = null;
}
}
else if (Position < 0)
{
// Exit short at S2 (take profit) or R1 (stop loss)
if (candle.LowPrice <= _s2 || candle.HighPrice >= _r1)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
}
// Entry signals based on pivot cross
if (Position == 0)
{
var crossAbovePivot = _previousClose.Value <= _pivotLevel && candle.ClosePrice > _pivotLevel;
var crossBelowPivot = _previousClose.Value >= _pivotLevel && candle.ClosePrice < _pivotLevel;
if (crossAbovePivot)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (crossBelowPivot)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_previousClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DateTime
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class pivots_strategy(Strategy):
def __init__(self):
super(pivots_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for signal generation", "General")
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(pivots_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
candle_day = candle.OpenTime.Date
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._current_day is None or candle_day != self._current_day:
if self._current_day is not None and self._day_high > 0:
h = self._day_high
l = self._day_low
c = self._day_close
self._pivot_level = (h + l + c) / 3.0
self._r1 = 2.0 * self._pivot_level - l
self._s1 = 2.0 * self._pivot_level - h
self._r2 = self._pivot_level + (h - l)
self._s2 = self._pivot_level - (h - l)
self._pivot_ready = True
self._current_day = candle_day
self._day_high = high
self._day_low = low
self._day_close = close
else:
if high > self._day_high:
self._day_high = high
if low < self._day_low:
self._day_low = low
self._day_close = close
if not self._pivot_ready:
self._previous_close = close
return
if self._previous_close is None:
self._previous_close = close
return
if self.Position > 0:
if high >= self._r2 or low <= self._s1:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = None
elif self.Position < 0:
if low <= self._s2 or high >= self._r1:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
if self.Position == 0:
cross_above_pivot = self._previous_close <= self._pivot_level and close > self._pivot_level
cross_below_pivot = self._previous_close >= self._pivot_level and close < self._pivot_level
if cross_above_pivot:
self.BuyMarket()
self._entry_price = close
elif cross_below_pivot:
self.SellMarket()
self._entry_price = close
self._previous_close = close
def OnReseted(self):
super(pivots_strategy, self).OnReseted()
self._pivot_level = 0.0
self._r1 = 0.0
self._r2 = 0.0
self._s1 = 0.0
self._s2 = 0.0
self._previous_close = None
self._entry_price = None
self._pivot_ready = False
self._current_day = None
self._day_high = 0.0
self._day_low = float('inf')
self._day_close = 0.0
def CreateClone(self):
return pivots_strategy()