English Русский 中文 Español Deutsch 日本語
Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C#

Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C#

MetaTrader 5Exemplos | 12 abril 2019, 14:10
4 412 0
Vasiliy Sokolov
Vasiliy Sokolov

Introdução

Desde outubro de 2018, a MQL5 começou a oferecer suporte nativo à integração com as bibliotecas do .Net Framwork. Suporte nativo significa que os tipos, métodos e classes na biblioteca do .Net são agora acessíveis diretamente de programas MQL5, sem a declaração prévia das funções de chamada e seus parâmetros, bem como sem a complexa coerção dos tipos de dois linguagens entre si. Isso pode de fato ser considerado um avanço, já que agora a gigante base de código do .Net Framework e o poder da linguagem C# estão praticamente disponíveis para todos os usuários da MQL5.

As capacidades do .Net Framework não se limitam a apenas a si mesma. Graças ao ambiente de desenvolvimento VisualStudio integrado e condicionalmente gratuito, a criação de muitas coisas se torna muito mais fácil. Por exemplo, com sua ajuda, no modo arrastar e soltar, pode-se criar um aplicativo completo baseado no Windows, com um formulário e elementos que se comportarão da maneira usual, como qualquer outro aplicativo gráfico do Windows. Isto é o que estava faltando em MQL.

Claro, ao longo dos anos da existência desta linguagem, apareceram muitas bibliotecas, facilitando enormemente a construção de aplicativos gráficos dentro de programas MQL. No entanto, todas essas bibliotecas, não importa o quão boas sejam elas, são um conjunto de códigos, cujos princípios de trabalho é preciso entender e integrá-los ao código de EAs e indicadores. Em outras palavras, os usuários que não sabem de programação dificilmente poderiam usar estas bibliotecas na prática. A brecha entre a simplicidade de criar formulários no Visual Studio e a complexidade de configurar bibliotecas gráficas em MQL ainda permaneceria até hoje, porém, graças à nova integração com as bibliotecas do .Net Framework, a simples criação de formulários se torna uma realidade. 

Este artigo é dedicado à criação de interfaces gráficas do usuário para EAs ou indicadores escritos em MQL5. Aqui o conceito de interfaces gráficas é entendido como o de formulários padrão do Windows contendo um conjunto de elementos gráficos padrão, cada um dos quais está intimamente interconectado com a lógica de negociação do próprio EA.

Para criar um aplicativo gráfico, precisamos integrar o programa MQL com as bibliotecas do .Net. Este artigo também apresenta em detalhes como fazer isso e como funciona. Portanto, ele será interessante não apenas para aqueles que querem criar formulários gráficos para seus programas MQL, mas também para os interessados no tema da integração baseada em código de terceiros escrito no .Net.

O artigo dá particular ênfase na simplicidade da abordagem proposta. O objetivo principal é tornar a interação com o código escrito em C# o mais simples possível. Além disso, a interação deverá ser realizada de tal forma que o código escrito em C# seja criado automaticamente, sem intervenção do usuário. Graças ao desenvolvimento dos recursos da linguagem C#, bem como às avançadas capacidades do Visual Studio, isso é possível.

Assim, o leitor não precisa de nenhum conhecimento em C#. A idéia principal é garantir que o usuário no modo design coloque os controles gráficos como botões ou rótulos conforme necessário e, em seguida, em MQL, forneça a cada elemento a lógica apropriada. Todas as ações necessárias para integrar o painel com o programa em MQL ocorrem "nos bastidores", no modo automático. 


Esquema de interação com interfaces gráficas do .Net — princípios gerais.

.Net é o nome de patente da plataforma de linguagem comum da Microsoft. A base da plataforma é o chamado common language runtime ou CLR. Ao contrário de programas clássicos que são compilados diretamente em código nativo e podem ser executados diretamente no computador, um programa escrito para o .Net é executado numa máquina virtual CLR. Assim, o .Net é um tipo de ambiente através do qual um programa escrito numa linguagem de alto nível é executado na máquina do usuário.

A linguagem de programação C# é a principal linguagem de programação no .Net. Quando se fala em C#, fala-se no .Net e vice-versa, portanto, .Net está claramente associado a C#. Simplificando, pode-se dizer que o .Net é o ambiente de execução para programas escritos em C#. Nosso artigo não é uma exceção. Todo o código proposto no artigo é escrito nesta linguagem.

