Uso dos recursos no MQL5
A importância da interface nos programas modernos
Muito tempo atrás, o principal propósito dos programas de computador era fazer cálculos matemáticos pesados e processar grandes quantidades de dados. Mas com o aumento da potência dos computadores, as prioridades mudaram. Agora, entre dois programas com funcionalidade idêntica, um usuário pode escolher aquele que seja mais fácil de se trabalhar.
Hoje em dia, não é suficiente escrever um programa de acordo com o algoritmo de cálculo necessário, mas você também deve fornecer uma interface gráfica amigável ao usuário. Mesmo análises técnicas surgiram do desejo dos negociantes terem uma representação visual do estado atual do mercado: linhas de tendência, níveis de suporte e resistência, vários canais e indicadores técnicos foram desenvolvidos para mostrar uma imagem objetiva do que está acontecendo.
A nova linguagem do MQL5 fornece ferramentas ainda mais poderosas para criar aplicações inteiramente funcionais que não requerem nada além do que o Terminal cliente MetaTrader 5. Neste artigo, mostraremos como usar os Recursos para criar um arquivo EX5 executável com uma interface amigável para o usuário, e este arquivo não precisará de nenhuma operação de rotina como instalação ou início.
As possibilidades do MQL5
Primeiro e acima de tudo, claro, a possibilidade de trabalhar com gráficos é muito importante. Podem ser encontrados exemplos em artigos, aqui estão alguns deles:
- Desenhando Emissões de Indicador no MQL5;
- Criando um Indicador com Opções de Controle Gráfico;
- Criando um Jogo da "Cobra" no MQL5;
- Criando Painéis de Controle Ativo no MQL5 para Negociação;
- Conexão do Expert Advisor com ICQ no MQL5;
- Um Gerenciador de Ordem Virtual para Rastrear Ordens dentro do Ambiente MT5 com posição central;
- Criando um Quadro de Informações Utilizando as Classes da Biblioteca Padrão e Google Chart API;
- Construindo um Analisador de Espectro;
- Construindo um Expert Advisor de arrastar e soltar semiautomático interativo com base no risco predefinido e proporção R/R.
É o uso dos elementos do gráfico que torna um programa mais interessante e fácil de controlar a partir da perspectiva do usuário. Além das ferramentas clássicas para Análises Técnicas, o terminal MetaTrader 5 fornece uma vasta gama de objetos gráficos, que podem ser utilizados como tijolos para construir sua própria interface gráfica.
Utilização de arquivos de imagem para criação de interface
Para criar uma interface especial, geralmente, são usadas as imagens dos arquivos gráficos. Isso permite conseguir um design reconhecível exclusivo de vários elementos de controle. A linguagem do MQL5 oferece dois objetos gráficos que utilizam gráficos:
- OBJ_BITMAP - o objeto Bitmappermite o download de uma imagem de um arquivo BMP e mostrá-la em um gráfico;
- OBJ_BITMAP_LABEL - a etiqueta gráfica é, na verdade, um botão no qual a imagem muda dependendo de seu estado (pressionado/não pressionado).
Estes dois objetos permitem que você crie uma grande variedade de controles e os compare com os handlers de Eventos com um "clique do mouse" (CHARTEVENT_OBJECT_CLICK). Para definir a imagem desejada para OBJ_BITMAP ou OBJ_BITMAP_LABEL, especifique o arquivo BMP desejado na propriedade OBJPROP_BMPFILE. Isso pode ser feito manualmente na guia "Parameters" (Parâmetros) do objeto gráfico.
A segunda e principal forma para um programador MQL5 é especificar um nome de arquivo para a propriedade OBJPROP_BMPFILE utilizando a função ObjectSetString(). Por exemplo:
//--- Load an image for the "Pressed" button state bool set=ObjectSetString(0,object_name,OBJPROP_BMPFILE,0,bmp_file_name);
Um algoritmo padrão de uso do OBJ_BITMAP ou OBJ_BITMAP_LABEL:
- Crie um objeto utilizando a função ObjectCreate().
- Usando a função ObjectSetInteger() fixe o objeto ao canto do gráfico desejado, se necessário. As coordenadas X e Y do ponto de fixação em pixels serão definidas relativas a este canto.
- Em ObjectSetInteger(), defina os valores das coordenadas X e Y (OBJPROP_XDISTANCE e OBJPROP_YDISTANCE).
- Utilizando ObjectSetString(), defina o valor da propriedade OBJPROP_BMPFILE para o objeto gráfico (um para BITMAP ou dois para OBJ_BITMAP_LABEL).
- Para o objeto OBJ_BITMAP_LABEL, utilizando ObjectSetInteger(), você pode definir o estado atual do botão - pressionado ou não pressionado (OBJPROP_STATE é verdadeiro ou falso).
Após criar e configurar o objeto, durante a execução do programa MQL5, você pode mudar dinamicamente não apenas a posição e condição do objeto gráfico, mas também mudar o valor da propriedade OBJ_BITMAP_LABEL para exibir imagens. Assim, a interface pode ser muito flexível e reconfigurável.
Reproduzindo sons
Uma conveniência adicional necessária nos programas é a capacidade de perguntar ao usuário sobre uma ação no caso de uma determinada situação. Para implementar esta interação reversa, geralmente a reprodução de diferentes sons é utilizada, dependendo do evento. Isso aliviará o negociador de uma observação contínua de gráficos de preços, voltando sua atenção apenas em casos necessários. Para reproduzir arquivos de som, é utilizada a função PlaySound() no MQL5.
A função PlaySound() é muito fácil de usar e requer apenas a especificação de um caminho para um arquivo de som:
//--- The path to a sound file string wav_file_name="Ok.wav"; ... //--- Play a sound from the file terminal_directory\Sounds\Ok.wav bool played=PlaySound(wav_file_name); if(!played) //--- Failed to play the sound, notify of this { PrintFormat("Failed to play file %s. Error code=%d", wav_file_name, GetLastError()); }
Onde localizar arquivos de som e imagem
As funções ObjectSetString() e PlaySound() requerem a especificação de um caminho para um arquivo. Por questões de segurança, todos os arquivos que são usados nos programas MQL5 estão localizados dentro do sandbox de arquivo. Isso significa os arquivos só podem ser armazenados em determinados diretórios, o trabalho com arquivos de outros diretórios não é permitido. Primeiro, você precisa saber quais diretórios estão disponíveis para as funções e operações de arquivo e como são nomeados.
Existem três diretórios diferentes:
- Diretório terminal - o diretório de instalação do terminal cliente. O MetaTrader 5 é iniciado a partir deste diretório, para visualizar a pasta, selecione (Arquivo) "File"-"Open terminal data" (Abrir dados do terminal) no menu do terminal;
- Diretório de dados do terminal - a pasta que armazena os dados de um determinado usuário Windows. Os mecanismos de proteção incorporados do sistema operacional diferenciam o acesso do usuário, então cada dado do usuário pode ser armazenado separadamente daquele para o outro usuário. Então, neste diretório são armazenados na subpasta do MQL5, todos os indicadores, Expert Advisors e script exibidos na janela do Navegador;
- A pasta compartilhada de todos os terminais (de todos os terminais cliente do MetaTrader 5 instalados no computador) - uma pasta para realizar as operações de arquivo utilizando a flag FILE_COMMON.
Para determinar o local destes diretórios, você pode usar o script WhereMyFolders.mq5:
//+------------------------------------------------------------------+ //| WhereMyFolders.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- The folder from which the terminal is started - terminal_directory string terminal_path=TerminalInfoString(TERMINAL_PATH); //--- The folder that stores the terminal data - terminal_data_directory string terminal_data_path=TerminalInfoString(TERMINAL_DATA_PATH); //--- The shared folder of all client terminals - common_terminal_folder string common_data_path=TerminalInfoString(TERMINAL_COMMONDATA_PATH); //--- Show all the paths Print("TERMINAL_PATH(terminal_directory) = ",TerminalInfoString(TERMINAL_PATH)); Print("TERMINAL_DATA_PATH(terminal_data_directory) = ",TerminalInfoString(TERMINAL_DATA_PATH)); Print("TERMINAL_COMMONDATA_PATH(comon_terminal_folder) = ",TerminalInfoString(TERMINAL_COMMONDATA_PATH)); }
Importante: Em alguns casos, o local do terminal_directory e terminal_data_directory pode coincidir, mas é melhor nunca confiar nisso e, assim, não confundir estes conceitos.
Os arquivos de imagem e som são pesquisados pelo sistema runtime terminal na seguinte ordem:
- Se o separador barra invertida "\" (escrito como "\\") estiver localizado no início do caminho, ele busca pelo recurso relativo ao diretório terminal_data_directory\MQL5\;
- Se não houver barra invertida no começo do caminho, o arquivo é buscado por relativo ao local do arquivo EX5, do qual a função ObjectSetString(... , OBJPROP_BMPFILE, ...) ou PlaySound() é chamada.
Exemplos para arquivos de Som:
- O arquivo one.wav será procurado em terminal_data_directory\MQL5\
PlaySound("\\one.wav");
- O arquivo two.wav será procurado em terminal_data_directory\MQL5\Files\
PlaySound("\\Files\\two.wav");
- O arquivo three.wav será procurado em terminal_data_directory\MQL5\MySounds\
PlaySound("\\MySounds\\three.wav");
- O arquivo four.wav será procurado na pasta, na qual o EX5 executável está rodando. Se este arquivo não for encontrado nesta pasta, então haverá a tentativa de procurar o arquivo na pasta terminal_directory\Sounds\
PlaySound("four.wav");
Exemplos de arquivos de imagem:
- O arquivo bird.bmp será procurado em terminal_data_directory\MQL5\
//--- Setting an image for the OBJ_BITMAP_LABEL object bool res=ObjectSetString(0,object_name,OBJPROP_BMPFILE,0,"bird.bmp");// Modifier 0 is specified
- swan.bmp será procurado em terminal_data_directory\MQL5\Files\
//--- Setting an image for the OBJ_BITMAP object bool set=ObjectSetString(0,object_name,OBJPROP_BMPFILE,"\\Files\\swan.bmp");// No modifier
- dog.bmp será procurado em terminal_data_directory\MQL5\MyPictures\
//--- Setting an image for OBJ_BITMAP bool done=ObjectSetString(0,object_name,OBJPROP_BMPFILE,"\\MyPictures\\dog.bmp");// No modifier
- cat.bmp será procurado na pasta na qual o EX5 executável está rodando
//--- Setting an image for OBJ_BITMAP bool result=ObjectSetString(0,object_name,OBJPROP_BMPFILE,"cat.bmp");// No modifier
Observe que a barra invertida dupla "\\" é usada como separador ao escrever o caminho.
Importante: Ao especificar o caminho, sempre use barras invertidas duplas como separador, porque uma barra invertida única é um caractere de controle para o compilador quando analisa as linhas constantes e constantes de caractere no código-fonte de um programa.
Novas possibilidades - Recursos
Para usar imagens e sons no seu programa MQL5, certifique-se que todos os arquivos de mídia estejam localizados nas pastas adequadas. Isso impõe uma desvantagem definitiva na transferência de arquivo EX5 compilado de um terminal para outro. Mas, é possível resolver este problema no estágio de escrita do código. Em tais casos, use os recursos.
Para utilizar um recurso em um programa, ele deve ser declarado usando a diretiva do compilador #resource.
#resource path_to_resource_file
Agora, este recurso pode ser usado em vez de um caminho de arquivo. O comando #resource diz ao compilador que o recurso no caminho especificado path_to_resource_file deve ser incluso no arquivo EX5 executável. Assim, todas as imagens e sons necessários podem ser localizados diretamente em um arquivo EX5. Agora, para iniciar um programa MQL5 em outro terminal, você não precisará transmitir todos os arquivos o utilizando.
Qualquer arquivo EX5 pode conter recursos, e qualquer programa EX5 pode usar recursos de outro programa EX5. Ou seja, um Expert Advisor pode usar os recursos que estão localizados em um indicador ou biblioteca EX5. Esta é mais uma conveniência de uso de recursos.
Procura de recurso por um compilador
Um recurso é especificado utilizando a diretiva #resource "" (caminho para o arquivo de recurso).
#resource ""
O comprimento da string constante > não deve exceder a 63 caracteres. O compilador procura por um recurso no caminho especificado na seguinte sequência:1 - Se a barra invertida "\" estiver localizada no começo do caminho, o recurso é buscado relativo à pasta terminal_data_directory\MQL5\;
- Se não houver uma barra invertida, o recurso é procurado relativo à posição do arquivo fonte, no qual este recurso está escrito.
Importante: No caminho do recurso, é inaceitável usar substrings "..\\" e ":\\".
Exemplos de recurso incluindo os Recursos do tópico Ajuda:
//--- Correct specification of a resource #resource "\\Images\\euro.bmp" // euro.bmp is located in terminal_data_directory\MQL5\Images\ #resource "picture.bmp" // picture.bmp is locate in the same directory with the source file #resource "Resource\\map.bmp" // The resource is located in the folder source_file_directory\Resource\map.bmp //--- incorrect specification of resources #resource ":picture_2.bmp" // Use of ":" is not allowed #resource "..\\picture_3.bmp" // Use of ".." is not allowed #resource "\\Files\\Images\\Folder_First\\My_panel\\Labels\\too_long_path.bmp" //More than 63 characters
Nomes dos recursos
Após a declaração de um recurso utilizando a diretiva #resource, ele pode ser utilizado em qualquer parte de um programa. Para o nome do recurso, será utilizado seu caminho sem a barra no começo da string que define o caminho para o recurso.
Exemplos:
//---Examples of specifying resources and their names in the comments #resource "\\Images\\cat.bmp" // Resource name - Images\cat.bmp #resource "dog.bmp" // Resource name - dog.bmp #resource "Resource\\map.bmp" // Resource name - Resource\map.bmp #resource "\\Files\\Pictures\\bird.bmp" // Resource name - Files\Pictures\bird.bmp #resource "\\Files\\good.wav" // Resource name - Files\good.wav" #resource "\\Sounds\\thrill.wav" // Resource name - Sounds\thrill.wav"
Nomes de recursos independem da fonte - para o compilador, os nomes dog.bmp e DOG.bmp serão a mesma coisa.
Utilização de recursos próprios e de terceiros
Para utilizar um recurso, você deve especificar seu nome. O nome do recurso e seu caminho sem uma barra invertida no começo da linha. Quando você utiliza seu próprio recurso, o atributo especial "::" deve ser adicionado antes do nome do recurso.
//--- Use of resources ObjectSetString(0,bitmap_name,OBJPROP_BMPFILE,0,"::Images\\cat.bmp"); ... ObjectSetString(0,my_bitmap,OBJPROP_BMPFILE,0,"::dog.bmp"); ... set=ObjectSetString(0,bitmap_label,OBJPROP_BMPFILE,1,"::Files\\Pictures\\bird.bmp"); ... PlaySound("::Files\\good.wav"); ... PlaySound("::Sounds\\thrill.wav");
Você pode usar não apenas seus próprios recursos (de seu arquivo EX5), mas também de qualquer um dos módulos e bibliotecas EX5. Desta forma, você pode criar um repositório de recursos e usá-los em muitos outros programas MQL5.
Para utilizar recursos de outro arquivo EX5, o nome do recurso deve ser especificado na forma :: (nome do recurso). Suponha que o script Draw_Triangles_Script.mq5 contenha um recurso para uma imagem no arquivo triangle.bmp:
#resource "\\Files\\triangle.bmp"
Então, seu nome, para usar no script em si, ficará assim "Files\triangle.bmp", e para usá-lo, deve ser adicionado "::" no nome do recurso - "::Files\triangle.bmp". A fim de utilizar o mesmo recurso de outro programa, por exemplo, de um Expert Advisor, precisamos adicionar ao nome do recurso o caminho para o arquivo EX5 relativo à terminal_data_directory\MQL5\ e o nome do arquivo EX5 do script - Draw_Triangles_Script.ex5. Suponha que o script esteja localizado na pasta padrão terminal_data_directory\MQL5\Scripts\, então, a chamada deve ser escrita da seguinte forma:
//--- Use of a script resource in an Expert Advisor ObjectSetString(0,my_bitmap_name,OBJPROP_BMPFILE,0,"\\Scripts\\Draw_Triangles_Script.ex5::Files\\triangle_1.bmp");
Se o caminho para o arquivo executável não estiver especificado ao chamar o recurso de outro EX5, o arquivo executável é procurado na mesma pasta que contém o programa que chama o recurso. Isso significa o seguinte: se um Expert Advisor estiver localizado em terminal_data_directory\MQL5\Experts\ e um recurso do arquivo Draw_Triangles_Script.ex5 for solicitado nele sem a especificação do caminho, então o arquivo será procurado em terminal_data_directory\MQL5\Experts\.
//--- Request for a resource from a script in an Expert Advisor without path specification ObjectSetString(0,my_bitmap_name,OBJPROP_BMPFILE,0,"Draw_Triangles_Script.ex5::Files\\triangle_1.bmp");
Compressão de recursos nos arquivos EX5 - Como funciona
Arquivos em BMP e WAV são automaticamente comprimidos antes de serem inclusos em um arquivo EX5 executável. Isso significa que o uso de recursos não apenas permite que você crie programas MQL5 cheios de recursos, mas também reduz o tamanho geral dos arquivos necessários pelo terminal ao usar imagens e sons em comparação ao modo convencional de escrever programas MQL5.
O tamanho do arquivo de recurso não pode exceder 16 Mb.
Importante: A vantagem adicional em utilizar recursos é a compressão automática de arquivos WAV e BMP ao compilar em um arquivo EX5 executável. Isso reduz não apenas a quantidade, mas também o tamanho dos arquivos utilizados pelo programa.
Por exemplo, considere um pequeno programa Animals_EA.mq5. Um pequeno bloco do código para uso de recurso é dado abaixo:
//+------------------------------------------------------------------+ //| Animals_EA.mq5 | //| Copyright 2011, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- Declare image resources #resource "\\Images\\cat.bmp" #resource "\\Images\\dog.bmp" #resource "\\Images\\cow.bmp" #resource "\\Images\\bird.bmp" //--- Declare sound resources #resource "\\Files\\MySounds\\cat.wav" #resource "\\Files\\MySounds\\dog.wav" #resource "\\Files\\MySounds\\cow.wav" #resource "\\Files\\MySounds\\bird.wav" //--- Object names string cat_dog="cat_dog"; string cow_bird="cow_bird"; string canvas="canvas"; string text="text"; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create a substrate CreateCanvas(canvas,50,50,500,500); //--- Create buttons CreateObjectBITMAP_LABEL(cat_dog,110,120,"::Images\\cat.bmp","::Images\\dog.bmp"); CreateObjectBITMAP_LABEL(cow_bird,110,330,"::Images\\cow.bmp","::Images\\bird.bmp"); CreateText(text,"Click on any graphical object",200,90,clrTan); //--- Give a command for an immediate refresh to see the object ChartRedraw(); //--- return(0); } //+------------------------------------------------------------------+ //| Creating OBJ_BITMAP_LABEL with the specified images | //+------------------------------------------------------------------+ bool CreateObjectBITMAP_LABEL(string obj_name,int X,int Y,string res_name1,string res_name2) { //--- If there is no such an object on the chart if(ObjectFind(0,obj_name)==-1) { //--- Create it bool res=ObjectCreate(0,obj_name,OBJ_BITMAP_LABEL,0,0,0); //--- Check the result if(!res) { PrintFormat("%s: Failed to create OBJ_BITMAP_LABEL with the name %s. Error code=%d", __FUNCTION__, GetLastError()); return false; } } //--- Set the coordinates ObjectSetInteger(0,obj_name,OBJPROP_XDISTANCE,X); ObjectSetInteger(0,obj_name,OBJPROP_YDISTANCE,Y); //--- Disable display on the background ObjectSetInteger(0,obj_name,OBJPROP_BACK,false); //--- Reset the error code ResetLastError(); //--- Set an image for the pressed condition bool res=ObjectSetString(0,obj_name,OBJPROP_BMPFILE,0,res_name1); //--- Check the operation result if(!res) { PrintFormat("%s: Failed to upload an image from the resource %s. Error code=%d", __FUNCTION__, res_name1, GetLastError()); return false; } //--- Set an image for the depressed state res=ObjectSetString(0,obj_name,OBJPROP_BMPFILE,1,res_name2); //--- Check the operation result if(!res) { PrintFormat("%s: Failed to upload an image from the resource %s. Error code=%d", __FUNCTION__, res_name2, GetLastError()); return false; } //--- Set the button pressed ObjectSetInteger(0,obj_name,OBJPROP_STATE,true); return true; } //+------------------------------------------------------------------+
A tarefa do programa é desenhar um background azul (substrato), dois botões gráficos que mudam a aparência com o clique do mouse. Quando você clica no substrato, ele muda sua cor de azul para bege, e vice e versa. Em cada mudança, um som é executado, o evento do clique do mouse é manuseado pela função OnChartEvent(). Um gráfico imediatamente após o início do Adviser Animals_EA.mq5 é mostrado na figura.
Dê uma olhada nas propriedades do objeto OBJ_BITMAP_LABEL, por exemplo, cat_dog. Mudar as propriedades do Arquivo Bitmap (Ligado) e Arquivo Bitmap (Desligado) é possível agora através de uma caixa de diálogo, estes campos estão indisponíveis e estão escurecidos.
Importante: Nos objetos gráficos, imagens carregadas a partir de recursos podem ser modificadas apenas programaticamente. Alterar manualmente estas propriedades através da janela Propriedades do objeto se torna inacessível.
O volume total de imagens usado pelo Expert Advisor Animals_EA.mq5 é de 430 kb.
Mas o tamanho do arquivo executável resultante Animals_EA.ex5 que contém todas essas imagens é de 339 kb. Assim, em vez de 9 arquivos (um arquivo MQ5, quatro arquivos BMP e quatro arquivos WAV), temos agora um arquivo EX5 que contém todos os recursos necessários para o programa.
Apenas imagens em 24 ou 32 bits BMP podem ser usadas nos recursos. Imagens de 32-bit BMP podem conter composição alfa - nesse caso, elas serão aplicadas ao gráfico com transparência.
O arquivo do Expert Advisor Animals_EA.mq5, as imagens e os sons estão anexados ao artigo:
- As imagens devem ser extraídas de архива images.zip para terminal_data_directory\MQL5\Images\;
- O sons de MySounds.zip devem ser extraídos para terminal_data_directory\MQL5\Files\MySounds\.
Se quiser testar este programa no seu terminal, basta baixar o Expert Advisor Animals_EA.ex5 compilado em anexo, ele contém todos os recursos necessários. Neste caso, você não precisa fazer o download e instalar os arquivos de imagem e som.
Salvando a memória do terminal
Cada recurso é carregado apenas uma vez na memória do terminal. Embora sob condição normal de uso, cada acesso ao arquivo cause um carregamento adicional do arquivo na memória. Por exemplo, suponha que temos 50 objetos OBJ_BITMAP, cada um deles contém a mesma imagem de tamanho 100 kb. Então, durante o uso normal, estes 50 objetos poderiam precisar de memória de 50*100kb=5Mb.
Se declararmos um recurso para a imagem subida, então esta imagem será carregada na memória apenas uma vez, independente do número de objetos nos quais use.
Importante: Os recursos são carregados na memória apenas uma vez e podem economizar memória se usados diversas vezes.
Conclusão
O uso de recursos facilita o uso e distribuição de programas MQL5. Criar ferramentas confortáveis e modernas para negociação que requer o uso de arquivos de imagem e som multimídia. O conceito de recurso no MQL5 é simples e fácil de entender, então teste e veja.
As imagens no formato 32-bit BMP podem conter composição alfa - neste caso elas serão aplicadas ao gráfico com transparência.
Os recursos fornecem as seguintes vantagens:
- Compacidade - todos os arquivos são comprimidos em um único arquivo EX5 executável, então é fácil para o programa transmitir e iniciar;
- Economia de memória - a memória do terminal sempre contém apenas uma instância de cada recurso, independente da frequência de seu uso em um programa;
- Armazenamento conveniente - um arquivo EX5 com todos os recursos é menos que a soma dos arquivos de som e imagem originais.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/261
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso