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
SaveundLoadgespeichert 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:
- Strategieparameter für Einstellungen und Konfiguration
- Integrierte Speichermechanismen in Designer und Shell
- Statistics zum Sammeln von Handelskennzahlen
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:
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.
High-Level-API - Regeln bieten eine verständlichere und bequemere Schnittstelle als standardmäßige Ereignishandler. Beispielsweise ist
WhenCandlesFinisheddeutlich klarer als das Abonnieren des EreignissesCandleReceivedmit anschließender Prüfung des Kerzenzustands.Kombinieren von Bedingungen - Regeln können mit Operatoren wie
And,Orund 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);
- 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);
}
}