Depois de escrito programa para a plataforma do .Net, ele é compilado num código de byte intermediário de uma linguagem CIL de baixo nível (Common Intermediate Language) que a máquina virtual CLR executa. O próprio código é empacotado em entidades padrão de programas windows: executáveis exe ou bibliotecas dinâmicas dll. O código compilado para a máquina virtual do .Net possui uma estrutura de alto nível, suas propriedades são fáceis de explorar, é possível entender quais tipos de dados ele contém. Esse notável recurso é usado pelas versões mais recentes do compilador MQL, que, na etapa de compilação, carrega a biblioteca dinâmica do .Net e lê os métodos públicos estáticos definidos nela. Além de abrir métodos estáticos, o compilador MQL entende os tipos básicos de dados da linguagem de programação C#. Esses tipos de dados incluem:

  • todos os tipos de dados inteiros: long/ulong, int/uint, byte, short/ushort;
  • números reais com ponto flutuante float/double;
  • tipos de dados de caractere (diferentemente da MQL, em que char e uchar são tipos de dados de byte, em C# esse tipo é usado para definir um caractere);
  • tipos de string;
  • estruturas simples contendo os tipos básicos listados acima como seus campos.

Além dos tipos listados, o compilador MQL vê arrays C#. No entanto, por enquanto, ainda não é possível obter acesso padrão aos elementos do array pelo indexador '[]' no programa MQL. Pode-se dizer com confiança que, no futuro, o suporte a tipos se expandirá, no entanto, as capacidades atuais são suficientes para a realização de uma interação completa.

Em nosso projeto, criamos formulários usando Windows Forms. Ele é um conjunto bastante simples de APIs que permite rápida e simplesmente desenhar uma interface gráfica, mesmo para um usuário despreparado. Sua particularidade é uma abordagem orientada a eventos. Isso significa que quando o usuário clica num botão ou insere texto na janela de entrada, nesse momento é gerado um evento. Depois de processar tal evento, um programa escrito em C# determina que certo elemento gráfico do formulário foi modificado pelo usuário. Trabalhar com eventos é um processo bastante complicado para um usuário não familiarizado com C#, portanto, é necessário escrever um código intermediário especial que manipula os eventos que ocorrem no formulário e os transmite ao programa MQL em execução no terminal MetaTrader 5.

Assim, em nosso projeto, há três objetos independentes que interagem entre si:

  • um programa na forma de EA ou indicador escrito em MQL (arquivo EX5) que recebe eventos da janela gráfica ou os transmite para ela usando um controlador especial;
  • um controlador na forma da biblioteca dinâmica do .Net (arquivo DLL) usada pelo programa MQL;
  • uma janela gráfica criada pelo usuário usando C#, na forma de programa independente (EXE) ou de biblioteca dinâmica (DLL), cujos eventos são analisados pelo controlador.

Todos os três objetos interagem entre si através de sistemas de mensagens. Existe um sistema de mensagens entre o programa escrito em MQL e o controlador, e outro entre o controlador e a janela do usuário.



Fig. 1. Esquema geral de interação entre o programa MQL e o aplicativo gráfico C#

O esquema é apresentado na forma mais geral e, até o momento, não revela as especificidades da interação entre as partes descritas de nosso futuro aplicativo gráfico. No entanto, com base no esquema proposto, fica claro que nosso sistema é altamente distribuído, uma vez que cada módulo é independente e não exige intervenção em seu código se outro módulo tiver sofrido alterações. As seções a seguir descrevem em detalhes o processo de interação entre essas partes e os meios pelos quais é implementada essa separação.


Instalação e configuração do Visual Studio

Agora que o esquema geral de implementação está preparado, é hora de continuar com a implementação de nosso projeto. Para fazer isso, deve-se ter uma versão de trabalho do Visual Studio. Se já estiver instalada, você pode ignorar esta seção. No entanto, ela é útil para iniciantes. 

O Visual Studio é um ambiente de desenvolvimento profissional usado em inúmeras áreas da programação, ele é apresentado em várias edições. Nós estamos interessados na Community, pois é grátis. Após trinta dias de uso, é necessário registrá-la gratuitamente, passando pelo procedimento de verificação padrão através de um dos serviços da Microsoft. Mostraremos as etapas básicas para baixar, instalar e registrar a plataforma para que os usuários novatos possam começar a usar sua funcionalidade com segurança e o mais rápido possível. 

Assim, aqui está um guia passo a passo para instalar o Visual Studio no computador. Também são apresentadas capturas de tela para a versão internacional do instalador em inglês. No seu caso, a aparência pode ser diferente, dependendo das configurações regionais do seu computador. 

A primeira coisa a fazer é ir ao site oficial do Visual Studio visualstudio.microsoft.com e escolher a distribuição adequada. Nós precisamos escolher a versão Community:

 

Fig. 2. Escolhendo a distribuição do VisualStudio.


Uma vez selecionada, é iniciado o processo de download do instalador do Visual Studio. Se o site solicitar que você se registre, ignore esta etapa, pois faremos isso mais tarde.

Depois de iniciar o instalador, aparece uma janela para executar uma configuração específica do seu instalador. Concorde e clique no botão Continuar:

Fig. 3. Consentimento para continuar a instalação

Em seguida, é iniciado o download dos arquivos necessários para a instalação. Isso pode levar algum tempo, dependendo da largura de banda de seu link de comunicação. Concluído o processo, é exibida uma janela para configurar a instalação. Entre os recursos oferecidos, precisamos selecionar a opção ".Net desktop development": 


Fig. 4. Selecionando recursos

Selecionada a opção, deve-se clicar no botão Install. O processo de instalação é iniciado, o que também tarda um pouco:

Fig. 5. Processo de instalação.

Concluída a instalação, o Visual Studio é iniciado automaticamente. Se isso não acontecer, inicie-o manualmente. Ao iniciar o Visual Studio pela primeira vez, você deve fazer login na sua conta ou criar uma nova. Se você não tiver uma conta, é melhor criá-la agora, clicando no link "Create One":


Fig. 6. Criando uma nova conta


Depois de clicar no link "Create one", começa o processo de registro da caixa de correio vinculada a todos os serviços da Microsoft. Você precisa se registrar, realizando as ações propostas em ordem. Como o processo de registro em si é padrão, não vamos nos deter em detalhes.

Se, por algum motivo, você não precisar do registro, basta ignorar este passo clicando no link "Not now, maybe later". No entanto, lembre-se de que, após trinta dias, o Visual Studio exige registro, caso contrário, ele deixa de funcionar.


Criando o primeiro formulário — Início rápido.

Depois de registrar e fazer login na sua conta, o Visual Studio é iniciado. Tentemos criar nosso primeiro formulário e conectá-lo ao MetaTrader. Depois de ler esta seção, você verá como isso é realmente fácil de fazer.

Você precisa criar um novo projeto. Para fazer isso, selecionamos no menu  File -> New -> Project. É aberta uma caixa de diálogo para selecionar o tipo de projeto:

Fig. 7.

Precisamos selecionar o tipo "Windows Form App (.Net Framework)". No campo Name, é necessário inserir o nome do projeto. Mudamos o nome padrão do nosso projeto para Guimt. Em seguida, clicamos no botão "OK". Depois disso, o Visual Studio exibe automaticamente o designer visual com o formulário criado:

 

Fig. 8. Criando um formulário gráfico na janela do Visual Studio.


A janela Solution Explorer contém a estrutura do projeto criado. Note que o nome Form1.cs é um arquivo contendo o código do programa, que cria a representação gráfica do formulário, que vemos na janela do designer gráfico Form1.cs[Disign]. Lembre-se desse nome, ainda precisamos dele.

O designer visual permite alterar o tamanho do formulário com a ajuda do mouse. Adicionalmente, podem ser colocados elementos personalizados no formulário. Agora isso é suficiente para nossos primeiros experimentos. Abrimos a guia Toolbox, nas guias laterais à esquerda da janela principal e na seção All Windows Form, selecionamos o elemento Button:

Fig. 9. Seleção de botão

Arrastramo-o com o mouse até a superfície principal do nosso Form1:

Fig. 10. Primeiro formulário

O tamanho do botão também pode ser alterado. Você pode experimentar o tamanho da janela principal e a localização desse botão. Agora que há um botão no formulário, supomos que nosso primeiro aplicativo está pronto. Agora, compilemos isso. Podemos fazê-lo de diferentes maneiras, mas agora apenas o executamos no modo de depuração, pressionando o botão Start:

Fig. 11. Botão para iniciar o aplicativo no modo de depuração.  

Depois de clicar neste botão, o aplicativo é compilado e iniciado automaticamente. Quando o aplicativo estiver em execução, ele pode ser parado, basta clicar na cruz para fechar a janela ou interromper a depuração no Visual Studio clicando no botão Stop:

Fig. 11. Botão de parada de depuração

Assim, nosso primeiro aplicativo está pronto. A última coisa que precisamos fazer é descobrir o caminho absoluto para o programa que está sendo executando, que acabamos de criar. A maneira mais fácil é simplesmente olhar para o caminho no campo Project Folder da janela Properties, enquanto isso, o projeto GuiMT deve ser destacado na janela Solution Explorer:

Fig. 12. Caminho absoluto para o projeto de aplicativo na coluna Project Folder

O caminho nesta janela tem a ver com o projeto em si. A compilação do nosso programa está localizada num dos subdiretórios, dependendo do modo de compilação. No nosso caso, é .\bin\debug\<Nome_de_nosso_projeto.exe>. Assim, o caminho completo para o nosso programa é: C:\Users\<Nome_do_usuário>\source\repos\GuiMT\GuiMT\bin\debug\GuiMT.exe. Depois que encontramos o caminho, ele deve ser lembrado, porque mais tarde ele tem que ser inserido em nosso código MQL.


Obtendo a versão mais recente do GuiController.dll — Trabalhando com o GitHub

Os arquivos anexados a este artigo contêm uma biblioteca GuiController.dll. Esta biblioteca deve ser colocada no diretório \MQL5\Libraries. No entanto, muitas vezes acontece que a biblioteca continua a ser atualizada e desenvolvida, enquanto ao artigo é anexado um arquivo obsoleto. Para evitar essa e muitas outras situações semelhantes, é melhor usar sistemas de controle de versão que tornam o novo código automaticamente disponível para os destinatários. Nosso projeto não é a exceção. Para garantir a versão mais recente do GuiController, usamos o repositório aberto de códigos abertos GitHub.com. O código fonte deste controlador já está contido neste repositório, basta fazer o download de seu projeto e compilar o controlador numa biblioteca dinâmica. Se por algum motivo você não dispensar este sistema, ignore esta seção. Em vez disso, simplesmente copie o arquivoGuiController.dll para o diretório MQL5\Libraries. 

Assim, se você ainda tiver a solução atual aberta, feche-a executando o comando File -> Solution. Agora vamos para a guia Team Explorer e clicamos no link Clone. Na caixa amarela, insira o endereço em que está localizado o projeto:

https://github.com/PublicMqlProjects/MtGuiController

Na próxima imagem, é mostrado o caminho local em que o projeto baixado é salvo. Ele é selecionado automaticamente de acordo com o nome do projeto baixado, portanto, não o alteramos. A captura de tela abaixo mostra os valores que devem ser inseridos no Team Explorer:

Fig. 13. Conectando-se ao repositório remoto de código fonte

Agora que todo está pronto, clicamos no botão Clone. Depois de algum tempo, aparece o projeto com a versão mais recente do MtGuiController no endereço especificado. Abra-o através do comando no menu File -> Open -> Project/Solution. Depois que o projeto é carregado e aberto, é necessário compilá-lo, para o qual, pode-se pressionar a tecla "F6" ou selecionar no menu o comando Build -> Build Solution. Encontre o arquivo MtGuiController.dll compilado na pasta MtGuiController\bin\debug e copie-o para o diretório da biblioteca MetaTrader 5:  MQL5\Libraries.

Se por algum motivo você não tiver a última versão via github, não se desespere. Nesse caso, copie o controlador do arquivo anexado a este artigo.


Integrando o primeiro aplicativo com o MetaTrader 5

Agora que temos o primeiro aplicativo e controlador que transmite os sinais da janela gráfica no MetaTrader, temos que completar a parte final, isto é, escrever o programa em MQL como um EA que recebe eventos da nossa janela através do controlador. No MetaEditor, criamos um novo EA chamado GuiMtController com o seguinte conteúdo:

//+------------------------------------------------------------------+
//|                                              GuiMtController.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   GuiController::HideForm(assembly, "Form1");
   EventKillTimer();   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }
  }
//+------------------------------------------------------------------+

Lembre que, para compilar este código, a biblioteca MtGuiController.dll deve ser colocada no diretório MQL5\Libraries. Além disso, o caminho absoluto especificado na string

string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

deve ser ajustado segundo o caminho com a localização real do seu programa com uma janela.
Se tudo for feito corretamente, o EA é compilado, inciado, e nossa janela aparece no fundo da janela principal do MetaTrader:

Fig. 14. EA com aplicativo gráfico integrado em C#.

Observe que, se você pressionar o botão button1, o EA exibe na guia Experts o rótulo "Click on element button1", indicando que ele recebeu o evento de pressionar o botão. 


Interação do programa MQL com o GuiController — Modelo de evento

Para entender como funciona o programa feito por nós, analisemos em detalhes a listagem anterior de código MQL.

Assim, a primeira coisa que vemos é a diretiva import e a string assembly:

#import  "MtGuiController.dll"
string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

A primeira string informa ao compilador que são usadas chamadas para os métodos de classe estáticos abertos, localizados em MtGuiController.dll. Note que não precisamos especificar que métodos nos aplicamos nesta assembly. Este trabalho é feito pelo compilador automaticamente.

A segunda string contém o caminho para o formulário que devemos gerenciar. Este endereço deve corresponder à localização real do formulário.

A seguir, está o código padrão para inicialização do EA OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }

Nele ocorre a instalação de um temporizador de alta frequência e a primeira chamada para um dos métodos da nossa classe. Vamos descrever a função timer um pouco mais tarde, por enquanto, vamos voltar para a própria chamada ShowForm:

GuiController::ShowForm(assembly, "Form1");

Em C#, as funções não podem existir separadamente das classes. Assim, cada função (método) tem sua própria classe. No MtGuiController.dll, é definida uma única classe GuiController. Ela contém métodos estáticos que podem ser usados para controlar uma janela. Não há outras classes no MtGuiController.dll, o que significa que todo o gerenciamento é centralizado através desta classe. Isso é muito conveniente, porque o usuário trabalha com uma única interface de interação e não procura a função que precisa de um conjunto de definições diferentes.

Primeiro, no bloco de inicialização, o método ShowForm é chamado. Como você pode imaginar a partir do nome, ele inicia a exibição de formulários. O primeiro parâmetro do método define o caminho absoluto para o arquivo do formulário e o segundo estabelece o nome do formulário. Acontece que num arquivo vários formulários podem ser definidos de uma só vez. Portanto, é necessário especificar qual formulário no arquivo queremos executar. O nome do formulário nesse caso é o nome da classe do formulário, que o Visual Studio atribui por padrão ao que criamos. Ao abrir o projeto criado anteriormente no Visual Studio e acessar o arquivo Form1.Designer.cs no modo visualização de código, vemos o nome da classe que precisamos:

partial class Form1
{
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        ...
}

Para frente, é necessário fornecer nomes de classe melhor pensados. No Visual Studio, basta renomear a classe e todas as referências a ela. Nesse caso, o valor do segundo parâmetro do método ShowForm também tem que ser alterado.

A próxima função a tratar é OnTimer. De acordo com a configuração do temporizador, ela é chamada cinco vezes por segundo. Ela contém o código mais interessante de todo o nosso projeto. O corpo da função consiste num ciclo for que percorre os números de sequência dos eventos:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }

O evento, do ponto de vista do controlador, é qualquer ação do usuário no formulário. Por exemplo, quando um usuário clica num botão ou insere texto numa caixa de texto, o controlador recebe o evento correspondente e o coloca na lista de eventos. O número de eventos na lista é transmitido pelo método estático GuiController::EventsTotal(), que pode ser chamado pelo nosso programa MQL.

Existem muitos eventos no Windows Forms. Cada elemento (formulário, botão ou caixa de texto) contém vários eventos. Nem todos os eventos podem ser processados e nem todos são necessários. Portanto, o GuiController processa apenas os mais importantes. Na versão atual, existem apenas três deles:

  • evento de botão;
  • evento fim de entrada de texto;
  • evento de rolagem horizontal.

No futuro, essa lista será expandida, mas, para os propósitos deste artigo, esta é suficiente.

Assim, após o evento que suporta o nosso GuiController, ele é processado e adicionado à lista de eventos. O processamento de eventos consiste na criação de dados para o programa MQL poder determinar com relativa facilidade seu tipo e seus parâmetros. É por isso que o formato de dados de cada evento tem uma estrutura muito semelhante ao modelo de eventos da função OnChartEvent. Graças a essa similaridade, o usuário, ao trabalhar como GuiController, não precisa estudar o formato do novo modelo do evento. Naturalmente, a abordagem apresentada tem suas próprias dificuldades, por exemplo, eventos como o scroll são extremamente difíceis de encaixar no formato proposto, mas esses problemas são facilmente resolvidos com a ajuda de ferramentas de linguagem C# e seu modelo de programação orientado a objetos. Agora, o modelo proposto é suficiente para resolver nossos problemas.

Cada vez que aparece um novo evento, seus dados ficam disponíveis através dos tipos de referência usando o método estático GuiController::GetEvent. Este método tem o seguinte protótipo:

public static void GetEvent(int event_n, ref string el_name, ref int id, ref long lparam, ref double dparam, ref string sparam)

Descrevemos seus parâmetros em ordem: 

  • event-n — o número de sequência do evento a ser recebido. Graças à possibilidade de especificar o número de sequência de um evento, torna-se fácil controlar novos eventos, independentemente de quantos sejam recebidos;
  • el_name — nome do elemento que gera dado evento;
  • id — tipo de evento.
  • lparam — valor inteiro que dado evento possui;
  • dparam — valor real que tem dado evento;
  • sparam — valor da string que dado evento possui.

Como se pode apreciar, o modelo de evento GuiController se parece muito com o modelo de evento OnChartEvent. Qualquer evento no GuiController sempre tem um número de sequência e a fonte (nome do elemento) que gera este evento. Os demais parâmetros são opcionais. Assim, alguns eventos, como pressionar um botão, não possuem parâmetros adicionais (lparam, dparam, sparam), enquanto outros os contêm. Por exemplo, evento de final de texto no parâmetro sparam contém o texto inserido no campo pelo usuário.

Abaixo está uma tabela que contém os eventos e os parâmetros atualmente suportados:

Nome do evento Identificador (id)  Parâmetros
Exception  0 sparam contém a mensagem que causa as exceções
ClickOnElement  1 -
TextChange  2 sparam é o novo texto digitado pelo usuário 
ScrollChange  3

lparam é o nível de rolagem anterior

dparam é o nível de rolagem atual

Agora que dominamos o modelo de evento no GuiController, podemos entender o código apresentado dentro do ciclo for. String:

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);

Obtém o evento do índice i. Se o tipo de evento corresponder a um botão, o nome do botão e a mensagem sobre a sua pressão serão exibidos no console do terminal:

if(id == ClickOnElement)
   printf("Click on element " + el_name);

Observe que o id é comparado com a constante ClickOnElement, que não é definida em nenhum lugar no código MQL do programa. Na verdade, essa constante faz parte da enumeração definida no próprio GuiController em C#^

/// <summary>
/// Type of gui event
/// </summary>
public enum GuiEventType
{
    Exception,
    ClickOnElement,
    TextChange,
    ScrollChange
}

Como pode se apreciar, o compilador entende as enumerações externas definidas nas bibliotecas do .Net e sabe como trabalhar com elas. 

Observemos como as mensagens são recebidas. Para isso, é usado um temporizador, no entanto qualquer função chamada periodicamente pode ser usada, por exemplo, OnTick. No entanto, a periodicidade é muito difícil de controlar. É, talvez, impossível saber ao certo quanto tempo há entre duas chamadas OnTick consecutivas.

Além disso, é impossível garantir especificamente a periodicidade de uma chamada OnTimer. Por exemplo, no testador de estratégias, a frequência de chamada onTimer é muito diferente daquela que pode ser definida para esta função no modo de trabalho real. Todos esses efeitos levam ao fato de que entre duas chamadas de função pelo usuário podem ser gerados vários eventos seguidos. Por exemplo, ele pode pressionar um botão duas ou três vezes antes que o programa MQL tenha tempo para reagir ao primeiro clique.

Para esses fins, é criada a fila de eventos. Cada evento entra na lista e aguarda seus parâmetros serem recuperados pelo programa MQL. O programa em si lembra o último número de evento definindo uma variável estática na função, e, na próxima vez que for iniciado, recebe eventos recém-recebidos! É por isso que o ciclo for possui uma assinatura não padrão:

//-- O ciclo lembra o último índice de evento e inicia a função a partir dele na próxima vez que a função for iniciada a partir dele
for(static int i = 0; i < GuiController::EventsTotal(); i++)

Pode-se receber eventos usando o método GuiController::GetEvent ou enviá-los usando GuiController::SendEvent. O segundo método é usado quando o código precisa enviar alguns dados para a janela e alterar seu conteúdo. Ele tem o mesmo protótipo do GetEvent, a única diferença é que ele não contém o número de sequência do evento, já que nesse caso esse número não tem sentido. Não vamos nos deter em detalhes, no entanto, vamos mostrar o trabalho com ele num exemplo, apresentado na parte final do artigo.

O último método que não analisamos foi o GuiController::HideForm. Sua assinatura é semelhante ao ShowForm, e a ação é o oposto: esse método oculta a janela, para o qual se precisa especificar sua localização e nome.

Como se pode apreciar, o código MQL que lida com a exibição do formulário e com a análise de seus eventos se mostrou muito compacto e simples. Na verdade, o código descreve três ações simples:

  1. exibir a janela ao iniciar o programa;
  2. receber novos eventos da janela;
  3. ocultar a janela ao sair do programa.

Seria difícil de encontrar um esquema mais simples. Observe bem o código do formulário que criamos. Embora o formulário de janela contenha este código, nós mesmos não escrevemos nem uma única string em C#. Todo o trabalho foi feito para nós pelas ferramentas de geração automática de código desenvolvidas no Visual Studio e no próprio GuiController. É assim que o poder da metodologia do .Net se manifesta, porque o objetivo final de ambientes poderosos é a simplicidade.


Sob o capô do GuiController

Se você não é bem versado em C#, você pode seguramente pular esta seção. Ele é do interesse daqueles que querem entender como funciona o GuiController e como ocorre o acesso a aplicativos individuais e isolados do .Net.

O GuiController é uma classe compartilhada que consiste em duas partes: uma estática e uma parte da instância. A parte estática da classe contém métodos estáticos abertos para interação com o MetaTrader. Esta parte da classe implementa a interface entre o MetaTrader 5 e o próprio controlador. A segunda parte é a instância, o que significa que os dados e métodos dessa parte existem apenas no nível da instância. Sua tarefa é interagir com conjuntos independentes do .Net, nos quais as janelas gráficas estão localizadas. A janela gráfica no Windows Forms é uma classe herdada da classe base Form. Assim, com cada janela de usuário, pode-se trabalhar num nível mais alto e abstrato da classe Form.

Dentro das montagens do .Net, como DLLs ou EXEs, são contidos os tipos do .Net que são inerentemente abertos. Ter acesso a eles, suas propriedades e até métodos é bem simples. Isso pode ser feito usando um mecanismo chamado no .Net de reflexão ou reflexo. Graças a esse mecanismo, todos os arquivos, como DLL ou EXE criados no .Net, podem ser examinados quanto à presença do elemento de que precisamos. Isto é o que a classe GuiController faz. Aqui está o código para o método GetGuiController que faz este trabalho:

/// <summary>
/// Create GuiController for windows form
/// </summary>
/// <param name="assembly_path">Path to assembly</param>
/// <param name="form_name">Windows Form's name</param>
/// <returns></returns>
private static GuiController GetGuiController(string assembly_path, string form_name)
{
    //-- Carregar o assembly especificado
    Assembly assembly = Assembly.LoadFile(assembly_path);
    //-- Encontra nele o formulário especificado
    Form form = FindForm(assembly, form_name);
    //-- Atribuir o controlador ao formulário encontrado
    GuiController controller = new GuiController(assembly, form, m_global_events);
    //-- Retornar o controlador para o método de chamada
    return controller;
}

Este procedimento se assemelha ao chamado grabber de recursos: um programa especial com o qual você pode extrair conteúdo de mídia como ícones e imagens do código binário do programa.

A pesquisa de formulário é realizado usando reflexão. O método FindForm obtém todos os tipos definidos no assembly passado para ele. Entre esses tipos, ele procura aqueles cujo tipo base corresponde ao tipo Form. Se o nome do tipo encontrado corresponder ao do tipo necessário, será criada uma instância desse tipo, que, por sua vez, será retornada na forma de formulário:

/// <summary>
/// Find needed form
/// </summary>
/// <param name="assembly">Assembly</param>
/// <returns></returns>
private static Form FindForm(Assembly assembly, string form_name)
{
    Type[] types = assembly.GetTypes();
    foreach (Type type in types)
    {
        //assembly.CreateInstance()
        if (type.BaseType == typeof(Form) && type.Name == form_name)
        {
            object obj_form = type.Assembly.CreateInstance(type.FullName);
            return (Form)obj_form;
        }
    }
    throw new Exception("Form with name " + form_name + " in assembly " + assembly.FullName + "  not find");
}

O momento mais emocionante é a criação do próprio aplicativo e sua inicialização. Como a partir do conjunto externo de dados binários, o programa real ganha vida, começando a funcionar independentemente.

Depois que uma instância é criada, um controlador é atribuído a ela. O controlador é uma instância da classe GuiController que monitora o formulário enviado para ele. É responsabilidade do controlador monitorar e enviar eventos para o formulário.

A inicialização do formulário e sua remoção são realizados numa thread paralela. Por isso, a thread atual não é bloqueada para esperar que operação atual seja concluída. Imagine que iniciamos uma janela na thread atual. Como a janela continua a funcionar, o processo externo que a chama é bloqueado e espera o fechamento da janela. Executar a janela numa thread separada resolve esse problema.

Os métodos do controlador são responsáveis por iniciar e excluir a janela:

/// <summary>
/// Formulário personalizado chamado do MetaTrader deve ser executado de forma assíncrona,
/// para garantir a capacidade de resposta da interface.
/// </summary>
public static void ShowForm(string assembly_path, string form_name)
{
    try
    {
        GuiController controller = GetGuiController(assembly_path, form_name);
        string full_path = assembly_path + "/" + form_name;
        m_controllers.Add(full_path, controller);
        controller.RunForm();
    }
    catch(Exception e)
    {
        SendExceptionEvent(e);
    }
}
        
/// <summary>
/// Após o EA terminar de trabalhar com o formulário, é necessário completar seu processo de execução.
/// </summary>
public static void HideForm(string assembly_path, string form_name)
{
    try
    {
        string full_path = assembly_path + "/" + form_name;
        if (!m_controllers.ContainsKey(full_path))
            return;
        GuiController controller = m_controllers[full_path];
        controller.DisposeForm();
    }
    catch(Exception ex)
    {
        SendExceptionEvent(ex);
    }
}

A última coisa que precisamos considerar quanto ao controlador é o trabalho com eventos. Ao usar reflexão, é criado um novo formulário que é passado para o método que assina seus eventos (apenas aqueles que o controlador pode manipular). Para isso, é criado um mapeamento <elemento - lista de manipuladores de eventos>, onde cada manipulador de eventos assina o evento necessário: 

