Auf GitHub ansehen

4026 – Pivots-Strategie

Überblick

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

  1. Die Strategie abonniert einen konfigurierbaren Pivot-Zeitrahmen (PivotCandleType, standardmäßig täglich).
  2. Immer wenn eine Kerze dieses Zeitrahmens endet, werden klassische Floor-Pivot-Levels aus den OHLC-Preisen des Vortages abgeleitet:
    • Pivot = (High + Low + Close) / 3
    • R1 = 2 × Pivot − Low
    • S1 = 2 × Pivot − High
    • R2 = Pivot + (High − Low) und S2 = Pivot − (High − Low)
    • R3 = 2 × Pivot + High − 2 × Low und S3 = 2 × Pivot − (2 × High − Low)
  3. 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).

Parameter

Parameter Beschreibung Standard
OrderVolume Volumen jeder ausstehenden Bestellung (Lots/Kontrakte). 0.1
TrailingStopPoints 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

  1. Hängen Sie die Strategie an einen Connector an, der sowohl Tages- als auch Intraday-Kerzen für das ausgewählte Wertpapier bereitstellt.
  2. Passen Sie bei Bedarf den Schritt des Instruments an (PriceStep wird automatisch erkannt; der Fallback ist 0.0001).
  3. 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;
	}
}