Ir para o conteúdo

C# para negociação algorítmica

O que é C#

C# é uma linguagem de programação orientada a objetos usada para criar uma ampla gama de serviços e aplicações. A sua sintaxe clara e estruturada torna-a fácil de aprender e ler, mesmo para iniciantes, ao mesmo tempo que oferece uma abordagem estruturada para escrever código compacto e reutilizável.

Algoritmos C# em um minuto!

  • Mesmo que nunca tenha trabalhado com C# antes, criar e implementar o seu primeiro robô ou indicador leva apenas alguns minutos.
  • Como o C# é fácil de usar, pode rapidamente reescrever quaisquer trechos de código desta documentação para atender às suas necessidades.
  • Ao usar C#, pode aceder a um grande número de bibliotecas contendo classes e métodos predefinidos. Estes métodos podem lidar eficientemente com tarefas comuns, deixando-o livre para resolver problemas complexos de negociação.
  • Em C#, pode escrever código que não bloqueia os threads do servidor durante a execução. Por outras palavras, pode iniciar certas tarefas simultaneamente com outras tarefas.

O que é o .NET?

Para que os programas C# sejam executados, o seu código-fonte deve ser compilado em Linguagem Intermédia (IL). Este código IL segue a especificação Common Language Infrastructure (CLI) e é posteriormente compilado em instruções nativas da máquina em tempo de execução. A framework .NET fornece um ambiente de execução virtual construído sobre extensas bibliotecas de classes e o Common Language Runtime (CLR), a implementação da Microsoft do CLI.

Sem entrar em detalhes técnicos, o .NET cumpre as seguintes funções:

  • Facilitar o desenvolvimento e execução de aplicações: O SDK .NET já contém vários compiladores e motores de compilação integrados, eliminando a necessidade de criar soluções personalizadas.
  • Fornecer bibliotecas de tempo de execução: Ao adicionar novos tipos de dados ou coleções ao seu código, frequentemente descobrirá que o .NET já oferece classes e métodos adequados para a tarefa.

Os fundamentos do C#

Tipos de dados e declarações de variáveis

Os tipos de dados são uma forma de categorizar dados para que o C# saiba exatamente como tratar variáveis e propriedades. Nas declarações de variáveis/propriedades, os tipos de dados sempre precedem o nome da variável/propriedade.

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

Alternativamente, pode usar a palavra-chave var para evitar especificar um tipo de dados.

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

Objetos e classes

Pense nos objetos como abstrações de entidades tangíveis ou intangíveis. Estas entidades podem ter certas características (propriedades) e podem realizar várias operações (métodos). Por sua vez, as classes servem como modelos para a criação de objetos.

Como o C# é uma linguagem orientada a objetos, as classes também podem herdar propriedades e métodos de outras classes. Considere o seguinte exemplo no qual declaramos uma nova classe NewBot que herda da classe Robot. Também definimos algumas novas propriedades de 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; }
}

Tipos de dados

Como o C# é uma linguagem fortemente tipada, é necessário especificar tipos de dados ao declarar variáveis e propriedades de classe. Em resumo, os tipos de dados constituem classes únicas com diferentes conjuntos de comportamentos. Considere os seguintes exemplos nos quais vários tipos de dados são especificados para diferentes propriedades de classe e parâmetros relacionados.

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

No código acima, o tipo de dados DataSeries representa uma lista somente leitura de valores e, portanto, é tipicamente usado para representar preços de mercado.

Quando há necessidade de criar parâmetros com múltiplas opções, pode usar o tipo de dados integrado enum como detalhado abaixo. Pode pensar nos enum como classes especiais que contêm várias constantes.

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

Para aceder a uma constante contida num enum, use a seguinte sintaxe.

1
var newOption = Option.First;

Se um parâmetro ou uma propriedade precisar de ser um número, na maioria das vezes usará os tipos de dados int (inteiro) ou double (decimal). Embora o tipo double tenha menos precisão que o tipo decimal, também consome menos recursos.

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

Por último, o tipo bool tipicamente representa uma entrada sim ou não. Os valores sim e não correspondem a true e false, respetivamente.

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

Namespaces

Os namespaces atuam como coleções designadas de classes. Uma vez que atribua uma classe a um namespace, ela pode ser acedida posteriormente usando a notação Namespace.NomeDaClasse. Vamos atribuir o nosso NewBot ao namespace CoolTradingBots.

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

Métodos de classe

Os métodos de classe são definidos após a declaração da classe. Todos os objetos da nossa classe NewBot poderão chamar o método CustomTradeOperation(). Ele recebe dois argumentos, nomeadamente um objeto Symbol e um objeto double. O nosso método deve receber um símbolo e executar algum tipo de ação de negociação para o volume especificado

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

Bibliotecas de classes

As bibliotecas de classes (e, subsequentemente, os métodos de classe) podem ser acedidas pelo seu namespace, como mostrado no exemplo abaixo.

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

Alternativamente, pode digitar a palavra-chave using no início do seu código para designar certos namespaces e evitar redundâncias. Considere o seguinte trecho de código.

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

Declarações condicionais

Para usar declarações condicionais, use uma palavra-chave seguida de uma expressão entre parênteses. O exemplo abaixo usa a palavra-chave if para finalizar o nosso método CustomTradingOperation(). Para isso, usamos o método EvaluateMarket() que é definido no 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!")
            }
        }
    }
}

Coleções

As coleções são definidas como contentores que podem armazenar um ou mais objetos de uma classe particular.

As coleções são totalmente indexáveis, o que significa que os seus membros podem ser acedidos passando um certo valor integer entre parênteses retos. Considere o seguinte exemplo no qual o método Calculate() imprime as propriedades de uma barra acedida através do seu índice.

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

Criar extensões cTrader

No trecho abaixo, criamos um robô de negociação básico usando apenas o conhecimento abordado nas secções acima.

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

Operações síncronas e assíncronas

Como mencionado anteriormente, o C# suporta totalmente operações assíncronas. O diagrama abaixo define um exemplo básico de como as atividades de negociação são realizadas na execução síncrona.

graph TD
    A(Encontrar um Sinal Técnico) ==> B(Executar uma Ordem <br>de Mercado) 
    B ==> C(Take Profit/Stop Loss <br>Atingido) 
    C ==> D(Fechar a Posição)
    D ==> A

A execução síncrona tem uma desvantagem importante. No exemplo acima, a ação de executar uma ordem de mercado ocupa totalmente todas as threads do servidor, o que significa que o seu robô de negociação não pode fazer mais nada antes que esta operação seja concluída.

Isto é menos que ideal quando se quer reagir rapidamente a eventos de mercado e volatilidade. Idealmente, o seu bot deveria ser capaz de armazenar várias ações ou colocar certas tarefas em espera para se envolver em outras atividades mais urgentes. Vamos expandir o nosso exemplo para refletir melhor como as operações assíncronas são realizadas.

graph TD
    A([Encontrar um Sinal Técnico]) ==> B([Colocar uma Ordem <br>de Compra]) & C([Colocar uma Ordem de<br> Venda para Cobertura]) ==> D([Uma Ordem Atinge o Seu <br>Take Profit/Stop Loss]) 
    D ==> E([Fechar a Posição])
    E ==> A

No exemplo acima, o nosso robô de negociação colocou simultaneamente ordens em ambas as direções para fazer cobertura das suas posições. Em contraste com as operações síncronas, não há necessidade de esperar que uma operação seja concluída antes de prosseguir com outra. Isto expande significativamente as oportunidades dos programadores para criar robôs de negociação eficientes e confiáveis.

Note que a execução assíncrona é diferente da multi-threading:

  • Na execução assíncrona, todas as tarefas são iniciadas na mesma thread. Quando são armazenadas ou colocadas em espera, libertam esta thread e, quando a sua execução continua mais tarde, ocorre numa thread diferente escolhida do pool de threads.
  • No multi-threading, todas as tarefas começam em threads diferentes e continuam a sua execução nas suas threads iniciais. Não há troca de threads.

O cTrader nunca invoca os seus métodos em paralelo, por isso não precisa de se preocupar com questões de multi-threading.

Image title