/// <summary>
/// Subscribe on supported events
/// </summary>
/// <param name="form">Windows form</param>
private void SubscribeOnElements(Form form)
{
    Dictionary<Type, List<HandlerControl>> types_and_events = new Dictionary<Type, List<HandlerControl>>();
    types_and_events.Add(typeof(VScrollBar), new List<HandlerControl>() { vscrol => ((VScrollBar)vscrol).Scroll += OnScroll });
    types_and_events.Add(typeof(Button), new List<HandlerControl>()  { button => ((Button)button).Click += OnClick });
    types_and_events.Add(typeof(Label), new List<HandlerControl>());
    types_and_events.Add(typeof(TextBox), new List<HandlerControl>() { text_box => text_box.LostFocus += OnLostFocus, text_box => text_box.KeyDown += OnKeyDown });
    foreach (Control control in form.Controls)
    {
        if (types_and_events.ContainsKey(control.GetType()))
        {
            types_and_events[control.GetType()].ForEach(el => el.Invoke(control));
            m_controls.Add(control.Name, control);
        }
    }
}

Cada formulário tem uma lista aberta dos elementos que contém. O método localiza - examinando a lista de elementos - aqueles que o controlador é capaz de suportar e assina os eventos de que precisa. Quando o elemento no formulário não é suportado pelo controlador, ele é simplesmente ignorado. Os eventos associados a ele não são entregues ao programa MQL, e o próprio programa MQL não pode interagir com este elemento.


Painel de negociação baseado em GUI

Agora que cobrimos todas as partes do nosso sistema, é hora de criar algo realmente útil. Façamos um análogo do painel de negociação padrão que está no canto superior esquerdo do gráfico:

Fig. 15. Painel de negociação embutido MetaTrader 5.

Nosso painel consiste em elementos gráficos padrão da janela do Windows, portanto, apesar de um design mais simples, a funcionalidade incorporada nele é idêntica.

Pode-se começar de diferentes maneiras, por exemplo, criando esse painel do zero. No entanto, a descrição do trabalho do designer visual neste artigo tornar-se-ia complexo. Portanto, simplesmente, carregamos o projeto contendo esse painel no Visual Studio. É possível tanto copiar o projeto do arquivo e abri-lo no Visual Studio quanto baixá-lo do repositório remoto Git no seguinte endereço: 

https://github.com/PublicMqlProjects/TradePanelForm

Trabalhar com o git neste caso é igual como descrito anteriormente neste artigo.

Carregado e aberto projeto, é necessário ver o seguinte formulário:

Fig. 16. Janela TradePanel no Visual Studio

O projeto contém o layout do painel de negociação. Em projetos reais como esse, precisamos acessar constantemente os elementos colocados neste formulário, além de enviar eventos para eles. Para isso, é necessário acessar cada elemento pelo seu nome. Portanto, os nomes dos elementos devem ser bem pensados e fáceis de decorar. Vejamos como são chamados os elementos que vamos usar. Para visualizar o nome de cada elemento, é preciso encontrar a propriedade Name na janela Properties, primeiro selecionando este elemento. Dessa maneira, por exemplo, o botão com o rótulo Buy tem o nome ButtonBuy:

Fig. 17. Nome do elemento na janela Properties.

É necessário distinguir o texto que é representado no elemento e o nome do próprio elemento. Embora normalmente tenham um sentido similar, trata-se de valores diferentes.

Listemos os elementos que o nosso painel de negociação contém:

  • Janela gráfica principal (Form) tem o nome TradePanelForm, nela estão localizados todos os outros controles.
  • Rótulo de texto vermelho (Label) tem o nome AskLabel. Ele exibe o preço Ask do instrumento atual.
  • Rótulo de texto azul (Label) tem o nome Bidlabel. Ele exibe o preço Bid do instrumento atual.
  • Campo de entrada de texto (TextBox) tem o nome CurrentVolume. Neste campo, o usuário insere o volume da operação.
  • Rolagem Vertical (VScrollBar) tem o nome IncrementVol. Ela aumenta ou diminui o volume um incremento. O tamanho do incremento é determinado pelo programa MQL, com base no atual ambiente de negociação.
  • Botão de compra tem o nome ButtonBuy. Ao clicar neste botão, o usuário compra o volume especificado por ele de acordo com o preço Ask — aquele que é exibido no rótulo de texto vermelho.
  • Botão de venda tem o nome ButtonSell. Ao clicar neste botão, o usuário vende o volume especificado por ele de acordo com o preço Bid — aquele que é exibido no rótulo de texto azul.

Embora sejam poucos os elementos que usamos, combinando-os, temos uma interface bastante avançada. Observe que, como no exemplo anterior, nossa solução não contém uma única linha de código C#. Todas as propriedades necessárias dos elementos são exibidas na janela Properties, enquanto a localização e o tamanho dos elementos são definidos usando a técnica de arrastar e soltar, com o mouse!


Integrando a janela gráfica com código do EA

Agora que nossa janela está pronta, ela precisa ser integrada a um EA. Escrevemos a lógica de negociação na linguagem de programação MQL, que interage com os elementos da nossa interface. O código completo do EA é apresentado abaixo:

//+------------------------------------------------------------------+
//|                                                   TradePanel.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
#include <Trade\Trade.mqh>
string assembly = "c:\\Users\\Bazil\\source\\repos\\TradePanel\\TradePanel\\bin\\Debug\\TradePanel.dll";
string FormName = "TradePanelForm";
double current_volume = 0.0;

//-- Trade module for executing orders
CTrade Trade;  
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//--- create timer, show window and set volume
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, FormName);
   current_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Dispose form
   EventKillTimer();
   GuiController::HideForm(assembly, FormName);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//--- refresh ask/bid   
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
   GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));
//---
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- get new events by timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == TextChange && el_name == "CurrentVolume")
         TrySetNewVolume(sparam);
      else if(id == ScrollChange && el_name == "IncrementVol")
         OnIncrementVolume(lparam, dparam, sparam);
      else if(id == ClickOnElement)
         TryTradeOnClick(el_name);
   }
//---
}
//+------------------------------------------------------------------+
//| Validate volume                                                  |
//+------------------------------------------------------------------+
double ValidateVolume(double n_vol)
{
   double min_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   double max_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
   //-- check min limit 
   if(n_vol < min_vol)
      return min_vol;
   //-- check max limit
   if(n_vol > max_vol)
      return max_vol;
   //-- normalize volume
   double vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double steps = MathRound(n_vol / vol_step);
   double corr_vol = NormalizeDouble(vol_step * steps, 2);
   return corr_vol;
}
//+------------------------------------------------------------------+
//| Set new current volume from a given text                         |
//+------------------------------------------------------------------+
bool TrySetNewVolume(string nstr_vol)
{
   double n_vol = StringToDouble(nstr_vol);
   current_volume = ValidateVolume(n_vol);
   string corr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, corr_vol);
   return true;
}
//+------------------------------------------------------------------+
//| Execute trade orders                                             |
//+------------------------------------------------------------------+
bool TryTradeOnClick(string el_name)
{
   if(el_name == "ButtonBuy")
      return Trade.Buy(current_volume);
   if(el_name == "ButtonSell")
      return Trade.Sell(current_volume);
   return false;
}
//+------------------------------------------------------------------+
//| Increment or decrement current volume                            |
//+------------------------------------------------------------------+
void OnIncrementVolume(long lparam, double dparam, string sparam)
{
   double vol_step = 0.0;
   //-- detect increment press
   if(dparam > lparam)
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect decrement press
   else if(dparam < lparam)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect increment press again
   else if(lparam == 0)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect decrement press again
   else
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double n_vol = current_volume + vol_step;
   current_volume = ValidateVolume(n_vol);
   string nstr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, nstr_vol);
}
//+------------------------------------------------------------------+

O código apresentado executa a o trabalho básico de carga do nosso formulário. No entanto, vale ressaltar que toda essa funcionalidade é escrita em MQL5, dentro das funções padrão do manipulador de eventos. Analisemos o código apresentado em mais detalhes.

A primeira coisa que é feita na função OnInit é definir um temporizador com uma permissão de 200 milissegundos. Em seguida, a janela é exibida usando o método ShowForm:

GuiController::ShowForm(assembly, FormName);

Aqui, assembly é o caminho para a montagem em que está localizada nossa janela, enquanto FormName é o nome da classe do nosso formulário.

Imediatamente após a inicialização da janela, definimos o volume mínimo no campo de texto "CurrentVolume":

GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));

O volume mínimo em si é calculado com base no ambiente de negociação atual, usando a função SymbolInfoDouble.

Ao fechar o EA, a janela do formulário é fechada. Isso é feito na função OnDeinit usando o método GuiController::HideForm; 

A função OnTick reage a alterações no preço Ask/Bid atual. Assim, quando os preços atuais são recebidos nesta função e são transmitidos para os rótulos de formulários de texto correspondentes, o painel mostra rápido todas as alterações no preço atual.

//-- Obter o preço Ask
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//-- Obter o preço Bid
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
//-- Alterar o texto no rótulo de texto AskLabel para o preço Ask atual convertido nua string:
GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
//-- Alterar o texto no rótulo de texto BidLabel para o preço Bid atual convertido numa string:
GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));

Na função OnTimer, acontece o acompanhamento de três ações que o usuário pode realizar com o formulário, isto é:

  • Digitar o novo volume no rótulo de texto CurrentVolume.
  • Pressionar o botão para aumentar ou diminuir o incremento do volume, na forma de rolagem.
  • Clicar no botão Buy ou Sell, enviando um pedido para concluir a operação.

Dependendo de qual ação seja executada pelo usuário, um determinado conjunto de instruções é desencadeado. Agora analisemos o evento de pressionar botões de rolagem, o que aumenta ou diminui o volume atual o incremento mínimo permitido.

O evento de rolagem no modelo de evento atual consiste em dois parâmetros: lparam e dparam. O primeiro parâmetro contém o valor condicional que caracteriza o deslocamento do carro em relação ao nível zero antes que o usuário clique nos botões de rolagem. O segundo parâmetro contém o mesmo valor, mas só depois de pressionar. A rolagem tem si tem um intervalo específico de funcionamento, por exemplo, de 0 a 100. Desse modo, se o valor de lparam é 30 e o valor de dparam é 50, a rolagem vertical é movida 30% a 50% para baixo (a rolagem horizontal se move de maneira correspondente para a direita na mesma proporcionalmente). Em nosso painel, não é necessário determinar exatamente onde está localizada a rolagem. Só precisamos saber em qual botão clica o usuário. Para isso, é preciso analisar o valor anterior e atual. Isso é exatamente o que a função OnIncrementVolume faz. Determinado o tipo de clique de rolagem, ela aumenta ou diminui o volume atual um incremento mínimo de volume, que é reconhecido pela função do sistema SystemInfoDouble.

O novo volume de negociação pode ser definido não apenas com as setas de rolagem, mas, também, diretamente num rótulo de texto. Quando um usuário insere um novo caractere, o windows forms geram o evento correspondente. No entanto, é importante analisarmos a sequência final, em vez de cada caractere individualmente. Daí que o GuiController responda ao pressionar na tecla 'Enter' ou ao mudar o foco de uma etiqueta de texto. Esses eventos são considerados o final da entrada de texto. Quando um deles ocorre, o texto gerado é transferido para a fila de eventos, que nosso EA lê sequencialmente. Ao atingir o evento de mudança de texto no rótulo, o programa MQL analisa seu novo valor e define o novo volume de acordo com o especificado. A análise é realizada usando a função ValidateVolume. Ela controla os seguintes parâmetros do volume digitado:

  • O volume deve estar entre o mínimo e o máximo permitido.
  • O valor do volume deve ser um múltiplo de seu incremento, daí que, se o incremento for de 0,01 e o usuário inserir a cifra 1,0234, ela será ajustada para 1,02;

Observe que é possível controlar esses parâmetros apenas com a ajuda do ambiente de negociação atual. Assim, todo o controle dos valores inseridos pelo usuário é realizado pelo próprio programa MQL, e não pelo formulário criado pelo usuário. 

Iniciemos o painel de negociação no gráfico e tentemos fazer várias operações com ele:


Fig. 18. Trabalho do painel em tempo real. 

Como se pode apreciar, o painel de negociação funciona e executa perfeitamente as funções atribuídas a ele.


Trabalho de interfaces gráficas no testador de estratégias

O testador de estratégias no MetaTrader 5 possui vários recursos que devem ser considerados pelo desenvolvedor de interfaces gráficas na linguagem de programação MQL. A principal característica está no fato de que a função de processamento de eventos gráficos OnChartEvent não é chamada. Isso é lógico, porque o formulário gráfico pressupõe trabalhar com o usuário em tempo real. No entanto, há um tipo de painel que seria extremamente interessante de implementar no testador. Trata-se do chamado players de negociação, com o qual as pessoas podem testar suas estratégias de negociação no modo manual. Por exemplo, num modo acelerado o testador de estratégias gera os preços atuais de mercado, enquanto o usuário clica nos botões para comprar ou vender, simulando suas ações de negociação no histórico. O TradePanel criado por nós pertence a este tipo de painel. Apesar de sua simplicidade, pode ser um muito bom player de negociação simples com a funcionalidade mais necessária. 

