Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (I)
Введение
Длинный код может быть сложным для понимания, особенно если он не организован должным образом. Это часто приводит к отказу от проектов. Но означает ли это, что мне придется отказаться от проекта торговой панели администратора теперь, когда он разросся за счет нескольких интегрированных панелей? Конечно нет! Вместо этого нам нужны стратегии, обеспечивающие бесперебойную работу.
Это подводит нас к сегодняшнему обсуждению, в котором мы рассмотрим, как организация кода может улучшить разработку алгоритмов на MQL5. Успех MQL5 и других крупномасштабных проектов часто можно объяснить их структурированным подходом, позволяющим эффективно управлять и поддерживать обширные базы кодов.
Без надлежащей документации и структуры поддержка кода становится сложной задачей, а внесение изменений становится еще более затруднительным.
В этом обсуждении мы рассмотрим практические решения этих проблем, уделяя особое внимание структурированию торговой панели администратора для долгосрочной масштабируемости. Организация кода — это не просто удобство; это важнейший фактор написания эффективных и удобных в обслуживании программ. Внедряя эти передовые практики, мы можем гарантировать, что проекты MQL5 останутся надежными, доступными для совместного использования и масштабируемыми, что позволит отдельным разработчикам создавать и поддерживать сложные приложения.
Прежде чем мы углубимся в тему, разберем ключевые моменты этой дискуссии:
- Обзор обсуждения
- Организация кода
- Реализация на панели администратора (EA)
- Результаты и тестирование
- Один встроенный пример хорошо организованного кода
- Заключение
Обзор обсуждения
В предыдущей статье серии мы значительно расширили программу, добавив более специализированные панели в панель администратора, сделав ее полезной для любого трейдера. Благодаря этим дополнениям у нас теперь есть четыре панели: главная панель администратора, панель коммуникаций, панель управления торговлей и панель аналитики. Код значительно разросся, очертив основную структуру, но еще многое предстоит сделать для улучшения функциональности.
Когда я задумался о следующем шаге и добавлении еще большего количества функций, я осознал важность пересмотра всего кода с целью его лучшей организации. Вот тогда и возникла идея этой темы. Вместо того чтобы просто представить готовую программу, я посчитал, что будет полезно пройтись по процессу доработки и организации кода. В следующем разделе мы подробнее рассмотрим организацию кода на основе моих исследований.
К концу этого обсуждения вы сможете ответить на следующие вопросы:
- Как разрабатывать большие программы?
- Как сделать так, чтобы другие поняли мою большую программу?
Организация кода
Согласно различным источникам, организация кода — это практика структурирования и упорядочивания кода таким образом, чтобы повысить его читаемость, удобство обслуживания и масштабируемость. Хорошо организованный код облегчает разработчикам понимание, отладку и расширение их программ.
Как заметил программист Чжан Чжаоцзюнь (Zhaojun Zhang), "Организация кода подобна чистоте в доме: вам не нужно убираться каждый день, и вы можете жить в своем доме независимо от того, насколько он захламлен, пока вы можете это терпеть. Она станет актуальной, только когда вам нужно будет срочно найти чистую вещь или когда вы захотите пригласить гостей на шикарный ужин».
Эта аналогия ясно показывает, что организация кода важна для вашего рабочего процесса, а также для других, кто может работать с вашим кодом. Давайте разберем ключевые концепции организации кода — читаемость, поддерживаемость и масштабируемость — и рассмотрим их значение, особенно в контексте разработки алгоритмов на MQL5.
1. Читаемость:
Читабельность определяет, насколько легко человек может понять логику и структуру кода. В контексте MQL5 это особенно важно, поскольку над кодом могут работать несколько разработчиков, и даже если вы работаете в одиночку, в какой-то момент вам захочется пересмотреть или отладить свой собственный код, как я уже упоминал ранее.
Основные особенности:
- Четкое именование переменных: используйте осмысленные имена для переменных, функций и классов. Вместо использования неопределенных названий, таких как a, b или temp, выбирайте описательные, которые передают цель, например movingAveragePeriod или signalStrength.
- Комментирование: хорошие комментарии объясняют, почему существуют определенные блоки кода, а не только то, что они делают. Это необходимо для документирования намерений алгоритма.
- Единообразное форматирование: отступы и межстрочные интервалы помогают разбить код на удобочитаемые блоки. Например, используйте одинаковые отступы для циклов, условных операторов и функций.
- Модульный код: разделение кода на небольшие автономные функции или классы, каждый из которых выполняет определенную задачу (например, вычисление скользящей средней или проверка условий торговли), улучшает читаемость.
- Быстрая отладка: читаемый код облегчает обнаружение ошибок и их исправление.
- Сотрудничество: если ваш код чистый и понятный, другим разработчикам будет гораздо проще сотрудничать с вами или помогать вам устранять неполадки.
- Более быстрое освоение проекта: читаемый код гарантирует, что при повторном возвращении к проекту вам не придется тратить время на повторное понимание собственной работы.
2. Удобство обслуживания:
Удобство обслуживания определяет, насколько легко код может быть изменен или расширен с течением времени, особенно при добавлении новых функций или исправлении ошибок. В алгоритмической торговле, например в MQL5, где стратегии часто развиваются, удобство обслуживания имеет решающее значение для долгосрочного успеха.
Основные особенности:
- Модульность: используя функции или классы для разделения различных задач (например, одна функция для обработки исполнения сделок, другая для расчета индикаторов), вы создаете изолированные части системы. Изменения могут быть внесены в одну область, не затрагивая другие.
- Разделение обязанностей: каждая часть кода должна иметь одну ответственность. Например, логика размещения сделок должна быть отделена от логики оценки рыночных условий.
- Использование библиотек и встроенных функций: вместо того, чтобы изобретать велосипед, используйте встроенные функции и библиотеки MQL5 для выполнения распространенных задач, таких как скользящие средние или размещение ордеров, что может снизить сложность и количество ошибок.
- Контроль версий: используйте системы контроля версий (например, Git и MQL5 Storage) для отслеживания изменений, чтобы можно было выполнить откат, если модификация приводит к ошибкам или неожиданному поведению.
Преимущества:
- Будущие модификации: по мере развития стратегий поддержание хорошо структурированной базы кодов позволяет разработчикам реализовывать новые функции или вносить изменения с минимальными усилиями.
- Исправление ошибок: при обнаружении ошибок поддерживаемый код позволяет быстро устранять неполадки, не нарушая работу других частей системы.
- Эффективность: разработчики тратят меньше времени на выяснение того, как работает код, что приводит к более быстрым обновлениям и меньшему количеству ошибок.
3. Масштабируемость:
Масштабируемость относится к способности кода справляться с растущими объемами работы или удовлетворять растущие требования к данным/функциям. Поскольку торговые стратегии становятся более сложными и требуют больше данных, масштабируемость становится жизненно важной для бесперебойной работы.
Основные особенности:
- Эффективные алгоритмы: в алгоритмической торговле вам может потребоваться обрабатывать большие объемы исторических данных, совершать множество сделок или анализировать несколько активов одновременно. Оптимизация алгоритмов с точки зрения скорости и использования памяти имеет решающее значение.
- Структуры данных: выбор подходящих структур данных, таких как массивы, списки или карты, помогает эффективно управлять большими наборами данных. Вы можете использовать массивы и структуры MQL5 для масштабирования вашей стратегии.
- Параллельная обработка: MQL5 поддерживает многопоточность, что позволяет выполнять несколько задач параллельно. Это особенно полезно в сложных торговых стратегиях или при тестировании на истории, когда различные задачи (например, анализ рынка и исполнение ордеров) могут выполняться одновременно.
- Асинхронные операции: для задач, которые не должны блокировать выполнение других частей алгоритма (например, извлечение данных из внешних API), использование асинхронных операций помогает поддерживать отзывчивость системы.
Преимущества:
- Обработка больших объемов данных: масштабируемый код может обрабатывать большие наборы рыночных данных или включать дополнительные активы без существенного снижения производительности.
- Поддержка роста: если алгоритму необходимо реализовать дополнительные функции (например, торговлю несколькими парами, применение моделей машинного обучения или управление повышенными рисками), масштабируемый код обеспечивает гибкость для роста без серьезных изменений.
- Производительность в реальном времени: в реальной торговой среде масштабируемость гарантирует, что ваш алгоритм сможет обрабатывать потоки данных в реальном времени и исполнять ордера без задержек.
В MQL5 читаемость, удобство обслуживания и масштабируемость часто пересекаются и усиливают друг друга. Например, легко читаемую и модульную функцию легче поддерживать, когда она требует корректировки. Аналогично масштабируемый код, как правило, более модульный, что также повышает его читаемость и удобство обслуживания. При разработке торговых алгоритмов этот баланс гарантирует, что код будет хорошо работать сейчас и его можно будет адаптировать или расширить по мере развития торговых стратегий или по мере роста требований к производительности при наличии большего количества данных.
Например, в этой разработке мы начали с панели коммуникаций в Часть 1. По мере развития проекта мы плавно интегрировали новые панели с различной специализацией, не нарушая основную логику. Это демонстрирует масштабируемость, но все еще есть ключевые концепции, которые следует рассмотреть для улучшения возможности повторного использования существующих функций в коде.
Реализация на панели администратора (EA)
Мы будем ссылаться на код из предыдущей статьи по мере применения нами улучшений в организации кода. Подходы к структурированию кода могут различаться: некоторые разработчики предпочитают организовывать его по мере создания, в то время как другие могут решить оценить и доработать его позже. Независимо от подхода, быстрая оценка помогает определить, соответствует ли код основным стандартам. Как я уже упоминал ранее, приводя цитату Чжана Чжаоцзюня, хорошо организованный код не обязателен. Некоторым разработчикам комфортно работать с неорганизованным кодом, пока он работает. Однако это часто приводит к значительным проблемам, особенно при масштабировании проектов. Плохо структурированный код затрудняет его поддержку, расширение и отладку, что ограничивает долгосрочный рост. Вот почему я настоятельно рекомендую использовать лучшие практики организации кода. Рассмотрим это подробнее в следующем разделе.
Выявление проблем в коде
Пересматривая исходный код Admin Panel V1.24, я решил выделить компоненты, которые облегчат его быстрое понимание и выявление проблем. Как разработчик, я знаю компоненты своей программы. Единственная сложность состоит в том, чтобы организовать ее и сократить, сохранив при этом удобочитаемость. Итак, ниже я выделил около девяти основных моментов, которые позволяют нам уловить идею программы. Далее я подробнее расскажу о вопросах, которые собираюсь рассмотреть.
1. Элементы пользовательского интерфейса и глобальные переменные
// 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. Система аутентификации:
- Жестко запрограммированный пароль (Password = "2024")
- Базовый процесс двухфакторной аутентификации (2FA)
- Счетчик попыток входа (failedAttempts)
- Диалоги аутентификации: ShowAuthenticationPrompt() и ShowTwoFactorAuthPrompt()
3. Функции управления торговлей:
- Функции закрытия позиции
- Функции удаления ордеров
- Исполнение сделки
4. Функции взаимодействия:
- Интеграция Telegram через SendMessageToTelegram()
- Кнопки быстрых сообщений (8 предустановленных сообщений)
- Поле ввода сообщения со счетчиком символов
5. Аналитическая панель:
- Визуализация круговой диаграммы (CreateAnalyticsPanel())
- Анализ истории торговли (GetTradeData())
- Пользовательские классы графиков: CCustomPieChart и CAnalyticsChart
6. Структура обработки событий:
- Монолитный OnChartEvent() с:
- Проверяет нажатия кнопок
- Смешанная логика пользовательского интерфейса/торговли/аутентификации
- Прямые вызовы функций без маршрутизации.
7. Компоненты безопасности:
- Хранение паролей в открытом виде
- Базовая реализация 2FA
- Отсутствие шифрования учетных данных Telegram
- Нет управления сеансом
8. Инициализация/очистка:
- OnInit() с последовательным созданием пользовательского интерфейса
- OnDeinit() с удалением панели
- Нет системы управления ресурсами
9. Обработка ошибок:
- Базовые операторы Print() для ошибок
- Нет механизмов восстановления после ошибок
- Нет откатов транзакций
- Ограниченная проверка торговых операций
10. Форматирование, отступы и пробелы:
- Этот аспект хорошо отражен в MetaEditor.
- Наш код читабелен, за исключением некоторых моментов, таких как повторяющийся код, о котором мы поговорим в следующем разделе.
Вот ряд организационных вопросов, требующих внимания:
- Монолитная структура — все функции в одном файле
- Тесная связь — логика пользовательского интерфейса смешана с бизнес-логикой
- Повторяющиеся паттерны — аналогичный код для создания кнопок/панелей
- Риски безопасности — жестко запрограммированные учетные данные, отсутствие шифрования
- Ограниченная масштабируемость — отсутствие модульной архитектуры
- Непоследовательное именование, особенно в элементах пользовательского интерфейса и глобальных переменных.
Реорганизация кода
После этого шага программа должна по-прежнему работать и сохранять свою первоначальную функциональность. Основываясь на предыдущих оценках, мы обсудим ниже некоторые аспекты, которые позволят нам улучшить организацию кода.
1. Монолитная конструкция:
Такая конструкция делает код неоправданно длинным. Эту проблему можно решить, разделив код на модульные компоненты. Это подразумевает разработку отдельных файлов для различных функций, делая их пригодными для повторного использования, при этом основной код остается чистым и управляемым. Декларации и реализации будут находиться за пределами основного файла и включаться по мере необходимости.
Чтобы сохранить ясность и не перегружать статью лишней информацией, я сохранил подробное обсуждение для следующей статьи. Однако вот краткий пример: мы могли бы создать файл include для аутентификации. Взглянем на фрагмент кода ниже:
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; } };
Затем этот файл будет включен в наш основной код панели администратора, как показано ниже:
#include <AuthManager.mqh>
2. Глубокая связь:
В этой реализации мы решаем проблему смешивания обработчиков пользовательского интерфейса с торговой логикой. Эту проблему можно минимизировать, разделив их с помощью интерфейсов. Чтобы добиться этого, мы можем создать специальный заголовочный файл или класс на основе встроенного класса CTrade.
Для лучшей организации я создам заголовочный файл TradeManager для отдельной обработки логики, связанной с торговлей, что делает его пригодным для повторного использования и более простым в управлении. Включая этот пользовательский класс и правильно разделяя торговую логику и логику пользовательского интерфейса, мы улучшаем удобство поддержки и читаемость кода.
#include<TradeManager.mqh> 3. Повторяющиеся шаблоны кода
Проблема здесь заключается в дублировании кода создания пользовательского интерфейса, особенно для панелей и кнопок. Мы можем решить эту проблему, создав вспомогательные функции пользовательского интерфейса, которые упростят процесс создания интерфейсов и их элементов.
Ниже приведен пример вспомогательной функции для создания кнопки:
//+------------------------------------------------------------------+ //| 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; }
Остальные кнопки можно создать, используя этот подход, что позволяет исключить избыточный код и обеспечить более структурированную и удобную в обслуживании реализацию. Ниже приведен пример создания кнопки доступа к панели управления торговлей. Большая часть реализации включена в окончательный структурированный код, который можно найти в разделе результатов.
CreateButton(TradeMgmtAccessButton, AdminHomePanel, "TradeMgmtAccessButton", "Trade Management Panel", 10, 20, 250, 60)
4. Риски безопасности:
Для простоты мы продолжили использовать жестко запрограммированные пароли (этот момент уже рассматривался в Части (VII)). Эти проблемы можно решить, используя зашифрованную конфигурацию.
5. Непоследовательное наименование:
В определенные моменты я использовал сокращения для названий, чтобы сократить длину текста. Однако это может создать проблемы при взаимодействии с другими разработчиками. Лучший способ решения этой проблемы — внедрение единых соглашений об именовании.
Например, в приведенном ниже фрагменте кода я использовал строчную букву "t" вместо заглавной "T" и сокращение для "management", что может привести к путанице среди других разработчиков, не понимающих намерения автора. Кроме того, название функции для кнопки темы слишком многословно и могло бы быть более кратким для лучшей читаемости. На примере ниже продемонстрированы эти проблемы:
CButton tradeMgmtAccessButton; // Inconsistent void OnToggleThemeButtonClick(); // Verbose
Исправленный код:
CButton TradeManagementAccessButton; // PascalCase void HandleThemeToggle(); // Action-oriented
Результаты и тестирование
После тщательного применения решения мы обсудили здесь наш окончательный решенный код. В этой части мы удалили функционал темы, чтобы создать отдельный заголовочный файл, посвященный теме. Этот шаг направлен на решение проблем, связанных с расширением встроенных классов для функциональности, связанной с темой.
//+------------------------------------------------------------------+ //| 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(); }
В процессе организации кода при переходе от исходного кода (v1.24) к обновленной версии (v1.25) было введено несколько ключевых улучшений:
1. Улучшенные модульность и структура: Обновленный код представляет более логичную группировку функций внутри разделов. Например, в v1.24, большая часть инициализации и управления пользовательским интерфейсом была разбросана или не была четко разделена. В новой версии код организован в четко определенные разделы, такие как "Trade Management Functions" (функции управления торговлей), "Authentication Management" (управление аутентификацией) и "Panel Initialization Functions" (функции инициализации панели). Такое разделение делает код более читаемым и простым в обслуживании. Каждый раздел теперь начинается с понятного заголовка, указывающего, какая функциональность реализуется, что помогает быстро ориентироваться в базе кодов.
2. Разделение интересов: В v1.24 такие функции, как ShowAuthenticationPrompt и ShowTwoFactorAuthPrompt, были смешаны с глобальными объявлениями и не имели четкого разделения с логикой инициализации. Обновленный код в версии 1.25 более эффективно разделяет эти проблемы. Функции инициализации, такие как InitializeAuthenticationDialog и InitializeTwoFactorAuthDialog, теперь отделены от обработчиков событий или служебных функций, что снижает сложность в каждом сегменте. Такое разделение помогает понять жизненный цикл различных компонентов советника, от инициализации до обработки взаимодействия. Кроме того, введение специальных классов для обработки аналитики (CAnalyticsChart и CCustomPieChart) инкапсулирует сложную логику построения графиков, способствуя повторному использованию и поддержанию единого принципа ответственности для каждого класса или функции.
После успешной компиляции панель администратора успешно запускается и на первом этапе запрашивает проверку безопасности. После ввода правильных учетных данных система предоставит доступ к главной панели администратора.
Для справки: пароль по умолчанию — "2024", как указано в коде. Ниже представлено изображение, демонстрирующее запуск советника (EA) на графике:

Добавление панели администратора в график из нового исходного кода
Один встроенный пример хорошо организованного кода
Прежде чем написать эту статью, я наткнулся на встроенную реализацию класса Dailog в MQL5. Она вдохновила меня на создание более читаемого и пригодного для повторного использования кода. Примером является советник Controls, расположенный в папке Experts в Examples. Смотрите изображение ниже.
Расположение советника Controls в MetaTrader 5
Пример приложения отличается высокой отзывчивостью и включает в себя многочисленные функции интерфейса. Смотрите изображение ниже.

Добавление Controls на график
Чтобы просмотреть исходный код, откройте MetaEditor, перейдите в папку Experts и найдите исходный код Controls в папке Examples, как показано ниже.
Доступ к исходному коду Controls в MetaEditor
Это основной код программы, большая часть логики пользовательского интерфейса распределена в CControlsDialog, который использует класс Dialog для упрощения создания интерфейса. Исходный код компактен, удобочитаем и масштабируем.//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
В этом примере демонстрируется профессиональная организация кода с помощью модульной архитектуры и принципов передовой практики проектирования. Код четко разделяет задачи, делегируя всю логику пользовательского интерфейса классу CControlsDialog (определенному во включенном модуле ControlsDialog.mqh), в то время как основной файл фокусируется исключительно на управлении жизненным циклом приложения.
Этот модульный подход инкапсулирует детали реализации, предоставляя только стандартизированные интерфейсы, такие как Create(), Run() и Destroy(), для инициализации, выполнения и очистки. События графика отправляются непосредственно в компонент диалога через ExtDialog. Архитектура отделяет обработку событий от базовой логики приложения, обеспечивая возможность повторного использования и тестирования.
Структура соответствует высоким стандартам благодаря минималистичному дизайну основного файла (не содержит объявлений пользовательского интерфейса) и обеспечивает строгие границы компонентов, что обеспечивает безопасные изменения и совместную работу команды. Этот паттерн служит примером масштабируемой разработки MQL5, где отдельные модули выполняют определенные обязанности, снижая когнитивную нагрузку и одновременно повышая удобство обслуживания за счет четких интерфейсных контрактов и систематического управления ресурсами.
Заключение
Мы встали на путь создания более структурированной и корпоративной организации кода. Значительные улучшения были достигнуты за счет устранения несоответствий в соглашениях об именовании, улучшения комментариев, уточнения обработки ошибок и логической группировки связанных функций. Эти изменения привели к уменьшению размера основного файла, четкому разделению задач, созданию повторно используемых компонентов и установлению единых соглашений об именовании.
В результате этих организационных изменений база кодов становится более удобной для навигации и более масштабируемой, что позволяет легче вносить обновления и дополнения в определенные области функциональности, не затрагивая другие. Этот структурированный подход также способствует лучшему тестированию и отладке за счет изоляции различных частей системы.
Наши следующие шаги включают дальнейшую модуляризацию нашей программы, чтобы гарантировать возможность легкого повторного использования ее компонентов в других проектах советников и индикаторов. Эти постоянные усилия в конечном итоге принесут пользу всему торговому сообществу. Хотя мы заложили прочную основу, все еще есть несколько аспектов, требующих более глубокого обсуждения и анализа, которые мы более подробно рассмотрим в нашей следующей статье.
Я уверен, что с помощью этого руководства мы сможем разработать чистый, читаемый и масштабируемый код. Поступая таким образом, мы улучшаем собственные проекты, а также привлекаем других разработчиков и вносим вклад в создание большой библиотеки кода для будущего повторного использования. Эти коллективные усилия повышают эффективность нашего сообщества и стимулируют инновации.
Комментарии и отзывы приветствуются.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16539
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Автоматизация торговых стратегий на MQL5 (Часть 17): Освоение стратегии скальпинга Grid-Mart с динамической информационной панелью
Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II)
Искусство ведения логов (Часть 5): Оптимизация обработчика с помощью кэширования и ротации
Выборочные методы MCMC — Алгоритм Метрополиса-Гастингса
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования