Auf GitHub ansehen

Tägliche STP-Eintrittsrahmenstrategie (StockSharp)

Überblick

Die Daily STP Entry Frame-Strategie repliziert das Verhalten des ursprünglichen MetaTrader-Expertenberaters „Daily STP Entry Frame“ unter Verwendung des StockSharp-High-Levels API. Das System bereitet zu Beginn jedes neuen Handelstages Breakout-Stop-Orders vor. Die Einstiegspreise werden aus den Höchst- und Tiefstständen des Vortages abgeleitet, mit zusätzlichen Filtern, um sicherzustellen, dass sich der Markt in der Nähe dieser Extremwerte positioniert, bevor Aufträge erteilt werden. Die Logik ist auf Instrumente im Forex-Stil zugeschnitten, bei denen „Basispunkte“ einem Zehntel Pip für fünfstellige Kurse entsprechen.

Kern-Workflow

  1. Tägliche Range-Verfolgung – Die Strategie abonniert tägliche Kerzen, um sich an die Höchst- und Tiefststände der vorherigen Sitzung zu erinnern.
  2. Echtzeitüberwachung – Level1-Daten liefern die aktuellen Geld-, Brief- und letzten Handelspreise für das Intraday-Management.
  3. Order-Aktivierung – Wenn zu Beginn eines neuen Tages der letzte Preis mindestens ThresholdPoints vom gestrigen Hoch/Tief entfernt liegt und die Eröffnung des aktuellen Tages auf der richtigen Seite dieses Extrems liegt, wird eine Stop-Order übermittelt:
    • Kaufstopp bei High + SpreadPoints / 2 (umgerechnet in Preiseinheiten).
    • Verkaufsstopp bei Low - SpreadPoints / 2.
  4. Risikovalidierung – Neue Aufträge werden blockiert, wenn der Aktienrückgang MaximumDrawdownPercent überschreitet oder die Zeitfilter den Handel nicht zulassen (Wochenenden, Stundenfilter oder Tagesfilter).
  5. Positionsmanagement – sobald ein Handel aktiv ist, erzwingt die Strategie Folgendes:
    • Statische Stop-Loss- und Take-Profit-Distanzen.
    • Optionaler zeitbasierter Exit nach CloseAfterSeconds.
    • Optionaler Trailing-Stop, der den ursprünglichen Parameter „SL-Steigung“ emuliert.
  6. Hygiene am Ende des Tages – ausstehende Orders werden nach NoNewOrdersHour (oder dem entsprechenden Freitagsschluss) und bei jeder Änderung des Kalendertags storniert.

Handelsregeln

  • Lange Einträge
    • Zulässig, wenn SideFilter 0 (beide) oder 1 (nur lang) ist.
    • Höchststand des Vortages minus aktueller Preis ≥ ThresholdPoints.
    • Der heutige Eröffnungskurs liegt unter dem gestrigen Hoch.
    • Der berechnete Einstiegspreis muss den Mindestabstand zum aktuellen Brief einhalten.
  • Kurze Einträge
    • Zulässig, wenn SideFilter 0 (beide) oder -1 (nur kurz) ist.
    • Aktueller Preis minus Tiefststand des Vortages ≥ ThresholdPoints.
    • Der heutige Eröffnungskurs liegt über dem gestrigen Tief.
    • Der berechnete Einstiegspreis muss den Mindestabstand zum aktuellen Gebot einhalten.
  • Geldmanagement
    • Bei der dynamischen Volumengröße wird ein Prozentsatz des kumulierten Gewinns (PercentOfProfit) verwendet.
    • Die resultierende Größe wird durch MinVolume und MaxVolume begrenzt und an der VolumeStep des Instruments ausgerichtet.
    • Der Handel wird automatisch unterbrochen, sobald der gemessene Drawdown MaximumDrawdownPercent überschreitet.
  • Schutzlogik
    • Stop-Loss- und Take-Profit-Level werden in Basispunkten ausgedrückt und anhand der Pip-Größe des Instruments in Preisversätze umgerechnet.
    • Trailing Stop ist nur aktiv, wenn TrailingSlope < 1. Es verschiebt die Schutzschwelle näher an den Preis, wenn der nicht realisierte Gewinn wächst.
    • Lebenszeitausgänge schließen alle offenen Positionen, sobald die konfigurierte Anzahl von Sekunden verstrichen ist.

Parameter

Name Beschreibung
CandleType Zeitrahmen, der zum Abrufen der Referenzkerzen verwendet wird (standardmäßig täglich).
StopLossPoints Stop-Loss-Distanz in Basispunkten.
TakeProfitPoints Take-Profit-Distanz in Basispunkten.
TrailingSlope Teil des Gewinns, der während des Trailings einbehalten wird; ≥ 1 deaktiviert die Funktion.
SideFilter -1 nur kurz, 0 beide Richtungen, 1 nur lang.
ThresholdPoints Minimale Lücke zwischen dem aktuellen Preis und dem vorherigen Extrem, die erforderlich ist, um einen Stopp zu aktivieren.
SpreadPoints Zusätzlicher Versatz (die Hälfte wird über/unter dem Extremwert verwendet), um die Streuung auszugleichen.
SlippagePoints Sicherheitspuffer zur Prüfung des Mindestanhaltewegs hinzugefügt.
NoNewOrdersHour Stunde (Plattformzeit), um ausstehende Orders an regulären Tagen zu stornieren.
NoNewOrdersHourFriday Freitagsspezifische Stornierungsstunde.
EarliestOrderHour Früheste Stunde des Tages, zu der neue Aufträge erstellt werden können.
DayFilter 6 für alle Tage oder 0-5, um nur von Sonntag bis Freitag zu handeln.
CloseAfterSeconds Optionaler zeitbasierter Exit (0 deaktiviert).
PercentOfProfit Bruchteil des kumulierten Gewinns, der zur Skalierung der Positionsgröße verwendet wird.
MinVolume / MaxVolume Feste Grenzen für das berechnete Volumen.
MaximumDrawdownPercent Drawdown-Schwelle, die neue Aufträge blockiert.

