Strategiekompatibilität mit StockSharp-Plattformen

Bei der Entwicklung von Handelsstrategien in StockSharp ist es wichtig, ihre Kompatibilität mit verschiedenen Plattformen zu berücksichtigen: Designer, Shell, Runner und Cloud-Backtesting. Wenn Sie die folgenden Empfehlungen beachten, erstellen Sie eine Strategie, die in allen Umgebungen korrekt funktioniert.

Parameter im Strategiekonstruktor

Parameter im Konstruktor vermeiden

Um die Kompatibilität mit StockSharp-Plattformen sicherzustellen, insbesondere mit Cloud-Backtesting, sollten Sie dem Strategiekonstruktor keine Parameter hinzufügen:

// Korrekt: Konstruktor ohne Parameter
public class SmaStrategy : Strategy
{
	public SmaStrategy()
	{
		// Parameterinitialisierung
	}
}

// Falsch: Konstruktor mit Parametern
public class SmaStrategy : Strategy
{
	public SmaStrategy(int longLength, int shortLength) // Diesen Ansatz nicht verwenden
	{
		// ...
	}
}

StockSharp-Plattformen erstellen Strategieinstanzen mit einem parameterlosen Konstruktor. Wenn Ihre Strategie einen Konstruktor mit Parametern erfordert, wird sie nicht korrekt initialisiert.

StrategyParam statt normaler Eigenschaften verwenden

Vorteile von StrategyParam

Statt normale C#-Eigenschaften zu erstellen und anschließend die Methoden Save und Load zu überschreiben, verwenden Sie StrategyParam<T> für alle anpassbaren Parameter:

// Korrekt: Verwendung von StrategyParam
private readonly StrategyParam<int> _longSmaLength;

public int LongSmaLength
{
	get => _longSmaLength.Value;
	set => _longSmaLength.Value = value;
}

public SmaStrategy()
{
	_longSmaLength = Param(nameof(LongSmaLength), 80)
						.SetDisplay("Länge der langen SMA", string.Empty, "Grundeinstellungen");
}

// Falsch: Verwendung normaler Eigenschaften
private int _longSmaLength = 80; // Diesen Ansatz nicht verwenden

public int LongSmaLength
{
	get => _longSmaLength;
	set => _longSmaLength = value;
}

Über StrategyParam<T> erstellte Parameter werden automatisch:

  • In den Benutzeroberflächen der Plattformen angezeigt
  • Ohne Überschreiben der Methoden Save und Load gespeichert und geladen
  • In der Optimierung verwendet
  • Beim Senden an Cloud-Backtesting korrekt serialisiert

Arbeit mit der Benutzeroberfläche

Abstraktionen statt direktem UI-Zugriff verwenden

Statt direkt auf Elemente der Benutzeroberfläche zuzugreifen, verwenden Sie die von StockSharp bereitgestellten Abstraktionen:

// Korrekter Ansatz: Verwendung von IChart
protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

	// Den von der Laufzeitumgebung bereitgestellten Chart abrufen
	_chart = GetChart();

	if (_chart != null)
	{
		// Chart ist verfügbar (z. B. in Designer oder Shell)
		InitChart();
	}
	else
	{
		// Chart ist nicht verfügbar (z. B. in Runner oder Cloud-Backtesting)
		// Strategie arbeitet ohne Visualisierung weiter
	}
}

private void InitChart()
{
	// Chart über die abstrakte Schnittstelle konfigurieren
	_chart.ClearAreas();
	var area = _chart.AddArea();
	_chartCandleElement = area.AddCandles();
	// ...
}

Die Methode Strategy.GetChart() gibt eine IChart-Schnittstelle zurück, wenn in der aktuellen Laufzeitumgebung ein Chart verfügbar ist. Wenn die Strategie im Konsolen-Runner oder im Cloud-Backtesting ausgeführt wird, wo keine grafische Oberfläche vorhanden ist, gibt die Methode null zurück.

Die Schnittstelle IChart stellt Methoden zur Arbeit mit Charts bereit:

  • AddArea - zum Hinzufügen eines Bereichs zum Chart
  • RemoveArea - zum Entfernen eines Bereichs
  • AddElement - zum Hinzufügen eines Elements zum Chart
  • RemoveElement - zum Entfernen eines Elements
  • Reset - zum Zurücksetzen von Elementwerten

Chartverfügbarkeit prüfen

Prüfen Sie immer die Chartverfügbarkeit, bevor Sie den Chart verwenden:

private void DrawCandlesAndIndicators(ICandleMessage candle, IIndicatorValue longSma, IIndicatorValue shortSma)
{
	if (_chart == null) return; // Wichtige Prüfung

	var data = _chart.CreateData();
	data.Group(candle.OpenTime)
		.Add(_chartCandleElement, candle)
		.Add(_longSmaIndicatorElement, longSma)
		.Add(_shortSmaIndicatorElement, shortSma);
	_chart.Draw(data);
}

