English Deutsch
preview
MQL5で取引管理者パネルを作成する(第9回):コード編成(IV):取引管理パネルクラス

MQL5で取引管理者パネルを作成する(第9回):コード編成(IV):取引管理パネルクラス

MetaTrader 5 |
164 0
Clemence Benjamin
Clemence Benjamin

内容


はじめに

これまでのディスカッションでは、Trading Administrator Panelプロジェクトの円滑かつ拡張性のある開発を実現するための重要な戦略として、コードの構造化(関心の分離の原則に従うこと)を紹介してきました。このアプローチにより、NewAdminPanel内の各サブパネルに個別で集中できるようになり、プログラム全体のモジュール性と構造の明快さが保たれています。各機能を独立して開発することで、それぞれを最適化し、最高の機能性を実現することが可能になっています。

従来の取引パネルは機能が限られていましたが、現在ではこれまでになく強力になりました。同一インターフェイス内で、迅速な取引の実行と組み込み型のリスク管理を両立させる柔軟性を備えています。また、予約注文の設定もパネルから直接おこなえるようになり、取引の流れをスムーズにします。

今回のアップデートでは、以下の2つの大きな課題に対応しています。

  1. 開発・保守が容易な、大規模かつ整理されたプログラムの構築
  2. コミュニケーションインターフェイス、取引管理インターフェイス、そして将来的には分析インターフェイスも統合された、オールインワンEA内での迅速な取引操作の実現

以下では、これらの改善点をどのように実装・活用していくのか、全体像を解説していきます。


議論の概要

本記事の主な目的は、MQL5の実用的な使用方法を様々なプロジェクトに適用することで、その活用を実用的なものにすることです。今回は、取引管理パネルクラスの開発について探っていきます。MQL5においては、クラスヘッダーが類似した変数の宣言を含むことを理解しておくことが重要です。この文脈では、パネルに組み込みたいすべての取引機能は、CTrade、CDialog、CLabel、CEditといった組み込みクラスヘッダーから継承されます。

クラスが完全に開発された後、そのメソッドをメインプログラムであるNewAdminPanel EAに統合します。この記事では、テスト結果の共有やソースファイルの提供もおこない、読者が実装内容を確認したり、アイデアを参考にしたり、コードを試すことで自身のプロジェクトを改善できるようにします。

この段階では、ホームパネルの作成をメインプログラム内に集約することに決めました。コードの長さが大幅に増えることがなかったためです。以前のアプローチにも利点はありましたが、依存関係を減らして開発を簡素化するため、この構造を選びました。私の目標は、各機能ごとに専用クラスを使いながら、メインプログラムの焦点を明確に保つことです。インターフェイスの主要な要素は現在、メインプログラム内で直接作成されており、より洗練された効率的な設計になっています。その結果、NewAdminPanelにおいてAdminHomeDialogクラスのメソッドを呼び出すことはなくなりました。 

以下の画像は、議論の最後までに作成する内容を示しています。しかしこれは始まりに過ぎません。完成後は、さらなる拡張や改善のための堅固な基盤となります。

取引管理パネル(議論の最後まで)

このプロジェクトの利点

ここで開発しているツールには、以下のような重要な利点があります。

  • 迅速な取引:効率的に取引を実行できます。
  • リスク管理:注文を出す前にストップロス(S/L)やテイクプロフィット(T/P)を設定でき、さらに予約注文を活用して事前に取引を計画できます。
  • コミュニケーション機能:以前に開発したコミュニケーションパネルを通じて、他のトレーダーにメッセージを送ることができます。
  • 高度なMQL5の習得:MQL5におけるオブジェクト指向プログラミングについて、より深い知識を得ることができます。
  • コミュニティとの交流:コメント欄で自身の知見や経験を共有できます。
  • 再利用性:CTradeManagementPanelクラスは他のプロジェクトにも活用できます。
次のセクションでは、取引管理パネルクラスの機能を詳細に分解して解説し、その後メインプログラムへの統合について説明していきます。


CTradeManagementPanel取引管理パネルクラス

1. ヘッダーとマクロによる基盤の構築

何かを構築する前に、まずは適切なツールを導入する必要があります。最初のステップは、取引の実行、ユーザーインターフェイス要素、およびイベント管理を扱うヘッダーファイルをインクルードすることです。また、ボタンのサイズや余白などのレイアウト値をコード内に散らばらせる代わりに、冒頭でマクロとして定義します。これにより、UIデザインに一貫性を持たせることができ、将来的に寸法を変更する際も、複数のファイルを探し回る必要がなくなります。

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