Mas pensemos em como o nosso painel funciona no testador de estratégias do MetaTrader 5. A janela gráfica do painel TradePanel existe na forma de um Net. de assembly independente. Portanto, não depende nem do ambiente atual do MetaTrader 5 e nem do próprio terminal. Estritamente falando, ele pode ser executado a partir de qualquer programa, enquanto a montagem localizada no contêiner exe pode ser iniciada, inclusive, pelo próprio usuário.

Assim, nosso programa não precisa chamar OnChartEvent. Além disso, podem-se atualizar dados na janela e receber novas ordens de usuários em qualquer função-manipulador de eventos que seja executada regularmente no testador de estratégias. OnTick e OnTimer pertencem a esse tipo de funções. É através deles que o nosso painel opera. Assim, ele também funciona bem no testador de estratégias, pois foi desenvolvido para trabalho em tempo real. Não é necessário fazer alterações. Verifiquemos essa afirmação executando nosso painel no testador e fazendo várias operações:


Fig. 19. Operação do painel no modo de simulação, no testador de estratégias.

Acontece que o desenvolvimento de interfaces gráficas usando C# nos dá um bônus inesperado ao trabalhar no testador de estratégias. Para um aplicativo Windows Forms, o testador de estratégias não impõe nenhuma restrição. O modelo do evento não afeta nem o painel nem as maneiras de trabalhar com ele. Também não há necessidade de adequar o programa para ser usado com o testador de estratégias. 


Fim do artigo

O artigo propôs uma abordagem para que você possa fácil e rapidamente criar seu próprio formulário visual. Essa abordagem divide o aplicativo gráfico em três partes independentes: o programa MQL, o adaptador GuiController e o painel visual. O programa MQL funciona no ambiente de negociação MetaTrader e realiza funções de negociação ou analíticas com base nos parâmetros que recebe do painel através do GuiController. O próprio GuiController é um programa independente que você não precisa alterar ao modificar o formulário ou seus elementos. Finalmente, o painel gráfico é criado pelo próprio usuário, usando as ferramentas visuais avançadas do Visual Studio. Por causa disso, para criar um formulário bastante complexo, talvez nem seja necessário o conhecimento da linguagem de programação C#.

Os formulários criados não dependem do programa que os inicia. Isso pode acontecer com o próprio MetaTrader 5 ou com seu testador de estratégia, ou seja, a janela funciona de acordo com a lógica embutida em ambos os casos. Além disso, a janela não depende de qual função é chamada. Graças a isso, as interfaces gráficas funcionam igualmente bem tanto no próprio MetaTrader 5 quanto no testador de estratégias, independentemente de um EA ou um indicador estar trabalhando com a janela. Em todos os casos, o comportamento da janela é o mesmo.

Devido aos recursos acima mencionados, a abordagem proposta certamente encontrará seus fãs. Aqueles que precisam fazer formulários semi-automáticos vão adorar. A abordagem também será do agrado daqueles que não são bem versados em programação. Para criar o próprio formulário, basta conhecer a MQL5 em termos gerais. 

A metodologia proposta, como qualquer outra, tem suas desvantagens. A principal delas é que não se pode trabalhar no Mercado, já que a chamada de DLLs de terceiros é proibida. Em segundo lugar, a execução de uma DLL ou EXE desconhecido pode não ser seguro, porque seus módulos podem conter funções maliciosas. No entanto, este problema é resolvido pela transparência do projeto. O usuário sabe que o programa criado por ele a priori não contém outros elementos além daqueles especificados por ele e tem noção de que o GuiController é um projeto público de código aberto. Outra desvantagem é que a interação entre programas é um processo bastante complicado. Tal interação pode causar um bloqueio ou encerramento inesperado do programa. Depende muito do desenvolvedor que tratará da interface e da interação com ela. Um sólido sistema escrito em MQL5 é consistente e pode resolver esse problema.

Neste momento, o projeto ainda está se desenvolvendo. Para além dos controles, muitos leitores encontraram neste artigo os recursos atuais de interação com janelas gráficas, que, francamente, ainda são muito limitados. Tudo isso é verdade, porém, a tarefa principal do artigo foi concluída. Nós mostramos que criar windows forms e interagir com eles é mais fácil do que parece. Se este material for útil para a comunidade MQL, definitivamente seguiremos nesta direção.

 

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/5563

Arquivos anexados |
Source.zip (30.67 KB)
Colorindo os resultados da otimização de estratégias de negociação Colorindo os resultados da otimização de estratégias de negociação
Neste artigo nós vamos realizar um experimento: nós vamos colorir os resultados da otimização. A cor é determinada por três parâmetros: os níveis de vermelho, verde e azul (RGB). Existem outros métodos de codificação de cores, que também usam três parâmetros. Assim, três parâmetros de teste podem ser convertidos em uma cor, que representa visualmente os valores. Leia este artigo para descobrir se essa representação pode ser útil.
Estudo de técnicas de análise de velas (Parte II): Busca automática de novos padrões Estudo de técnicas de análise de velas (Parte II): Busca automática de novos padrões
No artigo anterior, nós analisamos 14 padrões selecionados de uma grande variedade de formações de velas existentes. É impossível analisar todos os padrões um por um, portanto, outra solução foi encontrada. O novo sistema busca e testa novos padrões de velas com base nos tipos de velas conhecidos.
Criando um EA gradador multiplataforma Criando um EA gradador multiplataforma
Neste artigo, aprenderemos como escrever EAs que funcionam tanto no MetaTrader 4 quanto no MetaTrader 5. Para fazer isso, tentaremos escrever um que trabalhe com o princípio de criação de grades de ordens. Um gradador é um Expert Advisor cujo trabalho fundamental consiste em colocar simultaneamente e na mesma quantidade ordens limitadas tanto acima como abaixo do preço atual.
Integração da MetaTrader 5 e Python: recebendo e enviando dados Integração da MetaTrader 5 e Python: recebendo e enviando dados
O vasto processamento de dados requer ferramentas extensas e muitas vezes está além do ambiente seguro de um único aplicativo. Linguagens de programação especializadas são usadas para processar e analisar dados, estatísticas e aprendizado de máquina. Uma das principais linguagens de programação para processamento de dados é o Python. O artigo fornece uma descrição de como conectar a MetaTrader 5 e o Python usando sockets, além de como receber cotações por meio da API do terminal.