Konvertierungshinweise

  • Die Pip-Konvertierung spiegelt die MetaTrader-Implementierung wider: Wenn die Sicherheit 3 oder 5 Dezimalstellen offenlegt, wird der Basispunkt zu PriceStep * 10.
  • Das Stopp-Order-Stornierungsfenster reproduziert die abendliche Aufräumaktion des Experten, einschließlich des separaten Freitags-Cutoffs.
  • Die Trailing-Logik folgt der ursprünglichen Steigungsformel (newStop = Bid - StopLoss - Slope * (Bid - Entry) für Longs).
  • Eigenkapitalbenachrichtigungen aus der Version MQL werden durch Strategieprotokollmeldungen ersetzt.
  • Die StockSharp-Implementierung hält ausstehende Aufträge auch dann aktiv, wenn eine Position offen ist, und entspricht damit dem Verhalten der Quelle.

Nutzungstipps

  • Weisen Sie ein Forex-Instrument mit den ordnungsgemäß konfigurierten Werten PriceStep, StepPrice und VolumeStep zu, um eine genaue Größenbestimmung sicherzustellen.
  • Kombinieren Sie die Strategie mit StockSharp Risikokontrollen (Portfoliolimits, Schutzmaßnahmen auf Connector-Ebene), wenn Sie live laufen.
  • Optimieren Sie ThresholdPoints, TrailingSlope und PercentOfProfit mit Designer oder Runner, um die Breakout-Empfindlichkeit an bestimmte Symbole anzupassen.
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>
/// Daily breakout strategy that enters on price crossing previous session extremes.
/// Converted from "Daily STP Entry Frame" MetaTrader expert.
/// Uses market orders when price breaks above previous high or below previous low.
/// </summary>
public class DailyStpEntryFrameStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private decimal _pipSize;
	private decimal _stopLossOffset;
	private decimal _takeProfitOffset;

	private decimal? _previousDayHigh;
	private decimal? _previousDayLow;
	private decimal _currentDayHigh;
	private decimal _currentDayLow;
	private DateTime? _currentTradingDay;
	private bool _tradedToday;

	private decimal _entryPrice;
	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public DailyStpEntryFrameStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame for monitoring", "General");

		_stopLossPoints = Param(nameof(StopLossPoints), 80m)
			.SetNotNegative()
			.SetDisplay("Stop-Loss (points)", "Stop-loss distance", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetNotNegative()
			.SetDisplay("Take-Profit (points)", "Take-profit distance", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_pipSize = 0m;
		_stopLossOffset = 0m;
		_takeProfitOffset = 0m;
		_previousDayHigh = null;
		_previousDayLow = null;
		_currentDayHigh = 0m;
		_currentDayLow = 0m;
		_currentTradingDay = null;
		_tradedToday = false;
		_entryPrice = 0m;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pipSize = Security?.PriceStep ?? 0.01m;
		if (_pipSize <= 0) _pipSize = 0.01m;

		_stopLossOffset = StopLossPoints * _pipSize;
		_takeProfitOffset = TakeProfitPoints * _pipSize;

		_previousDayHigh = null;
		_previousDayLow = null;
		_currentTradingDay = null;
		_tradedToday = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var date = candle.OpenTime.Date;

		// Track daily high/low
		if (_currentTradingDay != date)
		{
			// Save previous day's range
			if (_currentTradingDay != null)
			{
				_previousDayHigh = _currentDayHigh;
				_previousDayLow = _currentDayLow;
			}

			_currentTradingDay = date;
			_currentDayHigh = candle.HighPrice;
			_currentDayLow = candle.LowPrice;
			_tradedToday = false;
		}
		else
		{
			_currentDayHigh = Math.Max(_currentDayHigh, candle.HighPrice);
			_currentDayLow = Math.Min(_currentDayLow, candle.LowPrice);
		}

		// Manage existing position
		ManagePosition(candle);

		// Check for breakout entries
		if (_previousDayHigh is null || _previousDayLow is null)
			return;

		if (_tradedToday || Position != 0)
			return;

		var close = candle.ClosePrice;

		// Breakout above previous day high => buy
		if (close > _previousDayHigh.Value)
		{
			_entryPrice = close;
			_longStop = _stopLossOffset > 0 ? close - _stopLossOffset : null;
			_longTake = _takeProfitOffset > 0 ? close + _takeProfitOffset : null;
			_shortStop = null;
			_shortTake = null;
			BuyMarket();
			_tradedToday = true;
		}
		// Breakout below previous day low => sell
		else if (close < _previousDayLow.Value)
		{
			_entryPrice = close;
			_shortStop = _stopLossOffset > 0 ? close + _stopLossOffset : null;
			_shortTake = _takeProfitOffset > 0 ? close - _takeProfitOffset : null;
			_longStop = null;
			_longTake = null;
			SellMarket();
			_tradedToday = true;
		}
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				_longStop = null;
				_longTake = null;
				return;
			}
			if (_longTake is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				_longStop = null;
				_longTake = null;
			}
		}
		else if (Position < 0)
		{
			if (_shortStop is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				_shortStop = null;
				_shortTake = null;
				return;
			}
			if (_shortTake is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				_shortStop = null;
				_shortTake = null;
			}
		}
	}
}