//+------------------------------------------------------------------+
//| Defines for dimensions and spacing                               |
//+------------------------------------------------------------------+
#define BUTTON_WIDTH          100
#define BUTTON_HEIGHT         30
#define DELETE_BUTTON_WIDTH   130   
#define EDIT_WIDTH            80
#define EDIT_HEIGHT           20
#define DEFAULT_LABEL_HEIGHT  20    
#define SECTION_LABEL_WIDTH   250
#define GAP                   10
#define INPUT_LABEL_GAP       3     

2. コアクラスとそのコンポーネントの定義

取引パネルの中核をなすのは、インターフェイスと取引実行のロジックの両方を管理するクラスです。このクラスは、ベースとなるダイアログクラスから継承することで、UI管理の基本機能を自動的に備えつつ、私たちは主に動作のカスタマイズに集中できるようになります。

このクラスの内部では、いくつかの重要なメンバー変数を定義します。これには、取引チャートへの参照、取引実行用のオブジェクト、およびUIコントロールを体系的にまとめた構造体などが含まれます。こうした要素を分けて整理することで、UIの見た目や操作性と注文処理ロジックが分離され、コードが明瞭かつ保守しやすくなります。

//+------------------------------------------------------------------+
//| Trade Management Panel class                                     |
//+------------------------------------------------------------------+
class CTradeManagementPanel : public CAppDialog
{
protected:
   // Store chart and subwindow values for helper functions.
   long m_chart;
   int  m_subwin;
   
   CTrade m_trade;  // Trade object for executing trades

   // Section Labels
   CLabel m_secQuickLabel;
   CLabel m_secPendingLabel;
   CLabel m_secAllOpsLabel;
   
   // Section 1: Quick Execution
   CButton m_buyButton;      // Market Buy button
   CButton m_sellButton;     // Market Sell button
   CEdit   m_volumeEdit;     // Volume input
   CLabel  m_lotLabel;       // "Lot:" label above volume input
   // TP and SL for market orders—with a label to their right
   CEdit   m_tpEdit;
   CLabel  m_tpRightLabel;   // "TP:" label
   CLabel  m_tpErrorLabel;
   CEdit   m_slEdit;
   CLabel  m_slRightLabel;   // "SL:" label
   CLabel  m_slErrorLabel;
   
   // Section 2: Pending Orders
   // Column Headers for pending orders
   CLabel  m_pendingPriceHeader;
   CLabel  m_pendingTPHeader;
   CLabel  m_pendingSLHeader;
   CLabel  m_pendingExpHeader;
   
   // Buy pending orders
   CButton m_buyLimitButton;
   CEdit   m_buyLimitPriceEdit;
   CEdit   m_buyLimitTPEdit;
   CEdit   m_buyLimitSLEdit;
   CEdit   m_buyLimitExpEdit;   // Expiration input for Buy Limit
   CButton m_buyStopButton;
   CEdit   m_buyStopPriceEdit;
   CEdit   m_buyStopTPEdit;
   CEdit   m_buyStopSLEdit;
   CEdit   m_buyStopExpEdit;    // Expiration input for Buy Stop
   
   // Sell pending orders
   CButton m_sellLimitButton;
   CEdit   m_sellLimitPriceEdit;
   CEdit   m_sellLimitTPEdit;
   CEdit   m_sellLimitSLEdit;
   CEdit   m_sellLimitExpEdit;  // Expiration input for Sell Limit
   CButton m_sellStopButton;
   CEdit   m_sellStopPriceEdit;
   CEdit   m_sellStopTPEdit;
   CEdit   m_sellStopSLEdit;
   CEdit   m_sellStopExpEdit;   // Expiration input for Sell Stop
   
   // Section 3: All Order Operations
   CButton m_closeAllButton;
   CButton m_closeProfitButton;
   CButton m_closeLossButton;
   CButton m_closeBuyButton;
   CButton m_closeSellButton;
   CButton m_deleteAllOrdersButton;
   CButton m_deleteLimitOrdersButton;
   CButton m_deleteStopOrdersButton;
   CButton m_deleteStopLimitOrdersButton;
   CButton m_resetButton;        
   
