Przejdź do treści

C# do handlu algorytmicznego

Czym jest C#

C# to obiektowy język programowania używany do tworzenia szerokiej gamy usług i aplikacji. Jego przejrzysta, uporządkowana składnia sprawia, że jest łatwy do nauki i czytania, nawet dla początkujących, zapewniając jednocześnie uporządkowane podejście do pisania zwartego i wielokrotnego użytku kodu.

Algorytmy C# w jedną minutę!

  • Nawet jeśli nigdy wcześniej nie pracowałeś z C#, stworzenie i wdrożenie pierwszego bota lub wskaźnika zajmuje tylko kilka minut.
  • Ponieważ C# jest łatwy w użyciu, możesz szybko przepisać dowolne fragmenty kodu z tej dokumentacji, aby dostosować je do swoich potrzeb.
  • Korzystając z C#, możesz uzyskać dostęp do dużej liczby bibliotek zawierających predefiniowane klasy i metody. Te metody mogą efektywnie obsługiwać typowe zadania, pozostawiając Ci swobodę rozwiązywania złożonych problemów handlowych.
  • W C# możesz pisać kod, który nie blokuje wątków serwera podczas wykonywania. Innymi słowy, możesz uruchamiać określone zadania jednocześnie z innymi zadaniami.

Czym jest .NET?

Aby programy C# mogły działać, ich kod źródłowy musi zostać skompilowany do języka pośredniego (Intermediate Language, IL). Ten kod IL jest zgodny ze specyfikacją Common Language Infrastructure (CLI) i jest później kompilowany do natywnych instrukcji maszynowych w czasie wykonywania. Framework .NET zapewnia wirtualne środowisko wykonawcze zbudowane na rozbudowanych bibliotekach klas i Common Language Runtime (CLR), implementacji CLI firmy Microsoft.

Nie wchodząc w techniczne zawiłości, .NET spełnia następujące funkcje:

  • Ułatwienie tworzenia i wykonywania aplikacji: .NET SDK zawiera już kilka wbudowanych kompilatorów i silników kompilacji, eliminując potrzebę tworzenia jakichkolwiek niestandardowych rozwiązań.
  • Dostarczanie bibliotek środowiska wykonawczego: Dodając nowe typy danych lub kolekcje do swojego kodu, często przekonasz się, że .NET oferuje już odpowiednie klasy i metody do tego zadania.

Podstawy C#

Typy danych i deklaracje zmiennych

Typy danych to sposób kategoryzowania danych, dzięki czemu C# wie dokładnie, jak traktować zmienne i właściwości. W deklaracjach zmiennych/właściwości typy danych zawsze poprzedzają nazwę zmiennej/właściwości.

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

Alternatywnie możesz użyć słowa kluczowego var, aby uniknąć określania typu danych.

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

Obiekty i klasy

Pomyśl o obiektach jako o abstrakcjach namacalnych lub niematerialnych bytów. Te byty mogą mieć określone cechy (właściwości) i mogą wykonywać różne operacje (metody). Z kolei klasy służą jako szablony do tworzenia obiektów.

Ponieważ C# jest językiem obiektowym, klasy mogą również dziedziczyć właściwości i metody z innych klas. Rozważ poniższy przykład, w którym deklarujemy nową klasę NewBot, która dziedziczy z klasy Robot. Definiujemy również niektóre nowe właściwości klasy.

 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; }
}

Typy danych

Ponieważ C# jest językiem silnie typowanym, konieczne jest określenie typów danych podczas deklarowania zmiennych i właściwości klas. Krótko mówiąc, typy danych stanowią unikalne klasy z różnymi zestawami zachowań. Rozważ poniższe przykłady, w których różne typy danych są określone dla różnych właściwości klas i powiązanych parametrów.

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

W powyższym kodzie typ danych DataSeries reprezentuje listę wartości tylko do odczytu i dlatego jest zwykle używany do reprezentowania cen rynkowych.

Gdy istnieje potrzeba utworzenia parametrów wieloopcyjnych, możesz użyć wbudowanego typu danych enum, jak opisano poniżej. Możesz myśleć o enum jako o specjalnych klasach, które zawierają różne stałe.

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; }

Aby uzyskać dostęp do stałej zawartej w enum, użyj następującej składni.

1
var newOption = Option.First;

Jeśli parametr lub właściwość musi być liczbą, najczęściej będziesz używać typów danych int (liczba całkowita) lub double (liczba dziesiętna). Chociaż typ double ma mniejszą precyzję niż typ decimal, jest również mniej zasobożerny.

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

Wreszcie, typ bool zazwyczaj reprezentuje dane wejściowe typu tak lub nie. Wartości tak i nie odpowiadają odpowiednio true i false.

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

Przestrzenie nazw

