Zum Inhalt

C# für algorithmisches Handeln

Was ist C#?

C# ist eine objektorientierte Programmiersprache, die zum Erstellen einer Vielzahl von Diensten und Anwendungen verwendet wird. Ihre klare, strukturierte Syntax macht sie leicht zu erlernen und zu lesen, selbst für Anfänger, und bietet gleichzeitig einen strukturierten Ansatz zum Schreiben von kompaktem und wiederverwendbarem Code.

C#-Algorithmen in einer Minute!

  • Selbst wenn Sie noch nie mit C# gearbeitet haben, dauert das Erstellen und Bereitstellen Ihres ersten Bots oder Indikators nur ein paar Minuten.
  • Da C# einfach zu verwenden ist, können Sie jeden Code-Schnipsel aus dieser Dokumentation schnell umschreiben, um ihn an Ihre Bedürfnisse anzupassen.
  • Bei der Verwendung von C# können Sie auf eine große Anzahl von Bibliotheken zugreifen, die vordefinierte Klassen und Methoden enthalten. Diese Methoden können gängige Aufgaben effizient erledigen und lassen Ihnen die Freiheit, komplexe Handelsprobleme zu lösen.
  • In C# können Sie Code schreiben, der bei der Ausführung keine Server-Threads blockiert. Mit anderen Worten, Sie können bestimmte Aufgaben gleichzeitig mit anderen Aufgaben starten.

Was ist .NET?

Damit C#-Programme ausgeführt werden können, muss ihr Quellcode in Intermediate Language (IL) kompiliert werden. Dieser IL-Code folgt der Common Language Infrastructure (CLI)-Spezifikation und wird später zur Laufzeit in native Maschinenanweisungen kompiliert. Das .NET-Framework bietet eine virtuelle Ausführungsumgebung, die auf umfangreichen Klassenbibliotheken und der Common Language Runtime (CLR), Microsofts Implementierung der CLI, aufbaut.

Ohne auf technische Feinheiten einzugehen, erfüllt .NET die folgenden Funktionen:

  • Erleichterung der Entwicklung und Ausführung von Anwendungen: Das .NET SDK enthält bereits mehrere integrierte Compiler und Build-Engines, sodass keine benutzerdefinierten Lösungen erstellt werden müssen.
  • Bereitstellung von Laufzeitbibliotheken: Wenn Sie Ihrem Code neue Datentypen oder Sammlungen hinzufügen, werden Sie oft feststellen, dass .NET bereits geeignete Klassen und Methoden für die Aufgabe bietet.

Die Grundlagen von C#

Datentypen und Variablendeklarationen

Datentypen sind ein Mittel zur Kategorisierung von Daten, damit C# genau weiß, wie Variablen und Eigenschaften zu behandeln sind. In Variablen-/Eigenschaftsdeklarationen stehen Datentypen immer vor dem Variablen-/Eigenschaftsnamen.

1
string developer = "I am developing cBots, plugins, and indicators!";

Alternativ können Sie das Schlüsselwort var verwenden, um die Angabe eines Datentyps zu vermeiden.

1
var developer = "I am developing cBots, plugins, and indicators!";

Objekte und Klassen

Stellen Sie sich Objekte als Abstraktionen von materiellen oder immateriellen Entitäten vor. Diese Entitäten können bestimmte Eigenschaften (Properties) haben und verschiedene Operationen (Methoden) ausführen. Klassen dienen wiederum als Vorlagen für die Objekterstellung.

Da C# eine objektorientierte Sprache ist, können Klassen auch Eigenschaften und Methoden von anderen Klassen erben. Betrachten Sie das folgende Beispiel, in dem wir eine neue Klasse NewBot deklarieren, die von der Klasse Robot erbt. Wir definieren auch einige neue Klasseneigenschaften.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* We use the colon sign to designate inheritance.
Additionally, we used expressions in square brackets to
specify the parameters that apply to the entire class */
[Robot(AccessRights = AccessRights.None)]
public class NewBot : Robot
{
    /* We declare a custom class property and make it
    read-only. */
    public string CustomProperty { get; }

    /* In this declaration, we define the default value of a
    custom parameter. */
    [Parameter("BotName", DefaultValue = "Traders First!")]

    /* We declare the BotName property which is changeable via
    the "BotName" parameter. */
    public string BotName { get; }

