English Deutsch
preview
MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化

MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化

MetaTrader 5 | 18 6月 2025, 13:30
45 0
Clemence Benjamin
Clemence Benjamin

内容



はじめに

本ディスカッションは、保守性の高い管理パネルエキスパートアドバイザー(EA)の開発における画期的な進展を示しています。前回の記事で紹介したコード構成によりメインコードの品質は大幅に向上しました。本稿ではさらに一歩進み、主要なコンポーネントを外部ファイルにモジュール化する手法を取り入れます。この方法により、将来的なアップデートの際もコード全体を乱すことなく、個別のコンポーネントだけを効率的に改良できるようになります。

このメリットがよく分かったのは、コミュニケーションパネルの改良を行ったときでした。巨大でモノリシックなコードベースをスクロールして目的の箇所を探すのは非常に困難でした。しかし、コードを構造化されたモジュールに分割することで、ナビゲーションが格段に容易になり、開発や保守の効率が大幅に向上しました。

私たちのインスピレーションは、コード構成のベストプラクティスを体現する、しっかりと構造化されたプロジェクトから得ています。今回はプログラムの主要機能を担うカスタムクラスを導入し、モジュール化を実践していきます。以下に、開発を予定しているモジュールの全リストを示します。

モジュールファイル 詳細
AdminHomeDialog.mqh 取引管理パネルの中央セクションを宣言し、プログラム内の他のユーティリティへのアクセスを提供します。
Authentication.mqh パスワード検証や2要素認証などのユーザー認証を管理します。
ThemeManager.mqh 管理パネルの外観とスタイルの管理を担当します。
Telegram.mqh 通常はメッセージや通知の送信のために、Telegramと対話するための関数とクラスが含まれています。
CommunicationsDialog.mqh 管理パネル内のコミュニケーション機能に関連するユーザーインターフェイス(UI)とインタラクションの処理を担当します。 
AnalyticsDialog.mqh 取引統計、パフォーマンスメトリック、視覚的なチャートなどの分析データをダイアログパネル内に表示および管理します。
 TradeManagementDialog.mqh 取引関連タスクのUI作成を処理し、ユーザーが取引を効率的に実行および管理できるようにします。

これらのファイルが正常に作成されたら、メインコードにインクルードします。

#include <Telegram.mqh>
#include <Authentication.mqh>
#include <AdminHomeDialog.mqh>
#include  <AnalyticsDialog.mqh>
#include <TradeManagementDialog.mqh>
#include <CommunicationDialog.mqh>

パネルコンポーネントに関するすべての宣言はインクルードファイルにまとめ、メインコードには主に定義を記述します。一般的に定義は宣言よりもコンパクトなため、この方法によってメインプログラムはすっきりと整理され、可読性や保守性が向上します。

これらの工夫によってプロジェクトがどのように進化していくか、もうお分かりかと思います。次のセクションでは、モジュール化の詳細な解説と、それを本プロジェクトに実装する手順を紹介します。

宣言と実装

メインコードとヘッダーファイルの関係



議論の概要

冒頭で簡単に触れた内容を踏まえ、ここからはモジュール化についてより詳しく掘り下げていきます。その後、コードコンポーネントの開発と実装に進みます。各モジュールは、コードの各行が果たす機能を丁寧に分解しながら解説します。

最後に、これらすべてのモジュールを統合し、取引管理パネルの新しいメインコードベースを構築します。これにより、より構造化され効率的なシステムを一から作り上げることになります。

本議論の終わりには、以下のファイルを開発、統合、テスト済みの状態に仕上げます。

  • AdminHomeDialog.mqh
  • Authentication.mqh
  • Telegram.mqh

モジュール化

MQL5プログラミングにおけるモジュール化とは、主にクラス、関数、インクルードファイルを活用して、プログラムをより小さく独立した再利用可能な部品に分割する手法を指します。この方法により、UIコンポーネントや取引ロジックなどの特定機能をモジュールやクラスにカプセル化でき、必要に応じて、アプリケーション内や複数のプロジェクトで再利用・インスタンス化できます。こうしたモジュール化は、あるモジュールの修正が他の部分に影響を与えにくくなるため、コードの管理や保守が容易になり、バグの発生を抑制できます。また、コードの再利用性が向上し、可読性が高まることで、MetaTrader 5環境でのチーム開発もより効率的になります。

この文脈では、前述の導入部で新プログラムのサブコンポーネントをすでに概説しています。さらに詳しく学びたい場合には、多数のリソースや記事で様々なモジュール化の手法が紹介されています。

今後のステップでは、各モジュールの開発過程を丁寧に解説し、その実装および統合の仕組みをしっかり理解できるよう説明していきます。


コードの実装

ここからは、これまでのMQL5の知識を活かし、取引管理パネルEAの主要コンポーネントを開発していきます。幸いにも、これらのファイルは容易に他のプロジェクトへ適応・統合できる設計となっています。

MQL5のヘッダーファイルの主な構造