Przestrzenie nazw działają jako wyznaczone kolekcje klas. Po przypisaniu klasy do przestrzeni nazw można później uzyskać do niej dostęp, używając notacji Namespace.ClassName. Przypiszemy nasz NewBot do przestrzeni nazw CoolTradingBots.

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

Metody klas

Metody klas są definiowane po deklaracji klasy. Wszystkie obiekty naszej klasy NewBot będą mogły wywołać metodę CustomTradeOperation(). Przyjmuje ona dwa argumenty, a mianowicie obiekt Symbol i obiekt double. Nasza metoda ma przyjąć symbol i wykonać jakąś akcję handlową dla określonego wolumenu

 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. 
        }
    }
}

Biblioteki klas

Biblioteki klas (a następnie metody klas) można uzyskać dostęp przez ich przestrzeń nazw, jak pokazano w poniższym przykładzie.

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

Alternatywnie możesz wpisać słowo kluczowe using na początku kodu, aby wyznaczyć określone przestrzenie nazw i uniknąć redundancji. Rozważ następujący fragment kodu.

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

Instrukcje warunkowe

Aby użyć instrukcji warunkowych, użyj słowa kluczowego, po którym następuje wyrażenie w nawiasach okrągłych. Poniższy przykład używa słowa kluczowego if, aby sfinalizować naszą metodę CustomTradingOperation(). W tym celu używamy metody EvaluateMarket(), która jest zdefiniowana w przestrzeni nazw Analytics.Actions.

 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!")
            }
        }
    }
}

Kolekcje

Kolekcje są definiowane jako kontenery, które mogą przechowywać jeden lub więcej obiektów określonej klasy.

Kolekcje są w pełni indeksowalne, co oznacza, że do ich członków można uzyskać dostęp, przekazując określoną wartość integer w nawiasach kwadratowych. Rozważ następujący przykład, w którym metoda Calculate() drukuje właściwości słupka, do którego uzyskano dostęp za pomocą jego indeksu.

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

Tworzenie rozszerzeń cTrader

W poniższym fragmencie tworzymy podstawowego robota handlowego, używając tylko wiedzy omówionej w powyższych sekcjach.

 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);
            } 
        }
    }
}

Operacje synchroniczne i asynchroniczne

Jak wspomniano wcześniej, C# w pełni obsługuje operacje asynchroniczne. Poniższy diagram definiuje podstawowy przykład tego, jak działania handlowe są wykonywane w wykonaniu synchronicznym.

graph TD
    A(Wykrycie sygnału<br>technicznego) ==> B(Wykonanie<br>zlecenia rynkowego)
    B ==> C(Osiągnięcie Take<br>Profit/Stop Loss)
    C ==> D(Zamknięcie pozycji)
    D ==> A

Wykonanie synchroniczne niesie ze sobą jedną istotną wadę. W powyższym przykładzie akcja wykonania zlecenia rynkowego w pełni zajmuje wszystkie wątki serwera, co oznacza, że Twój robot handlowy nie jest w stanie zrobić nic innego, zanim ta operacja nie zostanie zakończona.

Jest to mniej niż idealne, gdy chcesz szybko reagować na wydarzenia rynkowe i zmienność. Idealnie, Twój bot powinien być w stanie przechowywać różne akcje lub wstrzymywać pewne zadania, aby zaangażować się w inne, bardziej pilne działania. Rozszerzymy nasz przykład, aby lepiej odzwierciedlić sposób wykonywania operacji asynchronicznych.

graph TD
    A([Wykrycie sygnału<br>technicznego]) ==> B([Złożenie<br>zlecenia 'Buy']) & C([Złożenie zlecenia<br>'Sell' jako<br>zabezpieczenia]) ==> D([Zlecenie osiąga<br>Take Profit/<br>Stop Loss])
    D ==> E([Zamknięcie pozycji])
    E ==> A

W powyższym przykładzie nasz robot handlowy jednocześnie złożył zlecenia w obu kierunkach w celu zabezpieczenia swoich pozycji. W przeciwieństwie do operacji synchronicznych nie ma potrzeby czekania na zakończenie jednej operacji przed przystąpieniem do kolejnej. To znacznie rozszerza możliwości programistów w zakresie tworzenia wydajnych i niezawodnych robotów handlowych.

Należy pamiętać, że wykonanie asynchroniczne różni się od wielowątkowości:

  • W wykonaniu asynchronicznym wszystkie zadania są uruchamiane w tym samym wątku. Gdy są przechowywane lub wstrzymywane, zwalniają ten wątek, a gdy ich wykonanie jest kontynuowane później, odbywa się to w innym wątku wybranym z puli wątków.
  • W przypadku wielowątkowości wszystkie zadania rozpoczynają się w różnych wątkach i kontynuują wykonywanie w swoich początkowych wątkach. Nie ma przetasowania wątków.

cTrader nigdy nie wywołuje Twoich metod równolegle, więc nie musisz martwić się problemami z wielowątkowością.

Image title