    /* We also declare the BotComment parameter.
    It can be both read and set. */
    [Parameter("BotComment", DefaultValue = "Our super-duper bot!")]
    public string BotComment { get; set; }
}

Datentypen

Da C# eine stark typisierte Sprache ist, ist es notwendig, Datentypen bei der Deklaration von Variablen und Klasseneigenschaften anzugeben. Kurz gesagt, Datentypen stellen einzigartige Klassen mit unterschiedlichen Verhaltensweisen dar. Betrachten Sie die folgenden Beispiele, in denen verschiedene Datentypen für unterschiedliche Klasseneigenschaften und zugehörige Parameter angegeben werden.

1
2
[Parameter("Price")]
public DataSeries Source { get; }

Im obigen Code stellt der Datentyp DataSeries eine schreibgeschützte Liste von Werten dar und wird daher typischerweise zur Darstellung von Marktpreisen verwendet.

Wenn es notwendig ist, Multi-Option-Parameter zu erstellen, können Sie den integrierten Datentyp enum verwenden, wie unten beschrieben. Sie können sich enum's als spezielle Klassen vorstellen, die verschiedene Konstanten enthalten.

1
2
3
4
5
6
7
8
9
public enum Option
{
    First,
    Second,
    Third
}

[Parameter("Option", DefaultValue = Option.Third)]
public Option SelectedOption { get; set; }

Um auf eine in einem enum enthaltene Konstante zuzugreifen, verwenden Sie die folgende Syntax.

1
var newOption = Option.First;

Wenn ein Parameter oder eine Eigenschaft eine Zahl sein muss, verwenden Sie meistens die Datentypen int (Ganzzahl) oder double (Dezimalzahl). Während der Typ double weniger Präzision als der Typ decimal hat, ist er auch weniger ressourcenintensiv.

1
2
[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
public int Periods { get; set; }

Schließlich repräsentiert der Typ bool typischerweise eine Ja- oder Nein-Eingabe. Die Werte Ja und Nein entsprechen true bzw. false.

1
2
[Parameter("Message", DefaultValue = true)]
public bool DisplayChartMessage { get; set; }

Namespaces

Namespaces fungieren als festgelegte Sammlungen von Klassen. Sobald Sie eine Klasse einem Namespace zuweisen, kann später über die Notation Namespace.ClassName darauf zugegriffen werden. Wir werden unseren NewBot dem Namespace CoolTradingBots zuweisen.

1
2
3
4
5
6
7
8
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ...
    }
}

Klassenmethoden

Klassenmethoden werden nach der Klassendeklaration definiert. Alle Objekte unserer Klasse NewBot können die Methode CustomTradeOperation() aufrufen. Sie nimmt zwei Argumente entgegen, nämlich ein Symbol-Objekt und ein double-Objekt. Unsere Methode soll ein Symbol entgegennehmen und eine Art Handelsaktion für das angegebene Volumen ausführen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        // We declare the CustomTradeOperation method. 
        protected override void CustomTradeOperation(string symbolName, double volume)

        {
            // This space is for declaring the method logic. 
        }
    }
}

Klassenbibliotheken

Auf Klassenbibliotheken (und folglich Klassenmethoden) kann über ihren Namespace zugegriffen werden, wie im folgenden Beispiel gezeigt.

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

Alternativ können Sie das Schlüsselwort using am Anfang Ihres Codes eingeben, um bestimmte Namespaces festzulegen und Redundanzen zu vermeiden. Betrachten Sie den folgenden Code-Ausschnitt.

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

Bedingte Anweisungen

Um bedingte Anweisungen zu verwenden, verwenden Sie ein Schlüsselwort gefolgt von einem Ausdruck in runden Klammern. Das folgende Beispiel verwendet das Schlüsselwort if, um unsere Methode CustomTradingOperation() zu finalisieren. Dazu verwenden wir die Methode EvaluateMarket(), die im Namespace Analytics.Actions definiert ist.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        protected override void CustomTradeOperation(string symbolName, double volume)
        {
            // We declare and initialize the 'result' variable.
            var result = Analytics.Action.EvaluateMarket();

            // We use a conditional statement based on the IsSuccessful property.
            if (result.IsSuccessful)
            {
                Print("Operation successful!")
            }
        }
    }
}

Collections

Collections werden als Container definiert, die ein oder mehrere Objekte einer bestimmten Klasse speichern können.

Collections sind vollständig indizierbar, was bedeutet, dass auf ihre Mitglieder zugegriffen werden kann, indem ein bestimmter integer-Wert in eckigen Klammern übergeben wird. Betrachten Sie das folgende Beispiel, in dem die Methode Calculate() die Eigenschaften eines Balkens ausgibt, auf den über seinen Index zugegriffen wird.

1
2
3
4
5
public override void Calculate(int index)
{
       var bar = Bars[index];
       Print($"{bar.Open} | {bar.High} | {bar.Low} | {bar.Close}");
}

cTrader-Erweiterungen erstellen

Im folgenden Ausschnitt erstellen wir einen grundlegenden Handelsroboter, indem wir nur das in den obigen Abschnitten behandelte Wissen verwenden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System; 

namespace Spotware.cBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class SuperAwesomeBot : Robot
    {

        /* In the below method, we define the operation(s) that our bot should
        perform when it is launched. */
        protected override void OnStart()
        {
            var result = ExecuteMarketOrder(TradeType.Buy, symbolName, 10000);

            /* We use a conditional statement using the IsSuccessful property of the
            TradeResult object. */
            if (result.IsSuccessful)
            {
                var position = result.Position;
                Print ("Position entry price is {0}", position.EntryPrice);
            } 
        }
    }
}

Synchrone und asynchrone Operationen

Wie zuvor erwähnt, unterstützt C# asynchrone Operationen vollständig. Das folgende Diagramm definiert ein grundlegendes Beispiel dafür, wie Handelsaktivitäten bei synchroner Ausführung durchgeführt werden.

graph TD
    A(Technisches Signal<br>erkennen) ==> B(Market-Order<br>ausführen)
    B ==> C(Take Profit/<br>Stop Loss<br>erreicht)
    C ==> D(Position schließen)
    D ==> A

Die synchrone Ausführung hat einen wichtigen Nachteil. Im obigen Beispiel belegt die Aktion der Ausführung einer Market-Order alle Server-Threads vollständig, was bedeutet, dass Ihr Handelsroboter nichts anderes tun kann, bevor diese Operation abgeschlossen ist.

Dies ist weniger als ideal, wenn Sie schnell auf Marktereignisse und Volatilität reagieren möchten. Idealerweise sollte Ihr Bot in der Lage sein, verschiedene Aktionen zu speichern oder bestimmte Aufgaben zurückzustellen, um sich anderen, dringenderen Aktivitäten zu widmen. Wir werden unser Beispiel erweitern, um besser widerzuspiegeln, wie asynchrone Operationen durchgeführt werden.

graph TD
    A([Technisches Signal<br>erkennen]) ==> B(['Buy'-Order<br>platzieren]) & C(['Sell'-Order zur<br>Absicherung<br>platzieren]) ==> D([Eine Order<br>erreicht<br>Take Profit/<br>Stop Loss])
    D ==> E([Position schließen])
    E ==> A

Im obigen Beispiel hat unser Handelsroboter gleichzeitig Orders in beide Richtungen platziert, um seine Positionen abzusichern. Im Gegensatz zu synchronen Operationen ist es nicht erforderlich, auf den Abschluss einer Operation zu warten, bevor mit einer anderen fortgefahren wird. Dies erweitert die Möglichkeiten der Entwickler zur Erstellung effizienter und zuverlässiger Handelsroboter erheblich.

Beachten Sie, dass asynchrone Ausführung sich von Multi-Threading unterscheidet:

  • Bei asynchroner Ausführung werden alle Aufgaben im selben Thread gestartet. Wenn sie gespeichert oder zurückgestellt werden, geben sie diesen Thread frei, und wenn ihre Ausführung später fortgesetzt wird, erfolgt dies in einem anderen Thread, der aus dem Thread-Pool ausgewählt wird.
  • Beim Multi-Threading starten alle Aufgaben in verschiedenen Threads und setzen ihre Ausführung in ihren ursprünglichen Threads fort. Es gibt kein Thread-Shuffling.

cTrader ruft Ihre Methoden niemals parallel auf, sodass Sie sich keine Gedanken über Multi-Threading-Probleme machen müssen.

Image title