Auf GitHub ansehen

Virtual-Profit/Loss-Trail-Strategie

Überblick

VirtualProfitLossTrailStrategy reproduziert das Verhalten des MetaTrader Expert Advisors "Virtual Profit Loss Trail" in StockSharp. Die Strategie eröffnet selbst niemals neue Positionen. Stattdessen überwacht sie kontinuierlich die aktuelle Position des ausgewählten Wertpapiers und wendet Schutzlogik an:

  • Eine konfigurierbare Take-Profit-Distanz in Pips.
  • Eine konfigurierbare Stop-Loss-Distanz in Pips.
  • Einen virtuellen Trailing Stop, der nach Erreichen eines Mindestgewinns aktiviert wird und nur dann mit dem Markt gleitet, wenn der Preis um den angegebenen Trailing-Schritt vorankommt.

Da die Schutzlevel virtuell sind, werden keine tatsächlichen Stop- oder Limit-Orders an die Börse gesendet. Die Strategie überwacht beste Bid-/Ask-Aktualisierungen und schließt die offene Position mit einer Marktorder, wenn eines der virtuellen Niveaus berührt wird.

Parameter

Parameter Beschreibung
Take-profit (pips) Distanz zwischen Einstiegspreis und Gewinnziel. Auf 0 setzen, um den Take-Profit-Ausstieg zu deaktivieren.
Stop-loss (pips) Distanz zwischen Einstiegspreis und Schutz-Stop. Auf 0 setzen, um den Stop-Loss-Ausstieg zu deaktivieren.
Trailing stop (pips) Distanz zur Berechnung des Trailing Stops. Bei 0 ist die Trailing-Logik vollständig deaktiviert.
Trailing step (pips) Zusätzlicher Gewinn, der erzielt werden muss, bevor der Trailing Stop weiter verschoben wird. 0 verwenden, um den Trail bei jedem neuen Hoch/Tief zu verschieben.
Trailing activation (pips) Mindestgewinn, der gesichert werden muss, bevor der Trailing Stop aktiv wird. Bei 0 startet Trailing sofort nach dem Positionseinstieg.

Alle Distanzen werden in Pip-Einheiten gemessen. Die Strategie leitet die Pip-Größe automatisch aus dem Preisschritt des Wertpapiers ab: Bei Symbolen mit drei oder fünf Dezimalstellen ist ein Pip als zehn Preisschritte definiert, ansonsten als ein Schritt.

Logik

  1. Marktdatensubscription - die Strategie abonniert Level1-Daten, um beste Bid- und Ask-Aktualisierungen zu erhalten. Nur fertige Aktualisierungen werden verarbeitet, sodass der Algorithmus sowohl in Echtzeit als auch bei historischen Replays funktioniert.
  2. Long-Positionsverwaltung - wenn die Nettoposition long ist, berechnet die Strategie virtuelle Stop-Loss-, Take-Profit- und Trailing-Stop-Niveaus auf Basis des durchschnittlichen Einstiegspreises. Berührt der beste Bid Stop-Loss oder Take-Profit, wird die Position sofort geschlossen. Sobald der Aktivierungsgewinn erreicht ist, folgt der Trailing Stop dem Preis nach oben. Der Stop wird nur weitergezogen, wenn die Trailing-Schritt-Anforderung erfüllt ist.
  3. Short-Positionsverwaltung - dieselbe Logik wird symmetrisch mit dem besten Ask für Ausstiege aus Short-Positionen angewendet.
  4. Reset-Verhalten - wenn die Position vollständig geschlossen ist, werden interne Trailing-Referenzen zurückgesetzt, um versehentliche Wiedereinstiegssignale zu verhindern.

Nutzungstipps

  • Binden Sie die Strategie an einen Connector und ein Wertpapier, das bereits eine offene Position hat oder Orders von anderen Strategien oder manuellem Handel erhalten wird. Der Manager steuert die aggregierte Positionsgröße.
  • Stellen Sie sicher, dass Level1-Daten verfügbar sind; ohne aktuelle Bid-/Ask-Werte können die virtuellen Niveaus nicht bewertet werden.
  • Die Strategie kann mit jeder einstiegserzeugenden Strategie kombiniert werden, indem beide unter demselben Portfolio und Wertpapier laufen. Nur eine Instanz sollte die Schutzlogik verwalten, um Konflikte zu vermeiden.

Unterschiede zum MQL-Expert

  • Die StockSharp-Version arbeitet mit aggregierten Positionen statt mit einzelnen Ordertickets. Sie berechnet automatisch den von der Plattform bereitgestellten durchschnittlichen Einstiegspreis.
  • Visuelles Linienzeichnen und Tonalarme des ursprünglichen Experts werden durch Logging in StockSharp ersetzt. Schutzaktionen sind im Strategiejournal sichtbar.
  • Die gleiche pipbasierte Konfiguration bleibt erhalten, einschließlich Trailing-Aktivierungsschwelle und inkrementellem Trailing-Schritt.

Dateien

  • CS/VirtualProfitLossTrailStrategy.cs - C#-Implementierung der Strategie.
  • README.md - diese Dokumentation.
  • README_zh.md - Übersetzung ins vereinfachte Chinesisch.
  • README_ru.md - russische Übersetzung.
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;

public class VirtualProfitLossTrailStrategy : 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 VirtualProfitLossTrailStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).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 = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

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

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}