MQL5のヘッダーファイル(通常は拡張子.mqh)は、クラス、定数、列挙型、関数プロトタイプを定義し、他のMQL5スクリプトやEAにインクルードして利用できる場所として使われます。以下は、組み込みヘッダーファイルのコードを参考にした典型的な構成例です。

  1. ファイルのメタデータ:  著作権情報、リンク、バージョン情報などの記載
  2. include文:依存する他のヘッダーファイルやライブラリの読み込み
  3. 定義/定数: マクロや定数の定義。クラス内または他のコードから一貫した値を利用する
  4. クラス宣言: クラスの定義(継承があればそれも含む)、privateメンバー、public・protectedメソッドの宣言
  5. イベントマッピング: イベント駆動型プログラミングに対応するため、メンバー関数とイベントを関連付けるマクロを使用する
  6. メソッドの実装: 必須ではないが、小規模クラスではヘッダー内に実装を含めることが多い。MQL5ではパフォーマンス面から関数呼び出しのオーバーヘッドを減らす狙いがある。
  7. コンストラクタとデストラクタ: クラスのオブジェクト生成・破棄時の処理を定義

上記の概要を踏まえ、以下にサンプルコードのテンプレートを示します。

// Meta Data here on top.
#include   // Include other necessary libraries or headers
#include 

//+------------------------------------------------------------------+
//| Defines                                                          |
//+------------------------------------------------------------------+
#define CONSTANT_NAME1    (value1)  // Constants or macro definitions
#define CONSTANT_NAME2    (value2)

//+------------------------------------------------------------------+
//| Class CClass                                                     |
//| Usage: Description of class purpose                              |
//+------------------------------------------------------------------+
class CClass : public CParentClass  // Inherits from another class if needed
  {
private:
   // Private member variables
   CSomeControl   m_control;  // Example control member

public:
   CClass(void);              // Constructor
   ~CClass(void);             // Destructor
   virtual bool   Create(/* parameters */);  // Virtual method for polymorphism
   virtual bool   OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   // Protected methods or members
   bool           CreateSomeControl(void);
   void           SomeEventHandler(void);

  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CClass)
   ON_EVENT(SOME_EVENT,m_control,SomeEventHandler)
EVENT_MAP_END(CParentClass)

// Constructor implementation
CClass::CClass(void)
  {
  }

// Destructor implementation
CClass::~CClass(void)
  {
  }

// Method implementations if included in the header
bool CClass::Create(/* parameters */)
  {
   // Implementation of create method
  }

// Event handler examples
void CClass::SomeEventHandler(void)
  {
   // Handle the event
  }

//+------------------------------------------------------------------+

MetaEditorでヘッダーファイルを作成するには、Ctrl+Nを押すか、メニューで移動して[新規作成]を選択します。表示されるウィンドウで「Include (*.mqh)」を選択し、編集を開始します。デフォルトで生成されるテンプレートには、編集の手助けとなるコメントが記載されています。

下の画像を参照してください。

インクルードファイルを作成する

MetaEditorで新しいヘッダーファイルを作成する

以下は、デフォルトのヘッダーファイルのテンプレートです。編集の参考になるコメントが含まれています。

//+------------------------------------------------------------------+
//|                                                     Telegram.mqh |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+

管理者ホームヘッダーファイル

このセクションでは、MQL5プログラムの管理パネルのメインインターフェイスとなるCAdminHomeDialogクラスを開発します。このクラスは、ダイアログやボタン操作に必要なヘッダーファイルを統合し、あらかじめ定義された定数を活用してパネルのサイズや間隔を一貫して管理します。

//+------------------------------------------------------------------+
//| AdminHomeDialog.mqh                                              |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>

//+------------------------------------------------------------------+
//| Defines                                                          |
//+------------------------------------------------------------------+
#define ADMIN_PANEL_WIDTH   (335)
#define ADMIN_PANEL_HEIGHT  (350)
#define INDENT_LEFT         (11)
#define INDENT_TOP          (11)
#define INDENT_RIGHT        (11)
#define INDENT_BOTTOM       (11)
#define CONTROLS_GAP_X      (5)
#define CONTROLS_GAP_Y      (5)
#define BUTTON_WIDTH        (250)
#define BUTTON_HEIGHT       (40)

CAdminHomeDialogクラスはCAppDialogを継承しており、管理パネルの各セクションへのスムーズなナビゲーションを提供する4つの主要なボタン(m_tradeMgmtButtonm_commButtonm_analyticsButtonm_showAllButton)を含んでいます。クラス構造はシンプルに保たれており、コンストラクタとデストラクタは最小限に抑えられています。一方で、Createメソッドではすべてのボタンが適切に初期化され、快適なユーザー体験を実現しています。

//+------------------------------------------------------------------+
//| CAdminHomeDialog class                                           |
//+------------------------------------------------------------------+
class CAdminHomeDialog : public CAppDialog
{
private:
    CButton m_tradeMgmtButton;
    CButton m_commButton;
    CButton m_analyticsButton;
    CButton m_showAllButton;

public:
    CAdminHomeDialog(void) {}
    ~CAdminHomeDialog(void) {}

