Criando um Painel de Administração de Trading em MQL5 (Parte IX): Organização de Código (I)
Introdução
Código extenso pode ser difícil de acompanhar, especialmente quando carece de organização adequada. Isso frequentemente leva a projetos abandonados. Mas isso significa que eu tenho que abandonar o projeto do Painel de Administração de Trading agora que ele cresceu com múltiplos painéis integrados? Absolutamente não! Em vez disso, precisamos de estratégias para mantê-lo funcionando de forma eficiente.
Isso nos leva à discussão de hoje, onde exploramos como a organização de código pode aprimorar o desenvolvimento de algoritmos em MQL5. O sucesso do MQL5 e de outros projetos em larga escala pode frequentemente ser atribuído à sua abordagem estruturada — permitindo gerenciar e manter extensas bases de código de forma eficiente.
Sem documentação e estrutura adequadas, manter um código torna-se um desafio, tornando futuras modificações ainda mais difíceis.
Nesta discussão, exploraremos soluções práticas para esses desafios, com foco na estruturação do Painel de Administração de Trading para escalabilidade a longo prazo. A organização de código não é apenas uma conveniência; é um fator crítico na escrita de programas eficientes e de fácil manutenção. Ao adotar essas melhores práticas, podemos garantir que projetos em MQL5 permaneçam robustos, compartilháveis e escaláveis — permitindo que desenvolvedores individuais construam e sustentem aplicações complexas.
Antes de nos aprofundarmos, permita-me detalhar os principais pontos desta discussão:
- Visão Geral da Discussão
- Compreendendo a Organização de Código.
- Implementação no Painel de Administração (EA)
- Resultados e Testes
- Um exemplo integrado de código bem organizado
- Conclusão
Visão Geral da Discussão
Na discussão anterior desta série, testemunhamos uma expansão significativa do nosso programa à medida que introduzimos painéis mais especializados ao Painel de Administração, transformando-o em um painel essencial para qualquer trader. Com essas adições, agora temos quatro painéis: o Painel Inicial de Administração, o Painel de Comunicações, o Painel de Gerenciamento de Trades e o Painel de Análises. O código cresceu consideravelmente, delineando a estrutura principal, mas ainda há muito a ser feito para aprimorar a funcionalidade de cada recurso.
Ao considerar dar o próximo passo e adicionar ainda mais funcionalidades, percebi a importância de revisitar todo o código para organizá-lo melhor. Foi nesse momento que surgiu a ideia para este tema. Em vez de simplesmente apresentar um programa finalizado, achei que seria valioso percorrer com você o processo de refinamento e organização do código. Na próxima seção, abordaremos mais sobre organização de código com base na minha pesquisa.
Acredito que, ao final desta discussão, alguém deve ter adquirido conhecimento para responder a estas perguntas:
- Como desenvolver programas grandes?
- Como fazer com que outros entendam meu programa grande?
Compreendendo a Organização de Código
De acordo com diversas fontes, organização de código refere-se à prática de estruturar e organizar o código de forma a aprimorar a legibilidade, a manutenibilidade e a escalabilidade. Código bem organizado facilita para os desenvolvedores entenderem, depurarem e estenderem seus programas.
Como o engenheiro de software Zhaojun Zhang certa vez mencionou, “A organização do código é como a arrumação da sua casa: você não precisa arrumá-la todos os dias, e ainda pode viver nela independentemente de quão bagunçada esteja, desde que consiga tolerar isso. Ela só passa a assombrar você quando precisa desesperadamente encontrar algo que não mexe há algum tempo ou quando quer convidar visitas para um jantar elegante.”
Acredito que essa analogia deixa claro que a organização de código é importante para o seu próprio fluxo de trabalho e também para outros que possam trabalhar com o seu código. Vamos detalhar esses conceitos-chave da organização de código — legibilidade, manutenibilidade e escalabilidade — e explorar sua importância, particularmente no contexto do desenvolvimento de algoritmos em MQL5.
1. Legibilidade:
Legibilidade refere-se à facilidade com que alguém consegue entender a lógica e a estrutura do código. No contexto do MQL5, isso é especialmente crucial porque o código pode ser trabalhado por vários desenvolvedores e, mesmo se você estiver trabalhando sozinho, em algum momento desejará revisitar ou depurar o seu próprio código, como mencionei anteriormente.
Principais Características:
- Nomenclatura Clara de Variáveis: Use nomes significativos para variáveis, funções e classes. Em vez de usar nomes vagos como a, b, ou temp, escolha nomes descritivos que transmitam o propósito, como movingAveragePeriod ou signalStrength.
- Comentários: Bons comentários explicam por que determinados blocos de código existem, não apenas o que eles fazem. Isso é essencial para documentar a intenção do algoritmo.
- Formatação Consistente: Recuo e espaçamento de linhas ajudam a dividir o código em blocos legíveis. Por exemplo, utilize indentação consistente para loops, condicionais e funções.
- Código Modular: Dividir o código em funções ou classes pequenas e autocontidas, cada uma lidando com uma tarefa específica (como calcular uma média móvel ou verificar uma condição de trade), melhora a legibilidade.
- Depuração Rápida: Código legível facilita a identificação de bugs e a correção deles.
- Colaboração: Se o seu código estiver limpo e compreensível, torna-se muito mais fácil para outros desenvolvedores colaborarem ou ajudarem a solucionar problemas.
- Integração Mais Rápida: Ao revisitar um projeto, código legível garante que você não perca tempo reaprendendo o seu próprio trabalho.
2. Manutenibilidade:
Manutenibilidade é a facilidade com que o código pode ser modificado ou estendido ao longo do tempo, especialmente quando novos recursos são adicionados ou bugs precisam ser corrigidos. No trading algorítmico, como no MQL5, onde estratégias frequentemente evoluem, a manutenibilidade é crítica para o sucesso a longo prazo.
Principais Características:
- Modularidade: Ao usar funções ou classes para compartimentar diferentes tarefas (por exemplo, uma função para lidar com a execução de trades, outra para calcular indicadores), você cria partes isoladas do sistema. Alterações podem ser feitas em uma área sem impactar outras.
- Separação de Responsabilidades: Cada parte do código deve ter uma única responsabilidade. Por exemplo, a lógica para executar trades deve ser separada da lógica para avaliar condições de mercado.
- Uso de Bibliotecas e Funções Integradas: Em vez de reinventar a roda, aproveite as funções e bibliotecas integradas do MQL5 para tarefas comuns, como médias móveis ou envio de ordens, o que pode reduzir a complexidade e os erros.
- Controle de Versão: Utilize sistemas de controle de versão (por exemplo, Git e MQL5 Storage) para rastrear alterações, permitindo reverter caso uma modificação introduza bugs ou comportamentos inesperados.
Benefícios:
- Modificações Futuras: À medida que as estratégias evoluem, manter uma base de código bem estruturada permite que os desenvolvedores implementem novos recursos ou façam ajustes com esforço mínimo.
- Correção de Bugs: Quando bugs são detectados, código manutenível permite resolver problemas rapidamente sem interromper outras partes do sistema.
- Eficiência: Desenvolvedores gastam menos tempo tentando entender como o código funciona, resultando em atualizações mais rápidas e menos erros.
3. Escalabilidade:
Escalabilidade refere-se à capacidade do código de lidar com volumes crescentes de trabalho ou acomodar requisitos crescentes de dados e funcionalidades. À medida que as estratégias de trading se tornam mais complexas e intensivas em dados, a escalabilidade torna-se vital para uma operação fluida.
Principais Características:
- Algoritmos Eficientes: No trading algorítmico, pode ser necessário processar grandes volumes de dados históricos, executar muitas operações ou analisar múltiplos ativos simultaneamente. Otimizar seus algoritmos para velocidade e uso de memória é crucial.
- Estruturas de Dados: A escolha de estruturas de dados apropriadas, como arrays, listas ou mapas, ajuda a gerenciar conjuntos de dados maiores de forma eficiente. O MQL5 fornece estruturas de dados como Array and Struct, que podem ser aproveitadas para escalar sua estratégia.
- Processamento Paralelo: O MQL5 oferece suporte a multithreading, permitindo executar várias tarefas em paralelo. Isso é particularmente útil em estratégias de trading complexas ou em backtests, onde diferentes tarefas (como análise de mercado e execução de ordens) podem ser tratadas simultaneamente.
- Operações Assíncronas: Para tarefas que não precisam bloquear a execução de outras partes do algoritmo (por exemplo, obtenção de dados de APIs externas), o uso de operações assíncronas ajuda a manter o sistema responsivo.
Benefícios:
- Manipulação de Grandes Volumes de Dados: Código escalável pode processar conjuntos maiores de dados de mercado ou incorporar ativos adicionais sem degradação significativa de desempenho.
- Suporte ao Crescimento: Se o algoritmo precisar acomodar recursos adicionais (como negociação de múltiplos pares, aplicação de modelos de machine learning ou gerenciamento de risco ampliado), código escalável fornece a flexibilidade para crescer sem grandes reformulações.
- Desempenho em Tempo Real: Em um ambiente de trading ao vivo, a escalabilidade garante que o algoritmo consiga lidar com feeds de dados em tempo real e execuções de ordens sem atrasos.
No MQL5, legibilidade, manutenibilidade e escalabilidade frequentemente se sobrepõem e se reforçam mutuamente. Por exemplo, uma função legível e modular é mais fácil de manter quando precisa de ajustes. Da mesma forma, código escalável tende a ser mais modular, o que também aprimora sua legibilidade e manutenibilidade. Ao desenvolver algoritmos de trading, esse equilíbrio garante que o código funcione bem agora e possa ser adaptado ou expandido conforme as estratégias evoluem ou as demandas de desempenho aumentam com mais dados.
Por exemplo, neste desenvolvimento, começamos com um Painel de Comunicações na Parte 1. À medida que o projeto evoluiu, integramos de forma fluida novos painéis com diferentes especializações sem interromper a lógica central. Isso demonstra escalabilidade, mas ainda há conceitos-chave a considerar para aprimorar a reutilização dos recursos existentes no código.
Implementação no Painel de Administração (EA)
Isso demonstra escalabilidade, mas ainda há conceitos-chave a considerar para aprimorar a reutilização dos recursos existentes no código. As abordagens para estruturar código podem variar — alguns desenvolvedores preferem organizá-lo enquanto constroem, enquanto outros optam por avaliá-lo e refiná-lo posteriormente. Independentemente da abordagem, uma avaliação rápida ajuda a determinar se o código atende a padrões essenciais. Como mencionei anteriormente na citação de Zhaojun Zhang, um código bem organizado não é obrigatório Alguns desenvolvedores sentem-se confortáveis com código desorganizado desde que ele funcione. No entanto, isso frequentemente leva a desafios significativos, especialmente ao escalar projetos. Código mal estruturado torna mais difícil manter, estender e depurar, limitando o crescimento a longo prazo. Por isso, incentivo fortemente a adoção de boas práticas de organização de código. Vamos explorar mais na próxima seção.
Identificando problemas no código
Ao analisar o código-fonte do Admin Panel V1.24, decidi criar um resumo dos componentes que facilitasse para mim uma compreensão rápida e a identificação de problemas. Em geral, como desenvolvedor original, conheço os componentes do meu programa, mas o único desafio é organizá-lo e encurtá-lo, mantendo-o legível. Assim, abaixo descrevi cerca de nove partes principais que nos permitem compreender a ideia do programa, e em seguida compartilharei mais sobre os problemas que irei abordar.
1. Elementos de UI e Variáveis Globais
// Panels CDialog adminHomePanel, tradeManagementPanel, communicationsPanel, analyticsPanel; // Authentication UI CDialog authentication, twoFactorAuth; CEdit passwordInputBox, twoFACodeInput; CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton; // Trade Management UI (12+ buttons) CButton buyButton, sellButton, closeAllButton, closeProfitButton, ...; // Communications UI CEdit inputBox; CButton sendButton, clearButton, quickMessageButtons[8];
2. Sistema de Autenticação:
- Senha codificada (Password = "2024")
- Fluxo básico de 2FA
- Contador de tentativas de login (failedAttempts)
- Diálogos de autenticação: ShowAuthenticationPrompt() e ShowTwoFactorAuthPrompt()
3. Funções de Gerenciamento de Trades:
- Funções de fechamento de posições
- Funções de exclusão de ordens
- Execução de trades
4. Recursos de Comunicação:
- Integração com Telegram via SendMessageToTelegram()
- Botões de mensagens rápidas (8 mensagens predefinidas)
- Caixa de entrada de mensagens com contador de caracteres
5. Painel de Análises:
- Visualização em gráfico de pizza (CreateAnalyticsPanel())
- Análise do histórico de trades (GetTradeData())
- Classes de gráficos personalizados: CCustomPieChart e CAnalyticsChart
6. Estrutura de Tratamento de Eventos:
- OnChartEvent() monolítico com:
- Verificações de cliques em botões
- Lógica de UI/trade/autenticação misturada
- Chamadas diretas de funções sem roteamento
7. Componentes de Segurança:
- Armazenamento de senha em texto simples
- Implementação básica de 2FA
- Nenhuma criptografia para credenciais do Telegram
- Nenhum gerenciamento de sessão
8. Inicialização/Limpeza:
- OnInit() com criação sequencial da UI
- OnDeinit() com destruição dos painéis
- Nenhum sistema de gerenciamento de recursos
9. Tratamento de Erros:
- Instruções básicas Print() para erros
- Nenhum mecanismo de recuperação de erros
- Nenhum rollback de transações
- Validação limitada para operações de trade
10. Formatação, indentação e espaços:
- Esse aspecto é bem coberto pelo MetaEditor.
- Nosso código é legível, exceto por outros pontos como código repetitivo, que abordaremos na próxima seção.
Após analisar o código, aqui está uma coleção de problemas organizacionais que precisam de atenção:
- Estrutura Monolítica — Toda a funcionalidade em um único arquivo
- Acoplamento Forte — Lógica de UI misturada com lógica de negócio
- Padrões Repetitivos — Código semelhante para criação de botões/painéis
- Riscos de Segurança — Credenciais codificadas, sem criptografia
- Escalabilidade Limitada — Nenhuma arquitetura modular
- Convenções de nomenclatura inconsistentes, principalmente em (Elementos de UI e Variáveis Globais)
Reorganizando o código
Após esta etapa, o programa ainda deve ser capaz de executar e manter sua funcionalidade original. Com base em avaliações anteriores, discutiremos alguns aspectos abaixo como forma de melhorar nossa organização de código.
1. Estrutura Monolítica:
Essa situação é desafiadora para nós, pois torna o código desnecessariamente longo. Podemos resolver isso dividindo o código em componentes modulares. Isso envolve desenvolver arquivos separados para diferentes funcionalidades, tornando-os reutilizáveis enquanto mantemos o código principal limpo e gerenciável. As declarações e implementações ficarão fora do arquivo principal e serão incluídas conforme necessário.
Para manter a clareza e evitar sobrecarregar este artigo com informações excessivas, preservei a discussão detalhada para o próximo artigo. No entanto, aqui está um breve exemplo: poderíamos criar um arquivo include para autenticação. Veja o código abaixo:
class AuthManager { private: string m_password; int m_failedAttempts; public: AuthManager(string password) : m_password(password), m_failedAttempts(0) {} bool Authenticate(string input) { if(input == m_password) { m_failedAttempts = 0; return true; } m_failedAttempts++; return false; } bool Requires2FA() const { return m_failedAttempts >= 3; } };
Esse arquivo será então incluído no código principal do nosso Painel de Administração, conforme mostrado abaixo:
#include <AuthManager.mqh>
2. Acoplamento Forte:
Nesta implementação, abordamos o problema de misturar manipuladores de interface do usuário com a lógica de trade. Isso pode ser aprimorado desacoplando-os por meio de interfaces. Para alcançar isso, podemos criar um arquivo de cabeçalho ou classe dedicada, com base na classe integrada CTrade.
Para uma melhor organização, criarei um arquivo de cabeçalho TradeManager para lidar separadamente com a lógica relacionada a trades, tornando-a reutilizável e mais fácil de gerenciar. Ao incluir essa classe personalizada e separar adequadamente a lógica de trade da lógica da interface do usuário, melhoramos a manutenibilidade e a legibilidade do código.
#include<TradeManager.mqh> 3. Padrões de Código Repetidos
O problema aqui é a duplicação do código de criação de UI, particularmente para painéis e botões. Podemos resolver isso criando funções auxiliares de UI, que irão simplificar o processo de construção das interfaces e de seus elementos.
Abaixo está um exemplo de uma função auxiliar para criação de botões:
//+------------------------------------------------------------------+ //| Generic Button Creation Helper | //+------------------------------------------------------------------+ bool CreateButton(CButton &button, CDialog &panel, const string name, const string text, int x1, int y1, int x2, int y2) { if(!button.Create(ChartID(), name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); panel.Add(button); return true; }
Os botões restantes podem ser criados utilizando essa abordagem, eliminando código redundante e garantindo uma implementação mais estruturada e de fácil manutenção. Abaixo está um exemplo da criação de um botão de acesso ao Painel de Gerenciamento de Trades. A maior parte da implementação está incluída no código final organizado, encontrado na seção de resultados.
CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60)
4. Riscos de Segurança:
Por simplicidade, continuamos utilizando senhas codificadas diretamente no código, mas esse aspecto já foi abordado na Parte (VII). Isso pode ser resolvido por meio do uso de configurações criptografadas.
5. Nomenclatura Inconsistente:
Em determinados pontos, utilizei abreviações nos nomes para reduzir o comprimento do texto. No entanto, isso pode criar desafios ao colaborar com outras pessoas. A melhor forma de lidar com isso é aplicar convenções de nomenclatura consistentes.
Por exemplo, no trecho de código abaixo, utilizei um “t” minúsculo em vez de um “T” maiúsculo e uma abreviação para “management”, o que pode gerar confusão para outros desenvolvedores que não conhecem a intenção do autor. Além disso, o nome da função do botão de tema é excessivamente longo e poderia ser mais conciso para melhorar a legibilidade. Veja o exemplo abaixo ilustrando esses problemas:
CButton tradeMgmtAccessButton; // Inconsistent void OnToggleThemeButtonClick(); // Verbose
Aqui está o código corrigido:
CButton TradeManagementAccessButton; // PascalCase void HandleThemeToggle(); // Action-oriented
Resultados e Testes
Após a aplicação cuidadosa da solução, discutida aqui, chegamos ao nosso código final resolvido. Neste trecho, removemos a funcionalidade de tema para que possamos construir um arquivo de cabeçalho dedicado exclusivamente a temas. A decisão visa atender aos problemas associados à extensão de classes integradas para funcionalidades relacionadas a temas.
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property description "A secure and responsive, communications, trade management and analytics Panel" #property version "1.25" //Essential header files included #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> #include <Canvas\Charts\PieChart.mqh> #include <Canvas\Charts\ChartCanvas.mqh> // Input parameters for quick messages input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "YOUR_CHAT_ID"; // Telegram chat ID for notifications input string InputBotToken = "YOUR_BOT_TOKEN"; // Telegram bot token // Security Configuration const string TwoFactorAuthChatId = "REPLACE_WITH_YOUR_CHAT_ID"; // 2FA notification channel const string TwoFactorAuthBotToken = "REPLACE_WITH_YOUR_BOT_TOKEN"; // 2FA bot credentials const string DefaultPassword = "2024"; // Default access password // Global UI Components CDialog AdminHomePanel, TradeManagementPanel, CommunicationsPanel, AnalyticsPanel; CButton HomeButtonComm, HomeButtonTrade, SendButton, ClearButton; CButton ChangeFontButton, ToggleThemeButton, LoginButton, CloseAuthButton; CButton TwoFactorAuthLoginButton, CloseTwoFactorAuthButton, MinimizeCommsButton; CButton CloseCommsButton, TradeMgmtAccessButton, CommunicationsPanelAccessButton; CButton AnalyticsPanelAccessButton, ShowAllButton, QuickMessageButtons[8]; CEdit InputBox, PasswordInputBox, TwoFactorAuthCodeInput; CLabel CharCounter, PasswordPromptLabel, FeedbackLabel; CLabel TwoFactorAuthPromptLabel, TwoFactorAuthFeedbackLabel; // Trade Execution Components CButton BuyButton, SellButton, CloseAllButton, CloseProfitButton; CButton CloseLossButton, CloseBuyButton, CloseSellButton; CButton DeleteAllOrdersButton, DeleteLimitOrdersButton; CButton DeleteStopOrdersButton, DeleteStopLimitOrdersButton; // Security State Management int FailedAttempts = 0; // Track consecutive failed login attempts bool IsTrustedUser = false; // Flag for verified users string ActiveTwoFactorAuthCode = ""; // Generated 2FA verification code // Trade Execution Constants const double DefaultLotSize = 1.0; // Standard trade volume const double DefaultSlippage = 3; // Allowed price deviation const double DefaultStopLoss = 0; // Default risk management const double DefaultTakeProfit = 0; // Default profit target //+------------------------------------------------------------------+ //| Program Initialization | //+------------------------------------------------------------------+ int OnInit() { if(!InitializeAuthenticationDialog() || !InitializeAdminHomePanel() || !InitializeTradeManagementPanel() || !InitializeCommunicationsPanel()) { Print("Initialization failed"); return INIT_FAILED; } AdminHomePanel.Hide(); TradeManagementPanel.Hide(); CommunicationsPanel.Hide(); AnalyticsPanel.Hide(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Trade Management Functions | //+------------------------------------------------------------------+ CTrade TradeExecutor; // Centralized trade execution handler // Executes a market order with predefined parameters // name="orderType">Type of order (ORDER_TYPE_BUY/ORDER_TYPE_SELL) // <returns>True if order execution succeeded</returns> bool ExecuteMarketOrder(int orderType) { double executionPrice = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) : SymbolInfoDouble(Symbol(), SYMBOL_BID); if(executionPrice <= 0) { Print("Price retrieval failed. Error: ", GetLastError()); return false; } bool orderResult = (orderType == ORDER_TYPE_BUY) ? TradeExecutor.Buy(DefaultLotSize, Symbol(), executionPrice, DefaultSlippage, DefaultStopLoss, DefaultTakeProfit) : TradeExecutor.Sell(DefaultLotSize, Symbol(), executionPrice, DefaultSlippage, DefaultStopLoss, DefaultTakeProfit); if(orderResult) { Print(orderType == ORDER_TYPE_BUY ? "Buy" : "Sell", " order executed successfully"); } else { Print("Order execution failed. Error: ", GetLastError()); } return orderResult; } // Closes positions based on specified criteria // name="closureCondition" // 0=All, 1=Profitable, -1=Losing, 2=Buy, 3=Sell bool ClosePositions(int closureCondition) { CPositionInfo positionInfo; for(int i = PositionsTotal()-1; i >= 0; i--) { if(positionInfo.SelectByIndex(i) && (closureCondition == 0 || (closureCondition == 1 && positionInfo.Profit() > 0) || (closureCondition == -1 && positionInfo.Profit() < 0) || (closureCondition == 2 && positionInfo.Type() == POSITION_TYPE_BUY) || (closureCondition == 3 && positionInfo.Type() == POSITION_TYPE_SELL))) { TradeExecutor.PositionClose(positionInfo.Ticket()); } } return true; } //+------------------------------------------------------------------+ //| Authentication Management | //+------------------------------------------------------------------+ CDialog AuthenticationDialog, TwoFactorAuthDialog; /// Initializes the primary authentication dialog bool InitializeAuthenticationDialog() { if(!AuthenticationDialog.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) return false; // Create dialog components if(!PasswordInputBox.Create(ChartID(), "PasswordInput", 0, 20, 70, 260, 95) || !PasswordPromptLabel.Create(ChartID(), "PasswordPrompt", 0, 20, 20, 260, 40) || !FeedbackLabel.Create(ChartID(), "AuthFeedback", 0, 20, 140, 380, 160) || !LoginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140) || !CloseAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) { Print("Authentication component creation failed"); return false; } // Configure component properties PasswordPromptLabel.Text("Enter Administrator Password:"); FeedbackLabel.Text(""); FeedbackLabel.Color(clrRed); LoginButton.Text("Login"); CloseAuthButton.Text("Cancel"); // Assemble dialog AuthenticationDialog.Add(PasswordInputBox); AuthenticationDialog.Add(PasswordPromptLabel); AuthenticationDialog.Add(FeedbackLabel); AuthenticationDialog.Add(LoginButton); AuthenticationDialog.Add(CloseAuthButton); AuthenticationDialog.Show(); return true; } /// Generates a 6-digit 2FA code and sends via Telegram void HandleTwoFactorAuthentication() { ActiveTwoFactorAuthCode = StringFormat("%06d", MathRand() % 1000000); SendMessageToTelegram("Your verification code: " + ActiveTwoFactorAuthCode, TwoFactorAuthChatId, TwoFactorAuthBotToken); } //+------------------------------------------------------------------+ //| Panel Initialization Functions | //+------------------------------------------------------------------+ bool InitializeAdminHomePanel() { if(!AdminHomePanel.Create(ChartID(), "Admin Home Panel", 0, 30, 80, 335, 350)) return false; return CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60) && CreateButton(CommunicationsPanelAccessButton, AdminHomePanel, "CommunicationsPanelAccessButton", "Communications Panel", 10, 70, 250, 110) && CreateButton(AnalyticsPanelAccessButton, AdminHomePanel, "AnalyticsPanelAccessButton", "Analytics Panel", 10, 120, 250, 160) && CreateButton(ShowAllButton, AdminHomePanel, "ShowAllButton", "Show All 💥", 10, 170, 250, 210); } bool InitializeTradeManagementPanel() { if (!TradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 500, 30, 1280, 170)) { Print("Failed to create Trade Management Panel."); return false; } CreateButton(HomeButtonTrade, TradeManagementPanel, "HomeButtonTrade", "Home 🏠", 20, 10, 120, 30) && CreateButton(BuyButton, TradeManagementPanel, "BuyButton", "Buy", 130, 5, 210, 40) && CreateButton(SellButton, TradeManagementPanel, "SellButton", "Sell", 220, 5, 320, 40) && CreateButton(CloseAllButton, TradeManagementPanel, "CloseAllButton", "Close All", 130, 50, 230, 70) && CreateButton(CloseProfitButton, TradeManagementPanel, "CloseProfitButton", "Close Profitable", 240, 50, 380, 70) && CreateButton(CloseLossButton, TradeManagementPanel, "CloseLossButton", "Close Losing", 390, 50, 510, 70) && CreateButton(CloseBuyButton, TradeManagementPanel, "CloseBuyButton", "Close Buys", 520, 50, 620, 70) && CreateButton(CloseSellButton, TradeManagementPanel, "CloseSellButton", "Close Sells", 630, 50, 730, 70) && CreateButton(DeleteAllOrdersButton, TradeManagementPanel, "DeleteAllOrdersButton", "Delete All Orders", 40, 50, 180, 70) && CreateButton(DeleteLimitOrdersButton, TradeManagementPanel, "DeleteLimitOrdersButton", "Delete Limits", 190, 50, 300, 70) && CreateButton(DeleteStopOrdersButton, TradeManagementPanel, "DeleteStopOrdersButton", "Delete Stops", 310, 50, 435, 70) && CreateButton(DeleteStopLimitOrdersButton, TradeManagementPanel, "DeleteStopLimitOrdersButton", "Delete Stop Limits", 440, 50, 580, 70); return true; } //+------------------------------------------------------------------+ //| Two-Factor Authentication Dialog | //+------------------------------------------------------------------+ bool InitializeTwoFactorAuthDialog() { if(!TwoFactorAuthDialog.Create(ChartID(), "Two-Factor Authentication", 0, 100, 100, 500, 300)) return false; if(!TwoFactorAuthCodeInput.Create(ChartID(), "TwoFACodeInput", 0, 20, 70, 260, 95) || !TwoFactorAuthPromptLabel.Create(ChartID(), "TwoFAPromptLabel", 0, 20, 20, 380, 40) || !TwoFactorAuthFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", 0, 20, 140, 380, 160) || !TwoFactorAuthLoginButton.Create(ChartID(), "TwoFALoginButton", 0, 20, 120, 100, 140) || !CloseTwoFactorAuthButton.Create(ChartID(), "Close2FAButton", 0, 120, 120, 200, 140)) { return false; } TwoFactorAuthPromptLabel.Text("Enter verification code sent to Telegram:"); TwoFactorAuthFeedbackLabel.Text(""); TwoFactorAuthFeedbackLabel.Color(clrRed); TwoFactorAuthLoginButton.Text("Verify"); CloseTwoFactorAuthButton.Text("Cancel"); TwoFactorAuthDialog.Add(TwoFactorAuthCodeInput); TwoFactorAuthDialog.Add(TwoFactorAuthPromptLabel); TwoFactorAuthDialog.Add(TwoFactorAuthFeedbackLabel); TwoFactorAuthDialog.Add(TwoFactorAuthLoginButton); TwoFactorAuthDialog.Add(CloseTwoFactorAuthButton); return true; } //+------------------------------------------------------------------+ //| Telegram Integration | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string headers; char postData[], result[]; string requestData = "{\"chat_id\":\"" + chatId + "\",\"text\":\"" + message + "\"}"; StringToCharArray(requestData, postData, 0, StringLen(requestData)); int response = WebRequest("POST", url, headers, 5000, postData, result, headers); if(response == 200) { Print("Telegram notification sent successfully"); return true; } Print("Failed to send Telegram notification. Error: ", GetLastError()); return false; } //+------------------------------------------------------------------+ //| Generic Button Creation Helper | //+------------------------------------------------------------------+ bool CreateButton(CButton &button, CDialog &panel, const string name, const string text, int x1, int y1, int x2, int y2) { if(!button.Create(ChartID(), name, 0, x1, y1, x2, y2)) { Print("Failed to create button: ", name); return false; } button.Text(text); panel.Add(button); return true; } //+------------------------------------------------------------------+ //| Enhanced Event Handling | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK || id == CHARTEVENT_OBJECT_ENDEDIT) { if(sparam == "InputBox") { int length = StringLen(InputBox.Text()); CharCounter.Text(IntegerToString(length) + "/4096"); } else if(id == CHARTEVENT_OBJECT_CLICK) { // Authentication event handling if(sparam == "LoginButton") { string enteredPassword = PasswordInputBox.Text(); if(enteredPassword == DefaultPassword) { FailedAttempts = 0; IsTrustedUser = true; AuthenticationDialog.Destroy(); AdminHomePanel.Show(); } else { if(++FailedAttempts >= 3) { HandleTwoFactorAuthentication(); AuthenticationDialog.Destroy(); InitializeTwoFactorAuthDialog(); } else { FeedbackLabel.Text("Invalid credentials. Attempts remaining: " + IntegerToString(3 - FailedAttempts)); PasswordInputBox.Text(""); } } } if(sparam == "AnalyticsPanelAccessButton") { OnAnalyticsButtonClick(); AdminHomePanel.Hide(); if(!InitializeAnalyticsPanel()) { Print("Failed to initialize Analytics Panel"); return; } // Communications Handling if(sparam == "SendButton") { if(SendMessageToTelegram(InputBox.Text(), InputChatId, InputBotToken)) InputBox.Text(""); } else if(sparam == "ClearButton") { InputBox.Text(""); CharCounter.Text("0/4096"); } else if(StringFind(sparam, "QuickMsgBtn") != -1) { int index = (int)StringToInteger(StringSubstr(sparam, 11)) - 1; if(index >= 0 && index < 8) SendMessageToTelegram(QuickMessageButtons[index].Text(), InputChatId, InputBotToken); } // Trade execution handlers else if(sparam == "BuyButton") ExecuteMarketOrder(ORDER_TYPE_BUY); else if(sparam == "SellButton") ExecuteMarketOrder(ORDER_TYPE_SELL); // Panel Navigation if(sparam == "TradeMgmtAccessButton") { TradeManagementPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "CommunicationsPanelAccessButton") { CommunicationsPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "AnalyticsPanelAccessButton") { OnAnalyticsButtonClick(); AdminHomePanel.Hide(); } else if(sparam == "ShowAllButton") { TradeManagementPanel.Show(); CommunicationsPanel.Show(); AnalyticsPanel.Show(); AdminHomePanel.Hide(); } else if(sparam == "HomeButtonTrade") { AdminHomePanel.Show(); TradeManagementPanel.Hide(); } else if(sparam == "HomeButtonComm") { AdminHomePanel.Show(); CommunicationsPanel.Hide(); } } } } } //+------------------------------------------------------------------+ //| Communications Management | //+------------------------------------------------------------------+ bool InitializeCommunicationsPanel() { if(!CommunicationsPanel.Create(ChartID(), "Communications Panel", 0, 20, 150, 490, 650)) return false; // Create main components if(!InputBox.Create(ChartID(), "InputBox", 0, 5, 25, 460, 95) || !CharCounter.Create(ChartID(), "CharCounter", 0, 380, 5, 460, 25)) return false; // Create control buttons with corrected variable names const bool buttonsCreated = CreateButton(SendButton, CommunicationsPanel, "SendButton", "Send", 350, 95, 460, 125) && CreateButton(ClearButton, CommunicationsPanel, "ClearButton", "Clear", 235, 95, 345, 125) && CreateButton(ChangeFontButton, CommunicationsPanel, "ChangeFontButton", "Font<>", 95, 95, 230, 115) && CreateButton(ToggleThemeButton, CommunicationsPanel, "ToggleThemeButton", "Theme<>", 5, 95, 90, 115); CommunicationsPanel.Add(InputBox); CommunicationsPanel.Add(CharCounter); CommunicationsPanel.Add(SendButton); CommunicationsPanel.Add(ClearButton); CommunicationsPanel.Add(ChangeFontButton); CommunicationsPanel.Add(ToggleThemeButton); return buttonsCreated && CreateQuickMessageButtons(); } bool CreateQuickMessageButtons() { string quickMessages[] = {QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8}; const int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for(int i = 0; i < 8; i++) { const int xPos = startX + (i % 2) * (width + spacing); const int yPos = startY + (i / 2) * (height + spacing); if(!QuickMessageButtons[i].Create(ChartID(), "QuickMsgBtn" + IntegerToString(i+1), 0, xPos, yPos, xPos + width, yPos + height)) return false; QuickMessageButtons[i].Text(quickMessages[i]); CommunicationsPanel.Add(QuickMessageButtons[i]); } return true; } //+------------------------------------------------------------------+ //| Data for Pie Chart | //+------------------------------------------------------------------+ void GetTradeData(int &wins, int &losses, int &forexTrades, int &stockTrades, int &futuresTrades) { wins = 0; losses = 0; forexTrades = 0; stockTrades = 0; futuresTrades = 0; if (!HistorySelect(0, TimeCurrent())) { Print("Failed to select trade history."); return; } int totalDeals = HistoryDealsTotal(); for (int i = 0; i < totalDeals; i++) { ulong dealTicket = HistoryDealGetTicket(i); if (dealTicket > 0) { double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); if (profit > 0) wins++; else if (profit < 0) losses++; string symbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL); if (SymbolInfoInteger(symbol, SYMBOL_SELECT)) { if (StringFind(symbol, ".") == -1) forexTrades++; else { string groupName; if (SymbolInfoString(symbol, SYMBOL_PATH, groupName)) { if (StringFind(groupName, "Stocks") != -1) stockTrades++; else if (StringFind(groupName, "Futures") != -1) futuresTrades++; } } } } } } //+------------------------------------------------------------------+ //| Custom Pie Chart Class | //+------------------------------------------------------------------+ class CCustomPieChart : public CPieChart { public: void DrawPieSegment(double fi3, double fi4, int idx, CPoint &p[], const uint clr) { DrawPie(fi3, fi4, idx, p, clr); // Expose protected method } }; //+------------------------------------------------------------------+ //| Analytics Chart Class | //+------------------------------------------------------------------+ class CAnalyticsChart : public CWnd { private: CCustomPieChart pieChart; // Declare pieChart as a member of this class public: bool CreatePieChart(string label, int x, int y, int width, int height) { if (!pieChart.CreateBitmapLabel(label, x, y, width, height)) { Print("Error creating Pie Chart: ", label); return false; } return true; } void SetPieChartData(const double &values[], const string &labels[], const uint &colors[]) { pieChart.SeriesSet(values, labels, colors); pieChart.ShowPercent(); } void DrawPieChart(const double &values[], const uint &colors[], int x0, int y0, int radius) { double total = 0; int seriesCount = ArraySize(values); if (seriesCount == 0) { Print("No data for pie chart."); return; } for (int i = 0; i < seriesCount; i++) total += values[i]; double currentAngle = 0.0; // Resize the points array CPoint points[]; ArrayResize(points, seriesCount + 1); for (int i = 0; i < seriesCount; i++) { double segmentValue = values[i] / total * 360.0; double nextAngle = currentAngle + segmentValue; // Define points for the pie slice points[i].x = x0 + (int)(radius * cos(currentAngle * M_PI / 180.0)); points[i].y = y0 - (int)(radius * sin(currentAngle * M_PI / 180.0)); pieChart.DrawPieSegment(currentAngle, nextAngle, i, points, colors[i]); currentAngle = nextAngle; } // Define the last point to close the pie points[seriesCount].x = x0 + (int)(radius * cos(0)); // Back to starting point points[seriesCount].y = y0 - (int)(radius * sin(0)); } }; //+------------------------------------------------------------------+ //| Initialize Analytics Panel | //+------------------------------------------------------------------+ bool InitializeAnalyticsPanel() { if (!AnalyticsPanel.Create(ChartID(), "Analytics Panel",0, 500, 450, 1285, 750)) { Print("Failed to create Analytics Panel"); return false; } int wins, losses, forexTrades, stockTrades, futuresTrades; GetTradeData(wins, losses, forexTrades, stockTrades, futuresTrades); CAnalyticsChart winLossChart, tradeTypeChart; // Win vs Loss Pie Chart if (!winLossChart.CreatePieChart("Win vs. Loss", 690, 480, 250, 250)) { Print("Error creating Win/Loss Pie Chart"); return false; } double winLossValues[] = {wins, losses}; string winLossLabels[] = {"Wins", "Losses"}; uint winLossColors[] = {clrGreen, clrRed}; winLossChart.SetPieChartData(winLossValues, winLossLabels, winLossColors); winLossChart.DrawPieChart(winLossValues, winLossColors, 150, 150, 140); AnalyticsPanel.Add(winLossChart); // Trade Type Pie Chart if (!tradeTypeChart.CreatePieChart("Trade Type", 950, 480, 250, 250)) { Print("Error creating Trade Type Pie Chart"); return false; } double tradeTypeValues[] = {forexTrades, stockTrades, futuresTrades}; string tradeTypeLabels[] = {"Forex", "Stocks", "Futures"}; uint tradeTypeColors[] = {clrBlue, clrOrange, clrYellow}; tradeTypeChart.SetPieChartData(tradeTypeValues, tradeTypeLabels, tradeTypeColors); tradeTypeChart.DrawPieChart(tradeTypeValues, tradeTypeColors, 500, 150, 140); AnalyticsPanel.Add(tradeTypeChart); return true; } //+------------------------------------------------------------------+ //| Analytics Button Click Handler | //+------------------------------------------------------------------+ void OnAnalyticsButtonClick() { // Clear any previous pie charts because we're redrawing them ObjectDelete(0, "Win vs. Loss Pie Chart"); ObjectDelete(0, "Trade Type Distribution"); // Update the analytics panel with fresh data AnalyticsPanel.Destroy(); InitializeAnalyticsPanel(); AnalyticsPanel.Show(); } //+------------------------------------------------------------------+ //| Cleanup Operations | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release all UI components AuthenticationDialog.Destroy(); TwoFactorAuthDialog.Destroy(); AdminHomePanel.Destroy(); AnalyticsPanel.Destroy(); }
No processo de organização do código, a transição do código original (v1.24) para a versão atualizada (v1.25) reflete várias melhorias importantes:
1. Modularidade e Estrutura Aprimoradas: O código atualizado introduz um agrupamento mais lógico das funcionalidades dentro de seções. Por exemplo, na v1.24, grande parte da inicialização e do gerenciamento da UI estava dispersa ou não claramente separada. A nova versão organiza o código em seções bem definidas, como “Funções de Gerenciamento de Trades”, “Gerenciamento de Autenticação” e “Funções de Inicialização de Painéis”. Essa segregação torna o código mais legível e mais fácil de manter. Cada seção agora começa com um cabeçalho claro, indicando qual funcionalidade está sendo implementada, o que ajuda a navegar rapidamente pela base de código.
2. Separação de Responsabilidades: Na v1.24, funções como ShowAuthenticationPrompt e ShowTwoFactorAuthPrompt estavam misturadas com declarações globais e careciam de uma separação clara da lógica de inicialização. O código atualizado na v1.25 separa essas responsabilidades de forma mais eficaz. Funções de inicialização, como InitializeAuthenticationDialog e InitializeTwoFactorAuthDialog, agora são distintas dos manipuladores de eventos ou funções utilitárias, reduzindo a complexidade de cada segmento. Essa separação ajuda a compreender o ciclo de vida dos diferentes componentes do EA, desde a inicialização até o tratamento das interações. Além disso, a introdução de classes específicas para lidar com análises (CAnalyticsChart e CCustomPieChart) encapsula a lógica complexa de desenho de gráficos, promovendo reutilização e mantendo o princípio de responsabilidade única para cada classe ou função.
Após a compilação bem-sucedida, o Painel de Administração é iniciado corretamente e solicita a verificação de segurança como etapa inicial. Após a inserção das credenciais corretas, o sistema concede acesso ao Painel Inicial de Administração.
Para referência, a senha padrão está definida como “2024”, conforme especificado no código. Abaixo está uma imagem demonstrando o lançamento do Expert Advisor (EA) no gráfico:

Adicionando o Painel de Administração ao gráfico a partir do novo código-fonte
Um exemplo integrado de código bem organizado
Antes de escrever este artigo, deparei-me com uma implementação integrada da classe Dailog no MQL5. Isso serviu como um exemplo motivador, inspirando esforços para criar código mais legível e reutilizável. O exemplo é um Expert Advisor de Controles, localizado na pasta Experts, dentro de Examples. Veja a imagem abaixo.
Localizando o EA Controls no MetaTrader 5
A aplicação de exemplo é altamente responsiva e inclui diversos recursos de interface. Veja a imagem abaixo.

Adicionando Controls ao gráfico
Para visualizar o código-fonte, abra o MetaEditor, navegue até a pasta Experts e localize o código-fonte do Controls na pasta Examples, conforme mostrado abaixo.
Para visualizar o código-fonte, abra o MetaEditor, navegue até a pasta Experts e localize o código-fonte do Controls na pasta Examples, conforme mostrado abaixo.
Esse arquivo serve como o código principal do programa, com grande parte da lógica de UI distribuída em CControlsDialog, que utiliza a classe Dialog para simplificar a criação da interface. O código-fonte é compacto, legível e escalável.//+------------------------------------------------------------------+ //| Controls.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "ControlsDialog.mqh" //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CControlsDialog ExtDialog; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create application dialog if(!ExtDialog.Create(0,"Controls",0,20,20,360,324)) return(INIT_FAILED); //--- run application ExtDialog.Run(); //--- succeed return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy dialog ExtDialog.Destroy(reason); } //+------------------------------------------------------------------+ //| Expert chart event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event ID const long& lparam, // event parameter of the long type const double& dparam, // event parameter of the double type const string& sparam) // event parameter of the string type { ExtDialog.ChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Esse exemplo demonstra uma organização de código profissional por meio de uma arquitetura modular e princípios de design baseados em boas práticas. O código separa claramente as responsabilidades ao delegar toda a lógica de UI para a classe CControlsDialog (definida no módulo incluído ControlsDialog.mqh), enquanto o arquivo principal se concentra exclusivamente no gerenciamento do ciclo de vida da aplicação.
Essa abordagem modular encapsula os detalhes de implementação, expondo apenas interfaces padronizadas como Create(), Run() e Destroy() para inicialização, execução e limpeza. Encaminhando os eventos do gráfico diretamente para o componente de diálogo por meio do ExtDialog. Encaminhando os eventos dos gráficos diretamente para o componente de diálogo por meio do ExtDialog.
A estrutura atende a altos padrões por meio de um design minimalista do arquivo principal — sem declarações de UI — e impõe limites rígidos entre componentes, permitindo modificações seguras e colaboração em equipe. Esse padrão exemplifica o desenvolvimento escalável em MQL5, onde módulos distintos gerenciam responsabilidades específicas, reduzindo a carga cognitiva e promovendo manutenibilidade por meio de contratos de interface claros e gerenciamento sistemático de recursos.
Conclusão
Iniciamos nossa jornada rumo a uma organização de código mais estruturada e em nível corporativo. Melhorias significativas foram realizadas ao corrigir convenções de nomenclatura inconsistentes, aprimorar comentários, refinar o tratamento de erros e agrupar logicamente funcionalidades relacionadas. Essas mudanças resultaram na redução do tamanho do arquivo principal, separação clara de responsabilidades, criação de componentes reutilizáveis e estabelecimento de convenções de nomenclatura consistentes.
Essas mudanças organizacionais resultam em uma base de código mais fácil de navegar e mais escalável, permitindo atualizações e adições mais simples em áreas específicas de funcionalidade sem afetar outras. Essa abordagem estruturada também facilita melhores testes e depuração ao isolar diferentes partes do sistema.
Nossos próximos passos envolvem modularizar ainda mais o programa para garantir que seus componentes possam ser facilmente reutilizados em outros projetos de Expert Advisor (EA) e indicadores. Esse esforço contínuo acabará beneficiando toda a comunidade de trading. Embora tenhamos estabelecido uma base sólida, ainda existem vários aspectos que merecem discussão e análise mais aprofundadas, as quais exploraremos em maior detalhe em nosso próximo artigo.
Estou confiante de que, com este guia, podemos desenvolver código limpo, legível e escalável. Ao fazer isso, aprimoramos nossos próprios projetos e também atraímos outros desenvolvedores e contribuímos para a construção de uma grande biblioteca de código para reutilização futura. Esse esforço coletivo aumenta a eficiência da nossa comunidade e promove a inovação.
Seus comentários e feedback são bem-vindos na seção abaixo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16539
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Dominando Registros de Log (Parte 5): Otimizando o Handler com Cache e Rotação
Busca oscilatória determinística — Deterministic Oscillatory Search (DOS)
Redes neurais em trading: Extração eficiente de características para classificação precisa (Mantis)
Engenharia de Recursos com Python e MQL5 (Parte III): Ângulo do Preço (2) Coordenadas Polares
- 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