Vai al contenuto

C# per il trading algoritmico

Cos'è C#

C# è un linguaggio di programmazione orientato agli oggetti utilizzato per creare un'ampia gamma di servizi e applicazioni. La sua sintassi chiara e strutturata lo rende facile da imparare e leggere, anche per i principianti, fornendo al contempo un approccio strutturato per scrivere codice compatto e riutilizzabile.

Algoritmi in C# in un minuto!

  • Anche se non hai mai lavorato con C# prima, creare e distribuire il tuo primo bot o indicatore richiede solo un paio di minuti.
  • Poiché C# è facile da usare, puoi riscrivere rapidamente qualsiasi frammento di codice di questa documentazione per adattarlo alle tue esigenze.
  • Quando utilizzi C#, puoi accedere a un gran numero di librerie contenenti classi e metodi predefiniti. Questi metodi possono gestire in modo efficiente attività comuni, lasciandoti libero di risolvere problemi di trading complessi.
  • In C#, puoi scrivere codice che non blocca i thread del server durante l'esecuzione. In altre parole, puoi avviare determinate attività contemporaneamente ad altre attività.

Cos'è .NET?

Affinché i programmi C# vengano eseguiti, il loro codice sorgente deve essere compilato in Intermediate Language (IL). Questo codice IL segue le specifiche della Common Language Infrastructure (CLI) e viene successivamente compilato in istruzioni macchina native in fase di esecuzione. Il framework .NET fornisce un ambiente di esecuzione virtuale basato su ampie librerie di classi e sul Common Language Runtime (CLR), l'implementazione Microsoft della CLI.

Senza entrare in dettagli tecnici, .NET svolge le seguenti funzioni:

  • Facilitare lo sviluppo e l'esecuzione di applicazioni: .NET SDK contiene già diversi compilatori e motori di compilazione integrati, eliminando la necessità di creare soluzioni personalizzate.
  • Fornire librerie di runtime: quando aggiungi nuovi tipi di dati o collezioni al tuo codice, spesso scoprirai che .NET offre già classi e metodi adatti all'attività.

Le basi di C#

Tipi di dati e dichiarazioni di variabili

I tipi di dati sono un mezzo per categorizzare i dati in modo che C# sappia esattamente come trattare variabili e proprietà. Nelle dichiarazioni di variabili/proprietà, i tipi di dati precedono sempre il nome della variabile/proprietà.

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

In alternativa, puoi utilizzare la parola chiave var per evitare di specificare un tipo di dati.

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

Oggetti e classi

Pensa agli oggetti come astrazioni di entità tangibili o intangibili. Queste entità possono avere determinate caratteristiche (proprietà) e possono eseguire varie operazioni (metodi). A loro volta, le classi fungono da modelli per la creazione di oggetti.

Poiché C# è un linguaggio orientato agli oggetti, le classi possono anche ereditare proprietà e metodi da altre classi. Considera il seguente esempio in cui dichiariamo una nuova classe NewBot che eredita dalla classe Robot. Definiamo anche alcune nuove proprietà della classe.

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

Tipi di dati

Poiché C# è un linguaggio fortemente tipizzato, è necessario specificare i tipi di dati quando si dichiarano variabili e proprietà di classe. In breve, i tipi di dati costituiscono classi uniche con diversi insiemi di comportamenti. Considera i seguenti esempi in cui vengono specificati vari tipi di dati per diverse proprietà di classe e parametri correlati.

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

Nel codice sopra, il tipo di dati DataSeries rappresenta un elenco di valori di sola lettura e viene, pertanto, tipicamente utilizzato per rappresentare i prezzi di mercato.

Quando è necessario creare parametri multi-opzione, puoi utilizzare il tipo di dati integrato enum come descritto di seguito. Puoi pensare agli enum come classi speciali che contengono varie costanti.

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

Per accedere a una costante contenuta in un enum, utilizza la seguente sintassi.

1
var newOption = Option.First;

Se un parametro o una proprietà deve essere un numero, utilizzerai più spesso i tipi di dati int (intero) o double (decimale). Sebbene il tipo double abbia meno precisione del tipo decimal, richiede anche meno risorse.

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

Infine, il tipo bool rappresenta tipicamente un input sì o no. I valori sì e no corrispondono rispettivamente a true e false.

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

Namespace

I namespace fungono da raccolte designate di classi. Una volta assegnata una classe a un namespace, può essere successivamente accessibile utilizzando la notazione Namespace.ClassName. Assegneremo il nostro NewBot al namespace CoolTradingBots.

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

Metodi di classe

I metodi di classe vengono definiti dopo la dichiarazione della classe. Tutti gli oggetti della nostra classe NewBot saranno in grado di chiamare il metodo CustomTradeOperation(). Questo metodo accetta due argomenti, ovvero un oggetto Symbol e un oggetto double. Il nostro metodo dovrebbe prendere un simbolo ed eseguire un'azione di trading per il volume specificato

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

Librerie di classe

Le librerie di classe (e, di conseguenza, i metodi di classe) sono accessibili tramite il loro namespace come mostrato nell'esempio seguente.

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

In alternativa, puoi digitare la parola chiave using all'inizio del tuo codice per designare determinati namespace ed evitare ridondanze. Considera il seguente frammento di codice.

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

Istruzioni condizionali

Per utilizzare le istruzioni condizionali, usa una parola chiave seguita da un'espressione tra parentesi tonde. L'esempio seguente utilizza la parola chiave if per finalizzare il nostro metodo CustomTradingOperation(). Per farlo, utilizziamo il metodo EvaluateMarket() che è definito nel namespace 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!")
            }
        }
    }
}

Collezioni

Le collezioni sono definite come contenitori che possono memorizzare uno o più oggetti di una particolare classe.

Le collezioni sono completamente indicizzabili, il che significa che i loro membri possono essere accessibili passando un determinato valore integer tra parentesi quadre. Considera il seguente esempio in cui il metodo Calculate() stampa le proprietà di una barra accessibile tramite il suo indice.

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

Crea estensioni cTrader

Nel frammento seguente, creiamo un robot di trading di base utilizzando solo le conoscenze trattate nelle sezioni precedenti.

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

Operazioni sincrone e asincrone

Come affermato in precedenza, C# supporta completamente le operazioni asincrone. Il diagramma seguente definisce un esempio di base di come le attività di trading vengono eseguite in esecuzione sincrona.

graph TD
    A(Rilevare un<br>segnale tecnico) ==> B(Eseguire un<br>ordine a mercato)
    B ==> C(Take Profit/<br>Stop Loss<br>raggiunto)
    C ==> D(Chiudere la<br>posizione)
    D ==> A

L'esecuzione sincrona presenta un importante svantaggio. Nell'esempio precedente, l'azione di eseguire un ordine di mercato occupa completamente tutti i thread del server, il che significa che il tuo robot di trading non è in grado di fare nient'altro prima che questa operazione sia completata.

Questo è tutt'altro che ideale quando vuoi reagire rapidamente agli eventi di mercato e alla volatilità. Idealmente, il tuo bot dovrebbe essere in grado di memorizzare varie azioni o mettere in attesa determinate attività per impegnarsi in altre attività più urgenti. Espanderemo il nostro esempio per riflettere meglio come vengono eseguite le operazioni asincrone.

graph TD
    A([Rilevare un<br>segnale tecnico]) ==> B([Piazzare un<br>ordine 'Buy']) & C([Piazzare un ordine<br>'Sell' di<br>copertura]) ==> D([Un ordine<br>raggiunge<br>Take Profit/<br>Stop Loss])
    D ==> E([Chiudere la<br>posizione])
    E ==> A

Nell'esempio precedente, il nostro robot di trading ha piazzato simultaneamente ordini in entrambe le direzioni per coprire le sue posizioni. A differenza delle operazioni sincrone, non è necessario attendere il completamento di un'operazione prima di procedere con un'altra. Questo espande significativamente le opportunità degli sviluppatori di creare robot di trading efficienti e affidabili.

Nota che l'esecuzione asincrona è diversa dal multi-threading:

  • Nell'esecuzione asincrona, tutte le attività vengono avviate sullo stesso thread. Quando vengono memorizzate o messe in attesa, liberano questo thread e, quando la loro esecuzione continua successivamente, avviene su un thread diverso scelto dal pool di thread.
  • Nel multi-threading, tutte le attività iniziano su thread diversi e continuano la loro esecuzione sui loro thread iniziali. Non c'è scambio di thread.

cTrader non invoca mai i tuoi metodi in parallelo, quindi non devi preoccuparti dei problemi di multi-threading.

Image title