    virtual bool Create(const long chart, const string name, const int subwin,
                       const int x1, const int y1, const int x2, const int y2);
    virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
};

//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CAdminHomeDialog::Create(const long chart, const string name, const int subwin,
                             const int x1, const int y1, const int x2, const int y2)
{
    if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
        return false;

    if(!CreateTradeMgmtButton()) return false;
    if(!CreateCommButton()) return false;
    if(!CreateAnalyticsButton()) return false;
    if(!CreateShowAllButton()) return false;

    return true;
}

ユーザーの操作はOnEventメソッド内で処理され、ボタンクリック時にデバッグメッセージが表示され、それぞれのイベントハンドラ(OnClickTradeManagementOnClickCommunicationsOnClickAnalyticsOnClickShowAll)が呼び出されます。これらのハンドラは現在、操作のログを記録するだけですが、今後機能拡張に伴い処理内容が充実していく予定です。

//+------------------------------------------------------------------+
//| Event Handling (Enhanced Debugging)                              |
//+------------------------------------------------------------------+
bool CAdminHomeDialog::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    if(id == CHARTEVENT_OBJECT_CLICK)
    {
        Print("Clicked object: ", sparam); // Debug which object was clicked
        
        if(sparam == m_tradeMgmtButton.Name())
        {
            Print("Trade Management button detected");
            OnClickTradeManagement();
            return true;
        }
        else if(sparam == m_commButton.Name())
        {
            Print("Communications button detected");
            OnClickCommunications();
            return true;
        }
        else if(sparam == m_analyticsButton.Name())
        {
            Print("Analytics button detected");
            OnClickAnalytics();
            return true;
        }
        else if(sparam == m_showAllButton.Name())
        {
            Print("Show All button detected");
            OnClickShowAll();
            return true;
        }
    }
    return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}

//+------------------------------------------------------------------+
//| Button Click Handlers                                            |
//+------------------------------------------------------------------+
void CAdminHomeDialog::OnClickTradeManagement() { Print("Trade Management Panel clicked"); }
void CAdminHomeDialog::OnClickCommunications()  { Print("Communications Panel clicked"); }
void CAdminHomeDialog::OnClickAnalytics()       { Print("Analytics Panel clicked"); }
void CAdminHomeDialog::OnClickShowAll()         { Print("Show All clicked"); }

ボタン作成メソッド(CreateTradeMgmtButton、CreateCommButton、CreateAnalyticsButton、CreateShowAllButton)は、固有の識別子を持ち、正確な位置に配置され、明確なラベルが付けられたボタンを動的に生成します。特に[Show All]ボタンには絵文字が取り入れられており、ユーザーインターフェイスの魅力を高めています。今後の開発においては、パフォーマンスや使い勝手を最適化するためのさらなる改善や調整がおこなわれる予定です。

//+------------------------------------------------------------------+
//| Control Creation Methods                                         |
//+------------------------------------------------------------------+
bool CAdminHomeDialog::CreateTradeMgmtButton()
{
    int x = INDENT_LEFT;
    int y = INDENT_TOP;
    return m_tradeMgmtButton.Create(m_chart_id, m_name+"_TradeBtn", m_subwin,
                                  x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT)
        && m_tradeMgmtButton.Text("Trade Management Panel")
        && Add(m_tradeMgmtButton);
}

bool CAdminHomeDialog::CreateCommButton()
{
    int x = INDENT_LEFT;
    int y = INDENT_TOP + BUTTON_HEIGHT + CONTROLS_GAP_Y;
    return m_commButton.Create(m_chart_id, m_name+"_CommBtn", m_subwin,
                             x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT)
        && m_commButton.Text("Communications Panel")
        && Add(m_commButton);
}

bool CAdminHomeDialog::CreateAnalyticsButton()
{
    int x = INDENT_LEFT;
    int y = INDENT_TOP + (BUTTON_HEIGHT + CONTROLS_GAP_Y) * 2;
    return m_analyticsButton.Create(m_chart_id, m_name+"_AnalyticsBtn", m_subwin,
                                  x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT)
        && m_analyticsButton.Text("Analytics Panel")
        && Add(m_analyticsButton);
}

bool CAdminHomeDialog::CreateShowAllButton()
{
    int x = INDENT_LEFT;
    int y = INDENT_TOP + (BUTTON_HEIGHT + CONTROLS_GAP_Y) * 3;
    return m_showAllButton.Create(m_chart_id, m_name+"_ShowAllBtn", m_subwin,
                                x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT)
        && m_showAllButton.Text("Show All 💥")
        && Add(m_showAllButton);
}

メインプログラムでのAdminHomeDialog.mqhの実装

1. #include "AdminHomeDialog.mqh"によるインクルード