Threads und Synchronisierung

Zusätzliche Threads vermeiden

In StockSharp müssen Sie keine zusätzlichen Threads für die Datenverarbeitung erstellen. Alle Ereignisse (Marktdaten, Transaktionen) kommen in einem einzigen Thread an:

// Korrekt: Verwendung standardmäßiger Ereignishandler
private void ProcessCandle(ICandleMessage candle)
{
	// Kerze im Hauptthread verarbeiten
	var longSmaIsFormedPrev = _longSma.IsFormed;
	var ls = _longSma.Process(candle);
	var ss = _shortSma.Process(candle);

	// ...
}

// Falsch: zusätzliche Threads erstellen
private void ProcessCandle(ICandleMessage candle)
{
	// Nicht so vorgehen
	Task.Run(() => {
		var longSmaIsFormedPrev = _longSma.IsFormed;
		// ...
	});
}

Synchronisationsobjekte vermeiden

Da alle Ereignisse in einem einzigen Thread verarbeitet werden, müssen keine Synchronisationsobjekte verwendet werden:

// Korrekt: normale Verarbeitung ohne Synchronisierung
private void ProcessCandle(ICandleMessage candle)
{
	var ls = _longSma.Process(candle);
	var ss = _shortSma.Process(candle);
	// ...
}

// Falsch: unnötige Synchronisierung
private readonly object _syncLock = new object(); // Nicht erforderlich

private void ProcessCandle(ICandleMessage candle)
{
	lock (_syncLock) // Nicht erforderlich
	{
		var ls = _longSma.Process(candle);
		// ...
	}
}

Externe Ressourcen

StockSharp-Infrastruktur verwenden

Statt direkt auf externe Ressourcen wie Dateien, Datenbanken oder Netzwerk zuzugreifen, verwenden Sie die von den StockSharp-Plattformen bereitgestellten Möglichkeiten:

// Korrekt: integrierte Mechanismen zur Datenspeicherung verwenden
protected override void OnStopped()
{
	// Daten werden automatisch über Strategieparameter gespeichert
	base.OnStopped();
}

// Falsch: direkter Zugriff auf externe Ressourcen
protected override void OnStopped()
{
	// Nicht so vorgehen
	File.WriteAllText("results.txt", $"PnL: {PnL}");

	// oder so
	using (var connection = new SqlConnection("..."))
	{
		// ...
	}

	base.OnStopped();
}

Datenspeicherung

Zum Speichern von Strategieergebnissen verwenden Sie:

Save- und Load-Methoden

Die Methoden Strategy.Save und Strategy.Load sind speziell zum Speichern zusätzlicher Strategiedaten vorgesehen, die keine Einstellungen oder Parameter sind. Dies ist der ideale Ort, um Daten zu speichern, die zur Wiederherstellung des Strategiezustands benötigt werden:

public override void Save(SettingsStorage settings)
{
	base.Save(settings); // Zuerst Strategieparameter speichern

	// Dann benutzerdefinierte Daten speichern
	settings.SetValue("CustomState", _customState);
	settings.SetValue("LastSignalTime", _lastSignalTime);
}

public override void Load(SettingsStorage settings)
{
	base.Load(settings); // Zuerst Strategieparameter laden

	// Dann benutzerdefinierte Daten laden
	if (settings.Contains("CustomState"))
		_customState = settings.GetValue<string>("CustomState");

	if (settings.Contains("LastSignalTime"))
		_lastSignalTime = settings.GetValue<DateTimeOffset>("LastSignalTime");
}

Die wichtigsten konfigurierbaren Parameter sollten jedoch weiterhin über StrategyParam<T> implementiert werden, wie oben beschrieben, da dadurch ihre automatische Anzeige in der Benutzeroberfläche sichergestellt wird.

Marktdatenabonnement

Regeln statt direktem Abonnement verwenden

Für die Verarbeitung von Marktdaten wird empfohlen, das Ereignismodell und Regeln zu verwenden:

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

	_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
	_longSma = new SimpleMovingAverage { Length = LongSmaLength };

	Indicators.Add(_shortSma);
	Indicators.Add(_longSma);

	var subscription = new Subscription(Serie, Security);

	// Korrekt: Regeln für die Datenverarbeitung verwenden
	Connector
		.WhenCandlesFinished(subscription)
		.Do(ProcessCandle)
		.Apply(this);

	Subscribe(subscription);
}