   //--- Helper: Create a text label using
   bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr)
   {
      string unique_name = m_name + label_name;
      if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height))
         return false;
      if(!Add(label))
         return false;
      if(!label.Text(text))
         return false;
      label.Color(clr);
      return true;
   }
   
   //--- Helper: Create and add a button control
   bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite)
   {
      if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      button.Text(name);
      button.Color(clr);
      if(!Add(button))
         return false;
      return true;
   }
   
   //--- Helper: Create and add an edit control
   bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT)
   {
      if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      if(!Add(edit))
         return false;
      return true;
   }
   
   // Event handler declarations
   virtual void OnClickBuy();
   virtual void OnClickSell();
   virtual void OnClickBuyLimit();
   virtual void OnClickBuyStop();
   virtual void OnClickSellLimit();
   virtual void OnClickSellStop();
   virtual void OnClickCloseAll();
   virtual void OnClickCloseProfit();
   virtual void OnClickCloseLoss();
   virtual void OnClickCloseBuy();
   virtual void OnClickCloseSell();
   virtual void OnClickDeleteAllOrders();
   virtual void OnClickDeleteLimitOrders();
   virtual void OnClickDeleteStopOrders();
   virtual void OnClickDeleteStopLimitOrders();
   virtual void OnClickReset();  
   
   // Validation helpers for market orders
   bool ValidateBuyParameters(double volume, double tp, double sl, double ask);
   bool ValidateSellParameters(double volume, double tp, double sl, double bid);
   
public:
   CTradeManagementPanel();
   ~CTradeManagementPanel();
   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);
   void Toggle();
};

3. 論理的なユーザーインターフェイスの構成

整理されたインターフェイスは、ユーザー体験を大きく向上させます。ボタンや入力フィールドを無秩序に配置するのではなく、パネル全体を3つの主要セクションに分けて構成します。

  1. 第1セクション:成行注文の即時実行用。ユーザーが即座に買いまたは売りの注文を出せるようにします。また、取引のボリュームや、ストップロス(SL)、テイクプロフィット(TP)を設定できるようにします。
  2. 第2セクション:予約注文用。ユーザーが価格、期限、注文タイプ(例:Buy LimitやSell Stop)を指定できるようにします。
  3. 第3セクション:一括操作用。すべてのポジションを一括でクローズしたり、利益が出ているポジションのみをクローズしたり、未決注文を削除したりする操作が可能です。これらの機能を目的別にまとめることで、インターフェイスの操作性が保たれ、使いやすさが向上します。

//+------------------------------------------------------------------+
//| Create the trade management panel                                |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::Create(const long chart, const string name, const int subwin,
                                     const int x1, const int y1, const int x2, const int y2)
{
   // Save chart and subwin for later use.
   m_chart  = chart;
   m_subwin = subwin;
   
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return false;
   
   int curX = GAP;
   int curY = GAP;
   
   // Section 1: Quick Order Execution
   if(!CreateLabelEx(m_secQuickLabel, curX, curY-10, DEFAULT_LABEL_HEIGHT, "SecQuick", "Quick Order Execution:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Market order row:
   if(!CreateButton(m_buyButton, "Buy", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen))
      return false;
   int volX = curX + BUTTON_WIDTH + GAP;
   if(!CreateLabelEx(m_lotLabel, volX, curY - DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "Lot", "Lot Size:", clrBlack))
      return false;
   if(!CreateEdit(m_volumeEdit, "VolumeEdit", volX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   m_volumeEdit.Text(DoubleToString(minVolume, 2));
   int sellBtnX = volX + EDIT_WIDTH + GAP;
   if(!CreateButton(m_sellButton, "Sell", sellBtnX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   curY += BUTTON_HEIGHT + GAP;
   
   // Next row: TP and SL for market orders
   if(!CreateEdit(m_tpEdit, "TPEdit", 4*curX+ GAP , curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_tpEdit.Text("0.00000");
   if(!CreateLabelEx(m_tpRightLabel, curX  + INPUT_LABEL_GAP, curY, DEFAULT_LABEL_HEIGHT, "TP", "TP:", clrBlack))
      return false;
   if(!CreateLabelEx(m_tpErrorLabel, curX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "TPError", "", clrBlack))
      return false;
   int slX = 2*EDIT_WIDTH  ; 
   if(!CreateEdit(m_slEdit, "SLEdit", slX + 4*curX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_slEdit.Text("0.00000");
   if(!CreateLabelEx(m_slRightLabel, slX  , curY, DEFAULT_LABEL_HEIGHT, "SL", "SL:", clrBlack))
      return false;
   if(!CreateLabelEx(m_slErrorLabel, slX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "SLError", "", clrBlack))
      return false;
   curY += EDIT_HEIGHT + GAP*2;
   
   // Section 2: Pending Orders
   if(!CreateLabelEx(m_secPendingLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecPend", "Pending Orders:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Column headers for pending orders (each label includes a colon)
   int headerX = curX + BUTTON_WIDTH + GAP;
   if(!CreateLabelEx(m_pendingPriceHeader, headerX, curY, DEFAULT_LABEL_HEIGHT, "PendPrice", "Price:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingTPHeader, headerX + EDIT_WIDTH + GAP, curY, DEFAULT_LABEL_HEIGHT, "PendTP", "TP:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingSLHeader, headerX + 2*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendSL", "SL:", clrBlack))
      return false;
   if(!CreateLabelEx(m_pendingExpHeader, headerX + 3*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendExp", "Expiration Date:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;
   
   // Prepare default expiration value as current day end time.
   datetime now = TimeCurrent();
   string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59";
   
   // --- Buy Pending Orders ---
   // Buy Limit Order row:
   if(!CreateButton(m_buyLimitButton, "Buy Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue))
      return false;
   int pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitPriceEdit, "BuyLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   string askStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_ASK), 5);
   m_buyLimitPriceEdit.Text(askStr);
   int pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitTPEdit, "BuyLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitTPEdit.Text("0.00000");
   int pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyLimitSLEdit, "BuyLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitSLEdit.Text("0.00000");
   int pending4X = pending3X + EDIT_WIDTH + GAP; // Double width for expiration input
   if(!CreateEdit(m_buyLimitExpEdit, "BuyLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyLimitExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Buy Stop Order row:
   if(!CreateButton(m_buyStopButton, "Buy Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_buyStopPriceEdit, "BuyStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopPriceEdit.Text(askStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopTPEdit, "BuyStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopSLEdit, "BuyStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_buyStopExpEdit, "BuyStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_buyStopExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // --- Sell Pending Orders ---
   // Sell Limit Order row:
   if(!CreateButton(m_sellLimitButton, "Sell Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitPriceEdit, "SellLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   string bidStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_BID), 5);
   m_sellLimitPriceEdit.Text(bidStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitTPEdit, "SellLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitSLEdit, "SellLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellLimitExpEdit, "SellLimitExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellLimitExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Sell Stop Order row:
   if(!CreateButton(m_sellStopButton, "Sell Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed))
      return false;
   pendingX = curX + BUTTON_WIDTH + GAP;
   if(!CreateEdit(m_sellStopPriceEdit, "SellStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopPriceEdit.Text(bidStr);
   pending2X = pendingX + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopTPEdit, "SellStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopTPEdit.Text("0.00000");
   pending3X = pending2X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopSLEdit, "SellStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopSLEdit.Text("0.00000");
   pending4X = pending3X + EDIT_WIDTH + GAP;
   if(!CreateEdit(m_sellStopExpEdit, "SellStopExp", pending4X, curY, 2*EDIT_WIDTH, EDIT_HEIGHT))
      return false;
   m_sellStopExpEdit.Text(exp_default);
   curY += BUTTON_HEIGHT + GAP;
   
   // Section 3: All Order Operations
   if(!CreateLabelEx(m_secAllOpsLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecAllOps", "All Order Operations:", clrBlack))
      return false;
   curY += DEFAULT_LABEL_HEIGHT + GAP;   
   
   int deleteX = curX + 2*BUTTON_WIDTH + 2*GAP; 
   int deleteY = curY  ;      
   CreateButton(m_closeAllButton, "Close All", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_closeProfitButton, "Close Profit", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen);
   CreateButton(m_deleteLimitOrdersButton, "Delete Limits", deleteX, curY, DELETE_BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   curY += BUTTON_HEIGHT + GAP;
   
   CreateButton(m_closeLossButton, "Close Loss", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_closeBuyButton, "Close Buy", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen);
   CreateButton(m_deleteStopOrdersButton, "Delete Stops", deleteX, curY  , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrRed);
   CreateButton(m_resetButton, "Reset", deleteX+ 2*BUTTON_WIDTH-3*curX, curY, DELETE_BUTTON_WIDTH, 2*BUTTON_HEIGHT, clrRed);
   curY += BUTTON_HEIGHT + GAP;
   
   CreateButton(m_closeSellButton, "Close Sell", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue);
   CreateButton(m_deleteAllOrdersButton, "Delete All", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed);
   CreateButton(m_deleteStopLimitOrdersButton, "Delete StopLimits", deleteX, curY , DELETE_BUTTON_WIDTH+10, BUTTON_HEIGHT, clrDarkRed);
   curY += BUTTON_HEIGHT + GAP;
  
   return true;
}

4. ヘルパー関数によるUIコントロール作成の自動化

ボタンや入力フィールドを一つひとつ手動で配置していたら、コードは冗長で管理しづらいものになります。そこで、コントロールの作成・配置・スタイリングを自動化するヘルパー関数を作成します。

このロジックを中央にまとめることで、UIコードが整理され、保守性が大幅に向上します。たとえば、ボタンやラベルの外観を変更したい場合でも、コード全体を見直す必要はなく、ヘルパー関数の1か所を修正するだけで済みます。

 //--- Helper: Create a text label using
   bool CreateLabelEx(CLabel &label, int x, int y, int height, string label_name, string text, color clr)
   {
      string unique_name = m_name + label_name;
      if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height))
         return false;
      if(!Add(label))
         return false;
      if(!label.Text(text))
         return false;
      label.Color(clr);
      return true;
   }
   
   //--- Helper: Create and add a button control
   bool CreateButton(CButton &button, string name, int x, int y, int w = BUTTON_WIDTH, int h = BUTTON_HEIGHT, color clr = clrWhite)
   {
      if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      button.Text(name);
      button.Color(clr);
      if(!Add(button))
         return false;
      return true;
   }
   
   //--- Helper: Create and add an edit control
   bool CreateEdit(CEdit &edit, string name, int x, int y, int w = EDIT_WIDTH, int h = EDIT_HEIGHT)
   {
      if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h))
         return false;
      if(!Add(edit))
         return false;
      return true;
   }

5. 中央イベントシステムによるユーザー操作の処理

UIが完成したら、次はユーザーの操作に対応する仕組みが必要になります。すべてのボタンに個別の関数を割り当てるのではなく、中央のイベントディスパッチャーを使います。

このディスパッチャーはユーザーの操作を監視し、どのコントロールがイベントを発生させたかを判断して、適切な関数を呼び出します。このアプローチにより、イベント処理は整理され、スッキリとした構造になります。たとえば、取引実行ボタンをクリックすれば、入力されたデータが注文実行ロジックへ送られ、リセットボタンをクリックすれば、入力フィールドがクリアされるといった動作を実現できます。

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == m_buyButton.Name())            OnClickBuy();
      else if(sparam == m_sellButton.Name())        OnClickSell();
      else if(sparam == m_buyLimitButton.Name())    OnClickBuyLimit();
      else if(sparam == m_buyStopButton.Name())     OnClickBuyStop();
      else if(sparam == m_sellLimitButton.Name())   OnClickSellLimit();
      else if(sparam == m_sellStopButton.Name())    OnClickSellStop();
      else if(sparam == m_closeAllButton.Name())    OnClickCloseAll();
      else if(sparam == m_closeProfitButton.Name()) OnClickCloseProfit();
      else if(sparam == m_closeLossButton.Name())   OnClickCloseLoss();
      else if(sparam == m_closeBuyButton.Name())    OnClickCloseBuy();
      else if(sparam == m_closeSellButton.Name())   OnClickCloseSell();
      else if(sparam == m_deleteAllOrdersButton.Name())  OnClickDeleteAllOrders();
      else if(sparam == m_deleteLimitOrdersButton.Name()) OnClickDeleteLimitOrders();
      else if(sparam == m_deleteStopOrdersButton.Name())   OnClickDeleteStopOrders();
      else if(sparam == m_deleteStopLimitOrdersButton.Name()) OnClickDeleteStopLimitOrders();
      else if(sparam == m_resetButton.Name())       OnClickReset(); // Handle reset button click
   }
   return true;
}

6. 市場注文の検証と実行

取引を実行する前に、システムは入力内容を検証してエラーを防ぎます。たとえば、取引量が許容範囲内かどうか、ストップロス(SL)やテイクプロフィット(TP)が現在の市場価格に対して適切に設定されているかをチェックします。

入力が有効な場合、取引実行オブジェクトが注文を処理します。そうでない場合は、システムはユーザーに警告を出し、誤った注文が発注されないようにします。この検証手順は、高額なミスを防ぎ、パネルの信頼性を保つ上で極めて重要です。

//+------------------------------------------------------------------+
//| Validate Buy order parameters (Market orders)                    |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::ValidateBuyParameters(double volume, double tp, double sl, double ask)
{
   bool valid = true;
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
   if(volume <= 0)
   {
      m_tpErrorLabel.Text("Invalid volume");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(tp != 0 && tp <= ask)
   {
      m_tpErrorLabel.Text("Invalid TP");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(sl != 0 && sl >= ask)
   {
      m_slErrorLabel.Text("Invalid SL");
      m_slErrorLabel.Color(clrRed);
      valid = false;
   }
   return valid;
}

//+------------------------------------------------------------------+
//| Validate Sell order parameters (Market orders)                   |
//+------------------------------------------------------------------+
bool CTradeManagementPanel::ValidateSellParameters(double volume, double tp, double sl, double bid)
{
   bool valid = true;
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
   if(volume <= 0)
   {
      m_tpErrorLabel.Text("Invalid volume");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(tp != 0 && tp >= bid)
   {
      m_tpErrorLabel.Text("Invalid TP");
      m_tpErrorLabel.Color(clrRed);
      valid = false;
   }
   if(sl != 0 && sl <= bid)
   {
      m_slErrorLabel.Text("Invalid SL");
      m_slErrorLabel.Color(clrRed);
      valid = false;
   }
   return valid;
}

7. 追加パラメータによる予約注文の管理

予約注文には、成行注文よりも多くの設定が必要です。ユーザーはエントリープライス(指値)と有効期限を指定する必要があるため、入力検証関数もそれに対応して拡張します。

ヘルパー関数は、固定時間での期限指定か、「キャンセルされるまで有効(Good Till Canceled)」の設定かを判断して処理します。この構造によって、予約注文が適切なルールに従って送信されることを保証します。

//+------------------------------------------------------------------+
//| Helper: Parse expiration input                                   |
//+------------------------------------------------------------------+
void ParseExpiration(string sExp, ENUM_ORDER_TYPE_TIME &type_time, datetime &expiration)
{
   if(StringCompare(StringToUpper(sExp), "GTC") == 0)
   {
      type_time = ORDER_TIME_GTC;
      expiration = 0;
   }
   else
   {
      type_time = ORDER_TIME_SPECIFIED;
      expiration = StringToTime(sExp);
   }
}

//+------------------------------------------------------------------+
//| Button click handlers - Pending Orders                           |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickBuyLimit()
{
   double price  = StringToDouble(m_buyLimitPriceEdit.Text());
   double tp     = StringToDouble(m_buyLimitTPEdit.Text());
   double sl     = StringToDouble(m_buyLimitSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_buyLimitExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.BuyLimit(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickBuyStop()
{
   double price  = StringToDouble(m_buyStopPriceEdit.Text());
   double tp     = StringToDouble(m_buyStopTPEdit.Text());
   double sl     = StringToDouble(m_buyStopSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_buyStopExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.BuyStop(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickSellLimit()
{
   double price  = StringToDouble(m_sellLimitPriceEdit.Text());
   double tp     = StringToDouble(m_sellLimitTPEdit.Text());
   double sl     = StringToDouble(m_sellLimitSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_sellLimitExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.SellLimit(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

void CTradeManagementPanel::OnClickSellStop()
{
   double price  = StringToDouble(m_sellStopPriceEdit.Text());
   double tp     = StringToDouble(m_sellStopTPEdit.Text());
   double sl     = StringToDouble(m_sellStopSLEdit.Text());
   double volume = StringToDouble(m_volumeEdit.Text());
   string sExp   = m_sellStopExpEdit.Text();
   ENUM_ORDER_TYPE_TIME type_time;
   datetime expiration;
   ParseExpiration(sExp, type_time, expiration);
   m_trade.SellStop(volume, price, Symbol(), sl, tp, type_time, expiration, "");
}

8. 一括注文処理の実装による取引管理の効率化

複数の取引を手動で管理するのは面倒な作業になりがちです。そこで本パネルでは、複数の注文に対して一括操作を実行できる機能を追加しています。

ユーザーは、すべての取引をクローズしたり、利益が出ているポジションだけ、あるいは損失が出ているポジションだけをクローズすることができます。また、未決注文をすべて削除することも可能です。これらの操作に対応するイベントハンドラは、すべてのポジションをループ処理し、要求された操作を効率的に適用します。この機能により、複数の注文を素早く簡単に管理することができるようになります。

//+------------------------------------------------------------------+
//| Button click handlers - All Order Operations                     |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickCloseAll()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseProfit()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetDouble(POSITION_PROFIT) > 0)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseLoss()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetDouble(POSITION_PROFIT) < 0)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseBuy()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickCloseSell()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
         m_trade.PositionClose(PositionGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteAllOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteLimitOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT)
         m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteStopOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
         m_trade.OrderDelete(OrderGetTicket(i));
}

void CTradeManagementPanel::OnClickDeleteStopLimitOrders()
{
   for(int i = OrdersTotal()-1; i >= 0; i--)
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP_LIMIT ||
         OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP_LIMIT)
         m_trade.OrderDelete(OrderGetTicket(i));
}

9. 使いやすさ向上のためのリセットボタンと表示切替機能の追加

ユーザー体験を向上させるために、小さいながら便利な2つの機能を追加しました。1つ目は、リセットボタンです。これを押すと、すべての入力フィールドがクリアされ、パネルが初期状態に戻ります。ユーザーが設定を変更した後にやり直したい時に役立ちます。

2つ目は、表示・非表示の切り替え機能です。パネルの表示をオン・オフできるため、作業スペースをスッキリ保ちながら、必要な時にすぐアクセスできるようにします。

//+------------------------------------------------------------------+
//| Reset all input fields to default values                         |
//+------------------------------------------------------------------+
void CTradeManagementPanel::OnClickReset()
{
   // Reset volume to minimum allowed
   double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   m_volumeEdit.Text(DoubleToString(minVolume, 2));

   // Reset TP and SL for market orders
   m_tpEdit.Text("0.00000");
   m_slEdit.Text("0.00000");

   // Reset pending order prices to current ASK/BID
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   m_buyLimitPriceEdit.Text(DoubleToString(ask, 5));
   m_buyStopPriceEdit.Text(DoubleToString(ask, 5));
   m_sellLimitPriceEdit.Text(DoubleToString(bid, 5));
   m_sellStopPriceEdit.Text(DoubleToString(bid, 5));

   // Reset pending order TP/SL to 0
   m_buyLimitTPEdit.Text("0.00000");
   m_buyLimitSLEdit.Text("0.00000");
   m_buyStopTPEdit.Text("0.00000");
   m_buyStopSLEdit.Text("0.00000");
   m_sellLimitTPEdit.Text("0.00000");
   m_sellLimitSLEdit.Text("0.00000");
   m_sellStopTPEdit.Text("0.00000");
   m_sellStopSLEdit.Text("0.00000");

   // Reset expiration dates to current day's end
   datetime now = TimeCurrent();
   string exp_default = TimeToString(now, TIME_DATE) + " 23:59:59";
   m_buyLimitExpEdit.Text(exp_default);
   m_buyStopExpEdit.Text(exp_default);
   m_sellLimitExpEdit.Text(exp_default);
   m_sellStopExpEdit.Text(exp_default);

   // Clear error messages
   m_tpErrorLabel.Text("");
   m_slErrorLabel.Text("");
}

この記事の最後に、完全なソースコードを添付していますので、ダウンロードしてご利用ください。次のセクションでは、取引管理パネルクラスをメインプログラムに統合する手順を、ステップごとに説明します。


New_Admin_Panelとの統合

1. New_Admin_Panelシステムに取引管理ヘッダーを追加する

まず、ヘッダーファイルをインクルードしてTradeManagementPanelを導入します。これにより、取引実行と管理に必要なすべての機能が管理パネル内で使えるようになります。シンプルですが、不可欠なステップです。

#include <TradeManagementPanel.mqh>

2. 取引パネルインスタンスを準備する

EA全体で取引パネルにアクセスできるように、グローバルポインタとして定義します。こうすることで、既に作成済みかどうかを確認し、必要に応じて操作できます。

CTradeManagementPanel  *g_tradePanel = NULL;

3. 取引管理ボタンを追加する

ユーザーがトレードパネルを開けるように、管理ホームパネル内に専用のボタンを設置しました。レイアウトやスタイルの一貫性を保つため、ヘルパー関数を使っています。このボタンはインターフェイス上に配置され、クリックすると取引パネルが起動されます。

CButton g_tradeMgmtButton;

4. 取引パネルをクリック可能にする

ここが面白い部分です。取引パネルのライフサイクルを管理する関数を作成しました。

  1. 取引パネルが存在しない場合は作成します。
  2. すでに存在する場合は、表示・非表示を切り替えます。

void HandleTradeManagement()
{
   if(g_tradePanel == NULL)
   {
      g_tradePanel = new CTradeManagementPanel();
      if(!g_tradePanel.Create(g_chart_id, "TradeManagementPanel", g_subwin, 310, 20, 310+565, 20+510))
      {
         delete g_tradePanel;
         g_tradePanel = NULL;
         Print("Failed to create Trade Management Panel");
         return;
      }
   }
   g_tradePanel.Toggle();
   ChartRedraw();
}

この設定により、ユーザーはボタンをクリックするだけで即座に取引管理機能にアクセスできるようになります。

5. 取引管理機能をイベントに連携させる

次に、取引パネルがユーザーの操作に応答できるようにします。イベントハンドラ内で、パネルが開いている場合は操作をパネルに渡します。これにより、注文の実行やストップロスの調整、取引のクローズなどの処理がリアルタイムに反映されるようになります。

if(g_tradePanel != NULL && g_tradePanel.IsVisible())
   return g_tradePanel.OnEvent(id, lparam, dparam, sparam);

6. EA終了時のクリーンアップ

EAが停止または削除された際には、メモリの解放とリーク防止が必要です。そのため、取引パネルを適切に破棄します。 

if(g_tradePanel != NULL)
{
   g_tradePanel.Destroy(reason);
   delete g_tradePanel;
   g_tradePanel = NULL;
}

残ったインスタンスもなく、無駄なリソースもありません。まさにクリーンなシャットダウンです。 New_Admin_Panelの完全なソースファイルは下に添付していますので、ダウンロードしてご利用ください。この手順を少し修正すれば、ご自身のプロジェクトにも取引管理パネルを統合できます。次のセクションでは、テスト結果を共有します。


テスト

コードのコンパイルに成功した後、MetaTrader 5プラットフォームで起動しました。以下の図は、管理ホームパネルから取引管理パネルが正常に展開された様子を示しています。ホームパネルでは、取引管理パネルの表示・非表示を切り替えられるため、必要に応じてチャートを全画面で表示する柔軟性があります。取引管理パネルがアクティブな状態では、チャート上で直接パネルの操作ができ、価格の動きを観察しながら取引をおこなえるため、スキャルピングトレードにとって大きな利点となります。下の画像をご覧ください。

取引管理パネルの使用


結論

取引管理パネルクラスの開発は、コードのモジュール化を進め、大規模なプログラムコンポーネントを異なるプロジェクト間で再利用可能にするという大きなマイルストーンとなりました。先に述べた通り、このアプローチには多くの利点があります。今回、開発プロセスを段階的に解説し、具体的な方法を示しましたので、参考になるポイントがきっとあるはずです。

私はこの分野で完璧だとは言えません。常に学び、改善を続けています。経験豊富な方から見れば誤りや改善点があるかもしれませんが、皆さんのためにも建設的なフィードバックをコメント欄でお寄せいただけると嬉しいです。このパネルを有用と感じてくださる方にとって、多くのメリットがあることを心から感謝しています。

また、今回の開発において有益な洞察を与えてくれたOmega J Msigwa氏のInformative Dashboardソースコードの貢献にも感謝いたします。添付したコードは自由に拡張・改造して、MQL5のスキルを磨き、さらに強力なツールを作成してください。

ファイル 説明
TradeManagementPanel.mqh New_Admin_Panel EA内で取引を効率的に実行、管理、変更するためのグラフィカルインターフェイスを提供するクラスヘッダーのソースコード。他のEAと統合することも可能です。
New_Admin_Panel.mq5 取引プラットフォーム内で実行するようにコンパイルされると、取引実行、通信、分析、およびその他の管理機能を管理するための集中型インターフェイスとして機能します。

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

添付されたファイル |
New_Admin_Panel.mq5 (17.12 KB)
ログレコードをマスターする(第6回):ログをデータベースに保存する ログレコードをマスターする(第6回):ログをデータベースに保存する
この記事では、ログを構造化され、スケーラブルな方法で保存するためにデータベースを使用する手法を取り上げます。基本的な概念、主要な操作、MQL5におけるデータベースハンドラの設定と実装を順を追って解説し、最後にその結果を検証し、このアプローチが最適化と効率的なモニタリングにどのように役立つかを明らかにします。
MQL5における高度なメモリ管理と最適化テクニック MQL5における高度なメモリ管理と最適化テクニック
MQL5の取引システムにおけるメモリ使用を最適化するための実践的なテクニックを紹介します。効率的で安定性が高く、高速に動作するエキスパートアドバイザー(EA)やインジケーターの構築方法を学びましょう。MQL5でのメモリの仕組み、システムを遅くしたり不安定にしたりする一般的な落とし穴、そして、最も重要なこととして、それらを解決する方法について詳しく解説します。
ペア取引における平均回帰による統計的裁定取引:数学で市場を攻略する ペア取引における平均回帰による統計的裁定取引:数学で市場を攻略する
本記事では、ポートフォリオレベルの統計的アービトラージの基本的な概念を紹介します。数学の深い知識がない読者にも理解しやすく説明し、実際の運用を始めるためのコンセプトフレームワークを提案することを目的としています。記事には、動作するエキスパートアドバイザー(EA)と、1年間のバックテストに関する注記、再現用の設定ファイル(.iniファイル)も含まれています。
知っておくべきMQL5ウィザードのテクニック(第59回):移動平均とストキャスティクスのパターンを用いた強化学習(DDPG) 知っておくべきMQL5ウィザードのテクニック(第59回):移動平均とストキャスティクスのパターンを用いた強化学習(DDPG)
MAとストキャスティクスを使用したDDPGに関する前回の記事に引き続き、今回は、DDPGの実装に欠かせない他の重要な強化学習クラスを検証していきます。主にPythonでコーディングしていますが、最終的には訓練済みネットワークをONNX形式でエクスポートし、MQL5に組み込んでウィザードで構築したエキスパートアドバイザー(EA)のリソースとして統合します。