#include "AdminHomeDialog.mqh"

AdminHomeDialog.mqhをインクルードすることで、メインスクリプト内でCAdminHomeDialogクラスを使用できるようになります。これを含めないと、コンパイラはCAdminHomeDialogを認識できず、エラーが発生します。このようなモジュール化のアプローチにより、メインスクリプトはシンプルに保たれつつ、ダイアログの実装は別ファイルに分離され、コードの整理や保守性が向上します。が発生します。

2. CAdminHomeDialog ExtDialogとして宣言する

CAdminHomeDialog ExtDialog;

ExtDialogCAdminHomeDialogのインスタンスとして宣言することで、スクリプト全体で管理者ホームパネルを参照・操作できるようになります。このオブジェクトは、パネルの作成、表示、イベント管理を担当し、さまざまな関数からアクセス可能にします。

3. CreateHiddenPanels内でExtDialogを使用して作成する

bool CreateHiddenPanels()
{
    bool success = ExtDialog.Create(0, "Admin Home", 0, 
                    MAIN_DIALOG_X, MAIN_DIALOG_Y, 
                    MAIN_DIALOG_X + MAIN_DIALOG_WIDTH, 
                    MAIN_DIALOG_Y + MAIN_DIALOG_HEIGHT);
    if(success) 
    {
        ExtDialog.Hide();
        ChartRedraw();
    }
    return success;
}

Createメソッドは、パネルを特定のサイズで初期化し、チャート上の正しい位置に配置します。これをCreateHiddenPanels内に配置することで、初期化時にパネルが一度だけ作成されるようにし、セットアップの手順を整理するとともに、不必要な再初期化を防ぎます。

4.OnChartEventの認証ステータスに基づいて表示または非表示になる

if(authManager.IsAuthenticated())
{
    if(!ExtDialog.IsVisible())
    {
        ExtDialog.Show();
        ChartRedraw();
    }
    ExtDialog.ChartEvent(id, lparam, dparam, sparam);
}
else
{
    if(ExtDialog.IsVisible()) 
    {
        ExtDialog.Hide();
    }
}

管理ホームパネルは、認証が成功した場合にのみアクセス可能となります。authManager.IsAuthenticatedを確認することで、認証されていないユーザーがパネルと操作できないようにしています。認証が有効な場合にのみパネルが表示され、それ以外は非表示のままとなるため、セキュリティとアクセス制御が強化されます。

5.スクリプトが削除されるとOnDeinitで破棄される

void OnDeinit(const int reason)
{
    ExtDialog.Destroy(reason);
}

EAがチャートから削除される際に、ExtDialog.Destroyを呼び出すことで、パネルに割り当てられたリソースを確実に解放します。これにより、メモリリークや将来のスクリプト実行時に干渉する可能性のある孤立したグラフィックオブジェクトの発生を防止できます。

Telegramヘッダーファイル

Telegram関数はシンプルで直接的な動作のため、ヘッダーソースにそのままコピーして作成しています。一方で、これとは異なり、前述のようにクラスやメソッド、コンストラクタ、デストラクタが必要なファイルはより構造化されたセットアップが求められます。したがって、このTelegramヘッダーファイルは今回作成する中で最もシンプルなものです。関数をモジュール化することで、メインコードの長さを短縮し、他のプロジェクトでも簡単に再利用できるようになります。

//+------------------------------------------------------------------+
//|                                                     Telegram.mqh |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
//+------------------------------------------------------------------+
//| Telegram.mqh - Telegram Communication Include File               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Send the message to Telegram                                     |
//+------------------------------------------------------------------+
bool SendMessageToTelegram(string message, string chatId, string botToken)
  {
   string url = "https://api.telegram.org/bot" + botToken + "/sendMessage";
   string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}";

   char postData[];
   ArrayResize(postData, StringToCharArray(jsonMessage, postData) - 1);

   int timeout = 5000;
   char result[];
   string responseHeaders;
   int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders);

   if (responseCode == 200)
     {
      Print("Message sent successfully: ", message);
      return true;
     }
   else
     {
      Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError());
      Print("Response: ", CharArrayToString(result));
      return false;
     }
  }
//+------------------------------------------------------------------+

メインコードでのTelegramヘッダーの実装

これは2つのステップで簡単に実行できます。

1. メインコード内でヘッダーファイルをインクルードします。具体的には次のように記述します。

#include<Telegram.mqh>

上記はファイルがMQL5/Includeディレクトリに保存されている場合にのみ機能します。もし別のサブフォルダに保存されている場合は、次のようにサブフォルダ名も明示する必要があります。

#include <FolderName\Telegram.mqh> // Replace FolderName with actual location name

2. 最後に、必要なタイミングでメインコード内から関数を呼び出します。具体的には以下のようにおこないます。

SendMessageToTelegram("Your verification code: " + ActiveTwoFactorAuthCode, 
                         TwoFactorAuthChatId, TwoFactorAuthBotToken)

認証ヘッダーファイルの開発