Regeln haben gegenüber normalen Ereignishandlern mehrere wichtige Vorteile:

  1. Automatisches Abmelden - Regeln melden sich automatisch von Ereignissen ab, wenn die Strategie stoppt oder wenn sie nicht mehr benötigt werden. Sie müssen Abonnements nicht manuell verwalten.

  2. High-Level-API - Regeln bieten eine verständlichere und bequemere Schnittstelle als standardmäßige Ereignishandler. Beispielsweise ist WhenCandlesFinished deutlich klarer als das Abonnieren des Ereignisses CandleReceived mit anschließender Prüfung des Kerzenzustands.

  3. Kombinieren von Bedingungen - Regeln können mit Operatoren wie And, Or und anderen kombiniert werden, um komplexe Aktivierungsbedingungen zu erstellen:

// Beispiel für das Kombinieren von Regeln
var tickSub = new Subscription(DataType.Ticks, Security);

tickSub
	.WhenTickTradeReceived(this)
	.And(Portfolio.WhenChanged(Connector))
	.Do(() => {
		// Code, der nur ausgeführt wird, wenn es einen neuen Trade gibt
		// und sich der Portfoliosaldo ändert
	})
	.Apply(this);

Subscribe(tickSub);
  1. Lebenszyklusverwaltung - Regeln können einmalig gemacht werden (Once()), Stornierungsbedingungen erhalten (Until()), verzögerte Aktionen hinzufügen usw.

Beispiel einer kompatiblen Strategie

Unten sehen Sie ein Beispiel für eine Strategie, die alle Empfehlungen befolgt und auf allen StockSharp-Plattformen korrekt funktioniert:

public class SmaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _series;
	private readonly StrategyParam<int> _longSmaLength;
	private readonly StrategyParam<int> _shortSmaLength;

	public DataType Serie
	{
		get => _series.Value;
		set => _series.Value = value;
	}

	public int LongSmaLength
	{
		get => _longSmaLength.Value;
		set => _longSmaLength.Value = value;
	}

	public int ShortSmaLength
	{
		get => _shortSmaLength.Value;
		set => _shortSmaLength.Value = value;
	}

	private SimpleMovingAverage _longSma;
	private SimpleMovingAverage _shortSma;
	private IChart _chart;
	private IChartCandleElement _chartCandleElement;
	private IChartIndicatorElement _longSmaIndicatorElement;
	private IChartIndicatorElement _shortSmaIndicatorElement;

	public SmaStrategy()
	{
		_longSmaLength = Param(nameof(LongSmaLength), 80)
							.SetDisplay("Länge der langen SMA", string.Empty, "Grundeinstellungen")
							.SetCanOptimize(true);

		_shortSmaLength = Param(nameof(ShortSmaLength), 30)
							.SetDisplay("Länge der kurzen SMA", string.Empty, "Grundeinstellungen")
							.SetCanOptimize(true);

		_series = Param(nameof(Serie), TimeSpan.FromMinutes(15).TimeFrame())
					.SetDisplay("Serie", string.Empty, "Grundeinstellungen");
	}

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

		_longSma = new SimpleMovingAverage { Length = LongSmaLength };
		_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };

		Indicators.Add(_shortSma);
		Indicators.Add(_longSma);

		// Chart initialisieren, falls verfügbar
		_chart = GetChart();
		if (_chart != null)
			InitChart();

		var subscription = new Subscription(Serie, Security);

		Connector
			.WhenCandlesFinished(subscription)
			.Do(ProcessCandle)
			.Apply(this);

		Subscribe(subscription);
	}

	private void InitChart()
	{
		_chart.ClearAreas();
		var area = _chart.AddArea();

		_chartCandleElement = area.AddCandles();

		_longSmaIndicatorElement = area.AddIndicator(_longSma);
		_longSmaIndicatorElement.Color = System.Drawing.Color.Brown;
		_longSmaIndicatorElement.DrawStyle = DrawStyles.Line;

		_shortSmaIndicatorElement = area.AddIndicator(_shortSma);
		_shortSmaIndicatorElement.Color = System.Drawing.Color.Blue;
		_shortSmaIndicatorElement.DrawStyle = DrawStyles.Line;
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		var ls = _longSma.Process(candle);
		var ss = _shortSma.Process(candle);

		// Im Chart zeichnen, falls verfügbar
		if (_chart != null)
		{
			var data = _chart.CreateData();
			data.Group(candle.OpenTime)
				.Add(_chartCandleElement, candle)
				.Add(_longSmaIndicatorElement, ls)
				.Add(_shortSmaIndicatorElement, ss);
			_chart.Draw(data);
		}

		if (!_longSma.IsFormed)
			return;

		var isShortLessCurrent = _shortSma.GetCurrentValue() < _longSma.GetCurrentValue();
		var isShortLessPrev = _shortSma.GetValue(1) < _longSma.GetValue(1);

		if (isShortLessCurrent == isShortLessPrev)
			return;

		// Handelslogik
		var volume = Volume + Math.Abs(Position);

		if (isShortLessCurrent)
			SellMarket(volume);
		else
			BuyMarket(volume);
	}
}

Siehe auch