これまでの開発で、セキュリティプロンプトのロジックが進化し、管理パネルの各バージョンで一貫して使用されていることを見てきました。このロジックは、特に機能を分類したモジュールを開発する際に、他のパネル関連プロジェクトにも応用可能です。この段階では、以前の管理パネルで使用されていたすべてのセキュリティロジックを統合したAuthentication.mqhを開発します。以下にコードを共有し、その動作の仕組みについて説明していきます。

//+------------------------------------------------------------------+
//|                                          authentication.mqh      |
//|                           Copyright 2024, Clemence Benjamin      |
//|        https://www.mql5.com/ja/users/billionaire2024/seller      |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.0"
#property strict

// Authentication Dialog Coordinates
#define AUTH_DIALOG_X         100
#define AUTH_DIALOG_Y         100
#define AUTH_DIALOG_WIDTH     300
#define AUTH_DIALOG_HEIGHT    200

#define PASS_INPUT_X          20
#define PASS_INPUT_Y          50
#define PASS_INPUT_WIDTH      260  // Wider input field
#define PASS_INPUT_HEIGHT     30

#define PASS_LABEL_X          20
#define PASS_LABEL_Y          20
#define PASS_LABEL_WIDTH      200
#define PASS_LABEL_HEIGHT     20

#define FEEDBACK_LABEL_X      20
#define FEEDBACK_LABEL_Y      100
#define FEEDBACK_LABEL_WIDTH  260
#define FEEDBACK_LABEL_HEIGHT 40

// Button spacing adjustments
#define LOGIN_BTN_X           20
#define LOGIN_BTN_Y           130
#define LOGIN_BTN_WIDTH       120
#define LOGIN_BTN_HEIGHT      30

#define CANCEL_BTN_X          160  // Added 20px spacing from login button
#define CANCEL_BTN_Y          130
#define CANCEL_BTN_WIDTH      120
#define CANCEL_BTN_HEIGHT     30

// Two-Factor Authentication Dialog Coordinates
#define TWOFA_DIALOG_X        100
#define TWOFA_DIALOG_Y        100
#define TWOFA_DIALOG_WIDTH    300
#define TWOFA_DIALOG_HEIGHT   200

#define TWOFA_INPUT_X         20
#define TWOFA_INPUT_Y         50
#define TWOFA_INPUT_WIDTH     180
#define TWOFA_INPUT_HEIGHT    30

#define TWOFA_LABEL_X         20
#define TWOFA_LABEL_Y         20
#define TWOFA_LABEL_WIDTH     260
#define TWOFA_LABEL_HEIGHT    20

#define TWOFA_FEEDBACK_X      20
#define TWOFA_FEEDBACK_Y      100
#define TWOFA_FEEDBACK_WIDTH  260
#define TWOFA_FEEDBACK_HEIGHT 40

#define TWOFA_VERIFY_BTN_X    60
#define TWOFA_VERIFY_BTN_Y    130
#define TWOFA_VERIFY_WIDTH    120
#define TWOFA_VERIFY_HEIGHT   30

#define TWOFA_CANCEL_BTN_X    140
#define TWOFA_CANCEL_BTN_Y    130
#define TWOFA_CANCEL_WIDTH    60
#define TWOFA_CANCEL_HEIGHT   30

#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>
#include <Telegram.mqh>

class CAuthenticationManager {
private:
    CDialog m_authDialog;
    CDialog m_2faDialog;
    CEdit m_passwordInput;
    CEdit m_2faCodeInput;
    CLabel m_passwordLabel;
    CLabel m_feedbackLabel;
    CLabel m_2faLabel;
    CLabel m_2faFeedback;
    CButton m_loginButton;
    CButton m_closeAuthButton;
    CButton m_2faLoginButton;
    CButton m_close2faButton;

    string m_password;
    string m_2faChatId;
    string m_2faBotToken;
    int m_failedAttempts;
    bool m_isAuthenticated;
    string m_active2faCode;

public:
    CAuthenticationManager(string password, string twoFactorChatId, string twoFactorBotToken) :
        m_password(password),
        m_2faChatId(twoFactorChatId),
        m_2faBotToken(twoFactorBotToken),
        m_failedAttempts(0),
        m_isAuthenticated(false),
        m_active2faCode("")
    {
    }

    ~CAuthenticationManager()
    {
        m_authDialog.Destroy();
        m_2faDialog.Destroy();
    }

    bool Initialize() {
        if(!CreateAuthDialog() || !Create2FADialog()) {
            Print("Authentication initialization failed");
            return false;
        }
        m_2faDialog.Hide();  // Ensure 2FA dialog starts hidden
        return true;
    }

    bool IsAuthenticated() const { return m_isAuthenticated; }

    void HandleEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
        if(id == CHARTEVENT_OBJECT_CLICK) {
            if(sparam == "LoginButton") HandleLoginAttempt();
            else if(sparam == "2FALoginButton") Handle2FAAttempt();
            else if(sparam == "CloseAuthButton") m_authDialog.Hide();
            else if(sparam == "Close2FAButton") m_2faDialog.Hide();
        }
    }

private:
    bool CreateAuthDialog() {
    if(!m_authDialog.Create(0, "Authentication", 0, 
       AUTH_DIALOG_X, AUTH_DIALOG_Y, 
       AUTH_DIALOG_X + AUTH_DIALOG_WIDTH, 
       AUTH_DIALOG_Y + AUTH_DIALOG_HEIGHT)) 
       return false;

    if(!m_passwordInput.Create(0, "PasswordInput", 0, 
       PASS_INPUT_X, PASS_INPUT_Y, 
       PASS_INPUT_X + PASS_INPUT_WIDTH, 
       PASS_INPUT_Y + PASS_INPUT_HEIGHT) ||
       !m_passwordLabel.Create(0, "PasswordLabel", 0, 
       PASS_LABEL_X, PASS_LABEL_Y, 
       PASS_LABEL_X + PASS_LABEL_WIDTH, 
       PASS_LABEL_Y + PASS_LABEL_HEIGHT) ||
       !m_feedbackLabel.Create(0, "AuthFeedback", 0, 
       FEEDBACK_LABEL_X, FEEDBACK_LABEL_Y, 
       FEEDBACK_LABEL_X + FEEDBACK_LABEL_WIDTH, 
       FEEDBACK_LABEL_Y + FEEDBACK_LABEL_HEIGHT) ||
       !m_loginButton.Create(0, "LoginButton", 0, 
       LOGIN_BTN_X, LOGIN_BTN_Y, 
       LOGIN_BTN_X + LOGIN_BTN_WIDTH, 
       LOGIN_BTN_Y + LOGIN_BTN_HEIGHT) ||
       !m_closeAuthButton.Create(0, "CloseAuthButton", 0, 
       CANCEL_BTN_X, CANCEL_BTN_Y, 
       CANCEL_BTN_X + CANCEL_BTN_WIDTH, 
       CANCEL_BTN_Y + CANCEL_BTN_HEIGHT))
       return false;       

        m_passwordLabel.Text("Enter Password:");
        m_feedbackLabel.Text("");
        m_feedbackLabel.Color(clrRed);
        m_loginButton.Text("Login");
        m_closeAuthButton.Text("Cancel");

        m_authDialog.Add(m_passwordInput);
        m_authDialog.Add(m_passwordLabel);
        m_authDialog.Add(m_feedbackLabel);
        m_authDialog.Add(m_loginButton);
        m_authDialog.Add(m_closeAuthButton);
        
        m_authDialog.Show();
        return true;
    }

    bool Create2FADialog() {
        if(!m_2faDialog.Create(0, "2FA Verification", 0, 
           TWOFA_DIALOG_X, TWOFA_DIALOG_Y, 
           TWOFA_DIALOG_X + TWOFA_DIALOG_WIDTH, 
           TWOFA_DIALOG_Y + TWOFA_DIALOG_HEIGHT))
            return false;

        if(!m_2faCodeInput.Create(0, "2FAInput", 0, 
           TWOFA_INPUT_X, TWOFA_INPUT_Y, 
           TWOFA_INPUT_X + TWOFA_INPUT_WIDTH, 
           TWOFA_INPUT_Y + TWOFA_INPUT_HEIGHT) ||
           !m_2faLabel.Create(0, "2FALabel", 0, 
           TWOFA_LABEL_X, TWOFA_LABEL_Y, 
           TWOFA_LABEL_X + TWOFA_LABEL_WIDTH, 
           TWOFA_LABEL_Y + TWOFA_LABEL_HEIGHT) ||
           !m_2faFeedback.Create(0, "2FAFeedback", 0, 
           TWOFA_FEEDBACK_X, TWOFA_FEEDBACK_Y, 
           TWOFA_FEEDBACK_X + TWOFA_FEEDBACK_WIDTH, 
           TWOFA_FEEDBACK_Y + TWOFA_FEEDBACK_HEIGHT) ||
           !m_2faLoginButton.Create(0, "2FALoginButton", 0, 
           TWOFA_VERIFY_BTN_X, TWOFA_VERIFY_BTN_Y, 
           TWOFA_VERIFY_BTN_X + TWOFA_VERIFY_WIDTH, 
           TWOFA_VERIFY_BTN_Y + TWOFA_VERIFY_HEIGHT) ||
           !m_close2faButton.Create(0, "Close2FAButton", 0, 
           TWOFA_CANCEL_BTN_X, TWOFA_CANCEL_BTN_Y, 
           TWOFA_CANCEL_BTN_X + TWOFA_CANCEL_WIDTH, 
           TWOFA_CANCEL_BTN_Y + TWOFA_CANCEL_HEIGHT))
            return false;

        m_2faLabel.Text("Enter verification code:");
        m_2faFeedback.Text("");
        m_2faFeedback.Color(clrRed);
        m_2faLoginButton.Text("Verify");
        m_close2faButton.Text("Cancel");

        m_2faDialog.Add(m_2faCodeInput);
        m_2faDialog.Add(m_2faLabel);
        m_2faDialog.Add(m_2faFeedback);
        m_2faDialog.Add(m_2faLoginButton);
        m_2faDialog.Add(m_close2faButton);
        
        return true;
    }

    void HandleLoginAttempt() {
        if(m_passwordInput.Text() == m_password) {
            m_isAuthenticated = true;
            m_authDialog.Hide();
            m_2faDialog.Hide();  // Ensure both dialogs are hidden
        } else {
            if(++m_failedAttempts >= 3) {
                Generate2FACode();
                m_authDialog.Hide();
                m_2faDialog.Show();
            } else {
                m_feedbackLabel.Text(StringFormat("Invalid password (%d attempts left)", 
                                                 3 - m_failedAttempts));
            }
        }
    }

    void Handle2FAAttempt() {
        if(m_2faCodeInput.Text() == m_active2faCode) {
            m_isAuthenticated = true;
            m_2faDialog.Hide();
            m_authDialog.Hide();  // Hide both dialogs on success
        } else {
            m_2faFeedback.Text("Invalid code - please try again");
            m_2faCodeInput.Text("");
        }
    }

    void Generate2FACode() {
        m_active2faCode = StringFormat("%06d", MathRand() % 1000000);
        SendMessageToTelegram("Your verification code: " + m_active2faCode, 
                             m_2faChatId, m_2faBotToken);
    }
};
//+------------------------------------------------------------------+

提供されたモジュールのCAuthenticationManagerクラスは、MQL5ベースのアプリケーション向けに多段階のユーザー認証プロセスを管理します。このクラスは、パスワード認証と二要素認証(2FA)をダイアログ形式で実装しています。認証の流れは、まずユーザーがダイアログにパスワードを入力するところから始まります。パスワードが正しければ即座にアクセスが許可されますが、不正な入力が続くと失敗回数がカウントされ、3回目の誤入力後に2FA用の別ダイアログが起動されます。2FAでは6桁の認証コードが生成され、Telegramを通じてユーザーに送信されます。ユーザーはこのコードを入力して認証を完了させる必要があります。

ダイアログ要素の座標はあらかじめ定義されており、画面レイアウトの一貫性を保ちます。また、エラーや認証成功時のフィードバックも組み込まれており、ユーザーにわかりやすい案内を提供します。このクラスはTelegram.mqhライブラリを活用して2FAコードの送信をおこない、パスワードや2FAの設定も柔軟に変更可能な拡張性を備えています。認証が成功すると両方のダイアログが非表示となり、ユーザー体験を損なうことなく強固なセキュリティを実現しています。


新しい取引管理パネル

この段階では、これまでに開発した各モジュールを統合し、より構造化され効率的な管理パネルを構築します。この改良版は、組織化とモジュール化を強化し、ターミナル内の他のアプリケーションでもコンポーネントの共有や再利用が容易になります。

 新しい管理パネルは、グラフィカルインターフェイス用にAdminHomeDialog.mqhを、認証管理用にAuthentication.mqhをインクルードしています。EAは、2FA検証用のTelegramチャットIDおよびボットトークンを入力パラメータとして定義します。初期化(OnInit)時には認証の初期化と非表示パネルの作成(CreateHiddenPanels)を試み、どちらかが失敗した場合は処理を中断します。

OnChartEvent関数はチャートイベントを処理し、認証状況に応じてAdminHomeDialogパネルの表示・非表示を制御します。認証済みであればパネルを表示し、イベントをパネルに転送します。認証されていなければパネルは非表示のままです。初期化解除関数(OnDeinit)ではダイアログの適切な破棄をおこないます。この設計により、管理パネルホームへのアクセスがセキュアに制御され、認証が完了して初めてパネルの機能操作が許可されます。

新しいプログラムの完全なコードは次のとおりです。

//+------------------------------------------------------------------+
//|                                              New Admin Panel.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.00"

// Panel coordinate defines
#define MAIN_DIALOG_X        30
#define MAIN_DIALOG_Y        80
#define MAIN_DIALOG_WIDTH    335
#define MAIN_DIALOG_HEIGHT   350

#include "AdminHomeDialog.mqh"
#include <Authentication.mqh>

// Input parameters for authentication
input string TwoFactorChatID = "YOUR_CHAT_ID";
input string TwoFactorBotToken = "YOUR_BOT_TOKEN";
string AuthPassword = "2024";

CAdminHomeDialog ExtDialog;
CAuthenticationManager authManager(AuthPassword, TwoFactorChatID, TwoFactorBotToken);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{  
    if(!authManager.Initialize() || !CreateHiddenPanels())
    {
        Print("Initialization failed");
        return INIT_FAILED;
    }
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    ExtDialog.Destroy(reason);
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
    authManager.HandleEvent(id, lparam, dparam, sparam);
    
    if(authManager.IsAuthenticated())
    {
        if(!ExtDialog.IsVisible())
        {
            ExtDialog.Show();
            ChartRedraw();
        }
        // Handle dialog events only when authenticated
        ExtDialog.ChartEvent(id, lparam, dparam, sparam);
    }
    else
    {
        if(ExtDialog.IsVisible()) 
        {
            ExtDialog.Hide();
            
        }
    }
}

//+------------------------------------------------------------------+
//| Create hidden panels                                             |
//+------------------------------------------------------------------+
bool CreateHiddenPanels()
{
    bool success = ExtDialog.Create(0, "Admin Home", 0, 
                    MAIN_DIALOG_X, MAIN_DIALOG_Y, 
                    MAIN_DIALOG_X + MAIN_DIALOG_WIDTH, 
                    MAIN_DIALOG_Y + MAIN_DIALOG_HEIGHT);
    if(success) 
    {
        ExtDialog.Hide();
        ChartRedraw();
    }
    return success;
}


テスト

こちらは、各コンポーネントを開発し、それらを論理的に統合した新しい管理パネルのテスト結果です。プログラムは問題なくコンパイルおよびチャート上での実行に成功しました。以下にその様子を示します。

新しい管理パネルのテスト

EURUSDチャートで新しい管理パネルをテストする



結論

管理パネルの構成がいかに合理化されたか、ご理解いただけたことと思います。CAppDialogから継承することで、アプリケーションの応答性が大幅に向上し、チャート上でパネルを自由にドラッグできるようになりました。また、基底クラスから継承した最小化ボタンにより、アプリケーションを最小化してバックグラウンドで動作させながら、チャートの視認性を確保できます。パネルは必要に応じていつでも最大化でき、シームレスな使いやすさを実現しています。

読みやすさ、拡張性、モジュール性に重点を置き、各コンポーネントを個別に開発しました。このアプローチによりコードの再利用性が高まり、関連ファイルをインクルードし必要なメソッドを呼び出すだけで、他のプログラムに簡単に統合可能です。今後は、新しい管理パネルをさらに強力にするため、CommunicationsDialog.mqh、TradeManagementDialog.mqh、AnalyticsDialog.mqhなど、残りのコンポーネントの完成を目指しています。これらも異なるアプリケーション間で再利用できるよう設計されています。

まずはTelegram.mqhをご自身のプログラムに実装し、その簡単な統合方法を体験してみてください。必要なファイルはすべて以下に添付してあるので、ぜひテストし、さらなる開発をお楽しみください。

ファイル 詳細
New Admin Panel .mqh 最新の管理パネルにはモジュール性の概念が組み込まれています。
Telegram.mqh Telegram経由でメッセージや通知を送信します。
Authentication.mqh すべてのセキュリティ宣言と完全なロジックが含まれています。
AdminHomeDialog.mqh 管理者ホームダイアログの作成用。すべての座標宣言が含まれています。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16562

添付されたファイル |
Telegram.mqh (1.62 KB)
Authentication.mqh (8.92 KB)
AdminHomeDialog.mqh (12.02 KB)
プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール
プライスアクションは、ダイバージェンスを特定することで効果的に分析することができます。RSI(相対力指数)などのテクニカル指標は、その確認シグナルとして重要な役割を果たします。本記事では、自動化されたRSIダイバージェンス分析によって、トレンドの継続や反転をどのように識別できるかを解説し、市場心理を読み解く上で理解を深める手助けをします。
MQL5でカスタムキャンバスグラフィックを使用したケルトナーチャネルインジケーターの構築 MQL5でカスタムキャンバスグラフィックを使用したケルトナーチャネルインジケーターの構築
本記事では、MQL5を用いてカスタムキャンバスグラフィック付きのケルトナーチャネルインジケーターを構築します。移動平均の統合、ATRの計算、そして視覚的に強化されたチャート表示について詳しく解説します。また、インジケーターの実用性を評価するためのバックテスト手法についても取り上げ、実際の取引に役立つ洞察を提供します。
MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築 MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築
この記事では、動的なロットスケーリングを採用したMQL5のグリッドトレーディングエキスパートアドバイザー(EA)を構築します。戦略の設計、コードの実装、バックテストのプロセスについて詳しく解説します。最後に、自動売買システムを最適化するための重要な知見とベストプラクティスを共有します。
知っておくべきMQL5ウィザードのテクニック(第54回):SACとテンソルのハイブリッドによる強化学習 知っておくべきMQL5ウィザードのテクニック(第54回):SACとテンソルのハイブリッドによる強化学習
Soft Actor Critic (SAC)は、以前の記事で紹介した強化学習アルゴリズムです。その際には、効率的にネットワークを学習させる手法としてPythonやONNXの活用についても触れました。今回は、このアルゴリズムを改めて取り上げ、Pythonでよく使われるテンソルや計算グラフを活用することを目的としています。