English Deutsch 日本語
preview
在MQL5中创建交易管理面板(第九部分):代码组织(5):分析面板(AnalyticsPanel)类

在MQL5中创建交易管理面板(第九部分):代码组织(5):分析面板(AnalyticsPanel)类

MetaTrader 5示例 |
241 1
Clemence Benjamin
Clemence Benjamin

内容


概述

之前的章节中,我介绍了“分析面板”的概念,在当时的阶段,该面板主要由静态界面元素组成,例如显示盈亏比的饼图。然而,这些组件缺乏实时数据更新功能,这就限制了它们在动态交易环境中的实用性。在本次讨论中,我们通过提升界面设计与功能,向前迈出了重要一步,重点聚焦于如何获取并展示实时交易数据。

我们的开发方法借助了MQL5标准库,特别是位于\Include\Controls\目录中的类。我们将扩展并定制现有类,如CLabel、CEdit和CDialog,以创建响应迅速、数据驱动的用户界面组件。这些组件构成了我们不断发展的交易管理面板的支柱,该面板设计为模块化、多界面工具,提供全面的账户和策略管理功能,以及通过Telegram进行远程控制和实时通知的通信接口。

尽管核心目标仍是开发这一高级面板,但此处分享的技术——特别是涉及实时数据采集和动态界面更新的技术——可应用于各种MQL5项目,不仅限于管理界面,还包括EA、自定义指标和交互式学习工具。


AnalyticsPanel类概述

作为适用于大型MQL5程序的模块化开发方法的一部分——同时为了提升代码的可重用性和可维护性——我们要创建一个专用的AnalyticsPanel类头文件。该类旨在封装分析面板的视觉布局以及市场数据的实时获取与展示功能。

除了提供标准的账户指标外,该面板还将显示各种技术指标值,这些指标值将输入到被称之为“汇合策略”(Confluence Strategy)的自定义策略中。该策略基于汇合原则,即比较多个指标的信号以生成统一的交易信号。如果各指标之间没有达成一致,面板将仅显示“无共识”信息,从而避免虚假或微弱信号。

AnalyticsPanel类将包含用于初始化并刷新面板布局、实时更新标签值以及根据策略逻辑管理视觉信号反馈的方法。以下包含我提供的面板视觉设计布局图,在接下来的讨论中,我们将详细介绍实现该布局的具体细节。

AnalyticsPanel设计

AnalyticsPanel特性

在我们持续的开发进程中,完成类开发后,我们会将其集成到主EA程序——New_Admin_Panel中。这种集成强化了我们系统的模块化架构,凸显了构建可重用且独立的组件的优势,这些组件能够在更大的应用程序框架内无缝交互。以下是一张展示我们产品最终性能效果的图片,其中,只有当所有指标值达成一致,形成买入或卖出决策的强烈共识时,才会生成交易信号。

AnalyticsPanel实时状态

AnalyticsPanel实时状态

将AnalyticsPanel集成到New_Admin_Panel中,我们可以获得多项实际的益处:

  • 集中监控:如今,诸如盈亏比、资金变动及交易摘要等实时分析数据,均可从主界面直接获取,无需借助其他独立工具。
  • 提升用户体验:将分析功能与策略执行、Telegram消息等核心功能相结合,为用户提供更加统一且直观的工作流程。
  • 代码可扩展性与维护性:将界面组件拆分为AnalyticsPanel等类,可提高代码可读性,便于测试,并支持未来升级时最小化干扰。
  • 跨项目重用性:模块化设计使得AnalyticsPanel(及其他面板)能够在其他EA或交易工具中复用或适配。
  • 交易信号:我们当前的这个版本能够给出有用的汇合信号。



在MQL5中的实现

在设计分析面板时,我们遵循面向对象的原则,将关键组件划分到不同访问级别,以提升清晰度、安全性和可维护性。该类结构使得程序的其他部分可访问某些功能——这些是“public"(公优)元素,作为与面板交互的接口。而处理内部运作和数据管理的其他细节则对外隐藏——这些是“private”(私有)元素。这种封装方法使我们能够为面板呈现清晰有序的规划,便于未来的开发者们理解每个部分的作用和目的。 

在定义CAnalyticsPanel类之前,需包含两个关键头文件:

Dialog.mqh—— 该头文件提供对对话框相关类的访问,包括CAppDialog,它是创建自定义面板的基类。

Label.mqh—— 其包含CLabel类的定义,用于创建和管理面板上显示的所有文本标签。

这些包含文件对于使自定义面板能够访问MQL5的标准用户界面控制结构是必要的。如果没有它们,面板类将无法继承自CAppDialog或创建标签控件。

我们还需要定义间距和布局的宏(AP_GAP, AP_DEFAULT_LABEL_HEIGHT),以确保其视觉结构既响应迅速又简洁清晰。
#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>

// Use unique macro names for layout in this class:
#define AP_DEFAULT_LABEL_HEIGHT 20
#define AP_GAP                10

以下内容将我们类的核心组件划分为五个关键领域进行阐述:

1. 类结构与用途

CAnalyticsPanel类是一个高度可视化且信息丰富的仪表盘,专为在MetaTrader 5图表中直接实时更新交易和市场状况而设计。该类继承自MQL5标准库中的CAppDialog类,利用面向对象编程,以整齐排列的版块展示结构化数据。面板包括账户详情、持仓交易、市场报价、指标以及综合信号摘要等标签——使得交易者能够在一个紧凑的窗口中监控所有信息。 

//+------------------------------------------------------------------+
//| Analytics Panel Class                                            |
//+------------------------------------------------------------------+
class CAnalyticsPanel : public CAppDialog
{
protected:
   // Helper: Create a text label.
   // The label's text color is set as provided and its background is forced to black.
   bool CreateLabelEx(CLabel &label, int x, int y, int width, int height, string label_name, string text, color clr)
   {
      // Construct a unique control name by combining the dialog name and label_name.
      string unique_name = m_name + "_" + label_name;
      if(!label.Create(m_chart_id, unique_name, m_subwin, x, y, x+width, y+height))
      {
         Print("Failed to create label: ", unique_name, " Err=", GetLastError());
         return false;
      }
      if(!Add(label))
      {
         Print("Failed to add label: ", unique_name, " Err=", GetLastError());
         return false;
      }
      if(!label.Text(text))
      {
         Print("Failed to set text for label: ", unique_name);
         return false;
      }
      label.Color(clr);          // Set text color
      label.Color(clrBlack);   // Force background to black
      return true;
   }

   // Account and Trade Data Labels:
   CLabel m_lblBalance;
   CLabel m_lblEquity;
   CLabel m_lblMargin;
   CLabel m_lblOpenTrades;
   // PnL is split into two labels:
   CLabel m_lblPnLName;
   CLabel m_lblPnLValue;

   // Market Data Labels (split Bid/Ask):
   CLabel m_lblBidName;
   CLabel m_lblBidValue;
   CLabel m_lblAskName;
   CLabel m_lblAskValue;
   CLabel m_lblSpread;

   // Indicator Section Separator:
   CLabel m_lblIndicatorSeparator;
   // New header for indicator section:
   CLabel m_lblIndicatorHeader;

   // Indicator Labels (static name and dynamic value labels):
   CLabel m_lblRSIName;
   CLabel m_lblRSIValue;
   CLabel m_lblStochName;
   CLabel m_lblStochK;  // %K value
   CLabel m_lblStochD;  // %D value
   CLabel m_lblCCIName;
   CLabel m_lblCCIValue;
   CLabel m_lblWilliamsName;
   CLabel m_lblWilliamsValue;

   // New Summary Labels (for consensus across indicators):
   CLabel m_lblSignalHeader;    // Header label for summary column ("SIGNAL")
   CLabel m_lblExpectation;     // Text summary (e.g., "Buy" or "Sell")
   CLabel m_lblConfirmation;    // Confirmation symbol (e.g., up or down arrow)

   // Previous values for dynamic color changes (market data):
   double m_prevBid;
   double m_prevAsk;

public:
   CAnalyticsPanel();
   virtual ~CAnalyticsPanel();

   // Create the panel and initialize UI controls.
   virtual bool CreatePanel(const long chart, const string name, const int subwin,
                            int x1, int y1, int x2, int y2);

   // Update the panel with the latest account, trade, market and indicator data.
   void UpdatePanel();
   void Toggle();

private:
   void CreateControls();
   void UpdateAccountInfo();
   void UpdateTradeStats();
   void UpdateMarketData();
   void UpdateIndicators();
};

针对下方变量,我提供了详细地说明与阐释:

成员变量

账户与交易数据的用户界面(UI)标签:

账户标签:

  • m_lblBalance、m_lblEquity和m_lblMargin分别显示账户余额、账户净值和保证金。
交易统计:
  • m_lblOpenTrades显示当前持仓交易的数量。
盈亏(PnL)信息分两个标签显示:
  1. m_lblPnLName:一个静态标签,仅显示文本“PnL:”(保持黑色不变)。
  2. m_lblPnLValue:一个动态标签,显示盈亏的数值,并根据盈亏情况改变颜色(盈利时为绿色,亏损时为红色)。

市场数据标签:

这些标签显示买入/卖出价及点差信息:

  • m_lblBidName和m_lblBidValue显示买入价。
  • m_lblAskName和m_lblAskValue显示卖出价。
  • m_lblSpread显示当前点差(以点为单位)。
  • 买入价和卖出价的颜色会根据市场变化动态更新(例如,数值增加时显示为蓝色,数值减少时显示为红色)。

指标区域:

  • 分隔线(m_lblIndicatorSeparator)在视觉上将指标区域与面板其余部分区分开来。
  • 标题标签(m_lblIndicatorHeader)显示“INDICATORS”,清晰标识该区域。

该区域包含若干个指标:

  • 相对强弱指数(RSI):两个标签,m_lblRSIName和m_lblRSIValue,分别显示名称(“RSI:”)及其当前值。
  • 随机震荡器:该指标分为两部分:
          1. m_lblStochName:静态部分(“Stoch:”)。
          2. m_lblStochK和m_lblStochD:分别显示%K和%D的动态值。
  • 商品通道指数(CCI):m_lblCCIName和m_lblCCIValue显示商品通道指数。
  • 威廉指标(Williams %R):m_lblWilliamsName和m_lblWilliamsValue显示威廉指标值。

总结(共识)区域:

  • 标题标签(m_lblSignalHeader)在总结列上方显示“SIGNAL”字样。
  • 两个标签,m_lblExpectation和m_lblConfirmation,用于显示所有指标的共识信号:
    1. m_lblExpectation提供文本摘要,如“买入”、“卖出”或“无共识”。
    2. m_lblConfirmation显示符号确认(买入时为上箭头“↑”,卖出时为下箭头“↓”,若无共识则为短横线“-”)。

市场数据内部状态:

两个变量,m_prevBid和m_prevAsk,存储先前的买入价和卖出价,以帮助确定这些值是上升还是下降——从而为这些标签使用的颜色编码提供依据。

2. 标签创建辅助方法

类中的一个核心实用函数是CreateLabelEx,这是一个用于在面板上创建和自定义文本标签的可复用方法。该方法封装了实例化标签、为其分配唯一名称、配置其大小和位置、应用所需文本和颜色以及将其添加到面板控件列表所需的所有步骤。如果此过程中的任何步骤失败,将记录对应的错误消息以供调试。

bool CAnalyticsPanel::CreateLabelEx(CLabel &label, int x, int y, int width, int height,
                                    string label_name, string text, color clr)
{
   string unique_name = m_name + "_" + label_name;
   if(!label.Create(m_chart_id, unique_name, m_subwin, x, y, x+width, y+height))
   {
      Print("Failed to create label: ", unique_name, " Err=", GetLastError());
      return false;
   }
   if(!Add(label))
   {
      Print("Failed to add label: ", unique_name, " Err=", GetLastError());
      return false;
   }
   if(!label.Text(text))
   {
      Print("Failed to set text for label: ", unique_name);
      return false;
   }
   label.Color(clr);          // Set text color
   label.BackColor(clrBlack); // Force background to black
   return true;
}
    

3. 用户界面布局规划

CreateControls方法负责在面板上规划所有用户界面元素。在此方法中,我们划分不同区域:账户信息(余额、净值、保证金)、交易统计(持仓交易数量及盈亏情况)、市场数据(买入价、卖出价及点差)以及指标区域。在指标区域,我们创建了“INDICATORS”和“SIGNAL”等标题,以清晰地标注各组信息。代码使用预定义的标签高度和间距,确保所有元素均匀分布,视觉上整齐有序。

//+------------------------------------------------------------------+
//| CreateControls: Instantiate and add UI controls                  |
//+------------------------------------------------------------------+
void CAnalyticsPanel::CreateControls()
{
   int curX = AP_GAP;
   int curY = AP_GAP;
   int labelWidth = 150;

   // Account Information Labels:
   CreateLabelEx(m_lblBalance, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Balance", "Balance: $0.00", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblEquity, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Equity", "Equity: $0.00", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblMargin, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Margin", "Margin: 0", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Trade Statistics Labels:
   CreateLabelEx(m_lblOpenTrades, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "OpenTrades", "Open Trades: 0", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   // PnL labels:
   CreateLabelEx(m_lblPnLName, curX, curY, 50, AP_DEFAULT_LABEL_HEIGHT, "PnLName", "PnL:", clrBlack);
   CreateLabelEx(m_lblPnLValue, curX+50, curY, labelWidth-50, AP_DEFAULT_LABEL_HEIGHT, "PnLValue", "$0.00", clrLime);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Market Data Labels:
   CreateLabelEx(m_lblBidName, curX, curY, 40, AP_DEFAULT_LABEL_HEIGHT, "BidName", "Bid:", clrDodgerBlue);
   CreateLabelEx(m_lblBidValue, curX+40, curY, 100, AP_DEFAULT_LABEL_HEIGHT, "BidValue", "0.00000", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblAskName, curX, curY, 40, AP_DEFAULT_LABEL_HEIGHT, "AskName", "Ask:", clrDodgerBlue);
   CreateLabelEx(m_lblAskValue, curX+40, curY, 100, AP_DEFAULT_LABEL_HEIGHT, "AskValue", "0.00000", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblSpread, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Spread", "Spread: 0.0 pips", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Indicator Section Separator:
   CreateLabelEx(m_lblIndicatorSeparator, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Separator", "------------------------------------------", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Indicator Section Header:
   CreateLabelEx(m_lblIndicatorHeader, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "IndicatorHeader", "INDICATORS", clrDodgerBlue);
   // Summary Column Header for Signal:
   CreateLabelEx(m_lblSignalHeader, curX+250, curY , 100, AP_DEFAULT_LABEL_HEIGHT, "SignalHeader", "SIGNAL", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Indicator Labels:
   // RSI:
   CreateLabelEx(m_lblRSIName, curX, curY, 50, AP_DEFAULT_LABEL_HEIGHT, "RSIName", "RSI:", clrDodgerBlue);
   CreateLabelEx(m_lblRSIValue, curX+50, curY, 90, AP_DEFAULT_LABEL_HEIGHT, "RSIValue", "N/A", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   // Stochastic:
   CreateLabelEx(m_lblStochName, curX, curY, 60, AP_DEFAULT_LABEL_HEIGHT, "StochName", "Stoch:", clrDodgerBlue);
   CreateLabelEx(m_lblStochK, curX+60, curY, 70, AP_DEFAULT_LABEL_HEIGHT, "StochK", "K: N/A", clrDodgerBlue);
   CreateLabelEx(m_lblStochD, curX+150, curY, 70, AP_DEFAULT_LABEL_HEIGHT, "StochD", "D: N/A", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   // CCI:
   CreateLabelEx(m_lblCCIName, curX, curY, 50, AP_DEFAULT_LABEL_HEIGHT, "CCIName", "CCI:", clrDodgerBlue);
   CreateLabelEx(m_lblCCIValue, curX+50, curY, 90, AP_DEFAULT_LABEL_HEIGHT, "CCIValue", "N/A", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   // Williams %R:
   CreateLabelEx(m_lblWilliamsName, curX, curY, 70, AP_DEFAULT_LABEL_HEIGHT, "WilliamsName", "Williams:", clrDodgerBlue);
   CreateLabelEx(m_lblWilliamsValue, curX+70, curY, 70, AP_DEFAULT_LABEL_HEIGHT, "WilliamsValue", "N/A", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;

   // Summary Column: Expectation text and Confirmation symbol.
   CreateLabelEx(m_lblExpectation, curX+250, curY - (AP_DEFAULT_LABEL_HEIGHT+AP_GAP)*4, 100, AP_DEFAULT_LABEL_HEIGHT, "Expectation", "Expect: N/A", clrDodgerBlue);
   CreateLabelEx(m_lblConfirmation, curX+300, curY - (AP_DEFAULT_LABEL_HEIGHT+AP_GAP)*4, 50, AP_DEFAULT_LABEL_HEIGHT, "Confirmation", "N/A", clrDodgerBlue);

   ChartRedraw(m_chart_id);
}

4. 账户与市场信息的实时数据更新

借助MQL5内置函数,该面板可获取账户信息(如余额、净值和原始保证金值)以及市场数据(如买入价、卖出价和点差)。UpdateAccountInfoUpdateTradeStats方法通过AccountInfoDouble()等函数直接查询账户信息。同时,UpdateMarketData方法使用SymbolInfoTick()获取实时行情数据,将当前值与先前值进行比较,并根据变化更新买价和卖价标签的颜色编码。这使得交易者能够实时查看最新的账户和市场状态。

//+------------------------------------------------------------------+
//| UpdateAccountInfo: Refresh account-related data                  |
//+------------------------------------------------------------------+
void CAnalyticsPanel::UpdateAccountInfo()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double equity  = AccountInfoDouble(ACCOUNT_EQUITY);
   double margin  = AccountInfoDouble(ACCOUNT_MARGIN);

   m_lblBalance.Text("Balance: $" + DoubleToString(balance, 2));
   m_lblEquity.Text("Equity: $" + DoubleToString(equity, 2));
   // Directly display the raw margin value.
   m_lblMargin.Text("Margin: " + DoubleToString(margin, 2));
}

//+------------------------------------------------------------------+
//| UpdateTradeStats: Refresh trade statistics                       |
//+------------------------------------------------------------------+
void CAnalyticsPanel::UpdateTradeStats()
{
   int total = PositionsTotal();
   double pnl = 0;
   for(int i = 0; i < total; i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket != 0)
         pnl += PositionGetDouble(POSITION_PROFIT);
   }
   // Update PnL labels:
   m_lblPnLName.Text("PnL:");  // static remains black
   m_lblPnLValue.Text("$" + DoubleToString(pnl, 2));
   if(pnl < 0)
      m_lblPnLValue.Color(clrRed);
   else
      m_lblPnLValue.Color(clrLime);

   m_lblOpenTrades.Text("Open Trades: " + IntegerToString(total));
}

//+------------------------------------------------------------------+
//| UpdateMarketData: Refresh market data display                    |
//+------------------------------------------------------------------+
void CAnalyticsPanel::UpdateMarketData()
{
   MqlTick last_tick;
   if(SymbolInfoTick(Symbol(), last_tick))
   {
      // Update Bid value and color based on change:
      color bidColor = clrBlue;
      if(m_prevBid != 0)
         bidColor = (last_tick.bid >= m_prevBid) ? clrBlue : clrRed;
      m_lblBidValue.Color(bidColor);
      m_lblBidValue.Text(DoubleToString(last_tick.bid, 5));

      // Update Ask value and color based on change:
      color askColor = clrBlue;
      if(m_prevAsk != 0)
         askColor = (last_tick.ask >= m_prevAsk) ? clrBlue : clrRed;
      m_lblAskValue.Color(askColor);
      m_lblAskValue.Text(DoubleToString(last_tick.ask, 5));

      m_prevBid = last_tick.bid;
      m_prevAsk = last_tick.ask;

      // Update Spread:
      double spread_pips = (last_tick.ask - last_tick.bid) * 1e4;
      m_lblSpread.Text("Spread: " + DoubleToString(spread_pips, 1) + " pips");
   }
}

5. 计算技术指标并生成共识信号

UpdateIndicators方法通过调用指标函数并读取其最新数据,获取多个技术指标(RSI、震荡器、CCI和威廉指标)的值。将每个指标的值与预设的阈值进行比较,以确定其是否发出买入或卖出信号。例如,RSI低于30被解读为买入信号,高于70则建议卖出。随后,面板将这些单个信号整合为一个总体共识;如果所有指标达成一致,总结区域将显示清晰的“买入”或“卖出”信号,并附带箭头符号。这就为交易者提供了关于市场状况的快速视觉提示。

//+------------------------------------------------------------------+
//| UpdateIndicators: Calculate and display various indicators       |
//+------------------------------------------------------------------+
void CAnalyticsPanel::UpdateIndicators()
{
   // Local suggestion variables (1 = Buy, -1 = Sell, 0 = Neutral)
   int suggestionRSI = 0, suggestionStoch = 0, suggestionCCI = 0, suggestionWilliams = 0;

   // --- RSI ---
   int handleRSI = iRSI(Symbol(), PERIOD_CURRENT, 14, PRICE_CLOSE);
   double rsi[1];
   color rsiValueColor = clrGreen; // default
   if(CopyBuffer(handleRSI, 0, 0, 1, rsi) > 0)
   {
      if(rsi[0] < 30)
      {
         rsiValueColor = clrBlue; // buy condition
         suggestionRSI = 1;
      }
      else if(rsi[0] > 70)
      {
         rsiValueColor = clrRed;  // sell condition
         suggestionRSI = -1;
      }
      m_lblRSIValue.Color(rsiValueColor);
      m_lblRSIValue.Text(DoubleToString(rsi[0], 2));
   }
   IndicatorRelease(handleRSI);

   // --- Stochastic ---
   int handleStoch = iStochastic(Symbol(), PERIOD_CURRENT, 5, 3, 3, MODE_SMA, STO_LOWHIGH);
   double stochK[1], stochD[1];
   if(CopyBuffer(handleStoch, 0, 0, 1, stochK) > 0 && CopyBuffer(handleStoch, 1, 0, 1, stochD) > 0)
   {
      // Initialize color variables.
      color colorK = clrGreen;
      color colorD = clrGreen;
      if(stochK[0] > stochD[0])
      {
         colorK = clrBlue;
         colorD = clrRed;
         suggestionStoch = 1;
      }
      else if(stochK[0] < stochD[0])
      {
         colorK = clrRed;
         colorD = clrBlue;
         suggestionStoch = -1;
      }
      else
         suggestionStoch = 0;

      m_lblStochK.Color(colorK);
      m_lblStochD.Color(colorD);
      m_lblStochK.Text("K: " + DoubleToString(stochK[0], 4));
      m_lblStochD.Text("D: " + DoubleToString(stochD[0], 4));
   }
   IndicatorRelease(handleStoch);

   // --- CCI ---
   int handleCCI = iCCI(Symbol(), PERIOD_CURRENT, 14, PRICE_TYPICAL);
   double cci[1];
   color cciValueColor = clrGreen;
   if(CopyBuffer(handleCCI, 0, 0, 1, cci) > 0)
   {
      if(cci[0] < -100)
      {
         cciValueColor = clrBlue; // buy condition
         suggestionCCI = 1;
      }
      else if(cci[0] > 100)
      {
         cciValueColor = clrRed;  // sell condition
         suggestionCCI = -1;
      }
      m_lblCCIValue.Color(cciValueColor);
      m_lblCCIValue.Text(DoubleToString(cci[0], 2));
   }
   IndicatorRelease(handleCCI);

   // --- Williams %R ---
   int handleWPR = iWPR(Symbol(), PERIOD_CURRENT, 14);
   double williams[1];
   color williamsValueColor = clrGreen;
   if(CopyBuffer(handleWPR, 0, 0, 1, williams) > 0)
   {
      if(williams[0] > -80)
      {
         williamsValueColor = clrBlue; // buy condition
         suggestionWilliams = 1;
      }
      else if(williams[0] < -20)
      {
         williamsValueColor = clrRed;  // sell condition
         suggestionWilliams = -1;
      }
      m_lblWilliamsValue.Color(williamsValueColor);
      m_lblWilliamsValue.Text(DoubleToString(williams[0], 2));
   }
   IndicatorRelease(handleWPR);

   // --- Consensus Summary ---
   int consensus = 0;
   if(suggestionRSI != 0 && suggestionRSI == suggestionStoch &&
      suggestionRSI == suggestionCCI && suggestionRSI == suggestionWilliams)
      consensus = suggestionRSI;

   if(consensus == 1)
   {
      m_lblExpectation.Text("Buy");
      m_lblExpectation.Color(clrBlue);
      m_lblConfirmation.Text("↑"); // Up arrow for Buy
      m_lblConfirmation.Color(clrBlue);
   }
   else if(consensus == -1)
   {
      m_lblExpectation.Text("Sell");
      m_lblExpectation.Color(clrRed);
      m_lblConfirmation.Text("↓"); // Down arrow for Sell
      m_lblConfirmation.Color(clrRed);
   }
   else
   {
      m_lblExpectation.Text("No Consensus");
      m_lblExpectation.Color(clrOrangeRed);
      m_lblConfirmation.Text("-");
      m_lblConfirmation.Color(clrOrangeRed);
   }
}

将CAnalyticsPanel集成到主管理面板中

1. 在EA文件顶部,引入AnalyticsPanel.mqh头文件,以提供对CAnalyticsPanel类的访问权限:

#include <AnalyticsPanel.mqh>

这样确保编译器在编译过程中能够识别该类及其成员。

2. 声明一个全局指针

声明一个类型为CAnalyticsPanel的全局指针,该指针将用于动态管理面板实例:

CAnalyticsPanel *g_analyticsPanel = NULL;

此指针有助于跟踪面板是否存在,并支持动态创建、销毁和切换其可见性。

3. 创建一个用于启动面板的按钮

在CreateAdminPanel()函数中,创建一个标有“Analytics Panel”的按钮,并将其添加到主管理面板中。该按钮作为用户启动面板的触发器。

bool CreateAdminPanel()
{
 
   // Analytics Panel Button
   if(!CreateButton(g_analyticsButton, ANALYTICS_BTN_NAME, INDENT_LEFT, y, INDENT_LEFT+btnWidth, y+BUTTON_HEIGHT, "Analytics Panel"))
      return false;
  
return true;

}

4. 处理按钮点击事件

HandleAnalytics()函数负责管理面板的生命周期。其会检查是否已经创建面板。如果尚未创建,则实例化CAnalyticsPanel对象,调用其CreatePanel()方法并传入定位坐标,然后切换其可见性(显示或隐藏)。如果面板已存在,则直接切换其显示/隐藏状态。

//------------------------------------------------------------------
// Handle Analytics Panel button click
void HandleAnalytics()
{
   if(g_analyticsPanel == NULL)
   {
      g_analyticsPanel = new CAnalyticsPanel();
      // Coordinates for Analytics panel (adjust as needed)
      if(!g_analyticsPanel.CreatePanel(g_chart_id, "AnalyticsPanel", g_subwin, 900, 20, 900+500, 20+460))
      {
         delete g_analyticsPanel;
         g_analyticsPanel = NULL;
         Print("Failed to create Analytics Panel");
         return;
      }
   }
   g_analyticsPanel.Toggle();
   ChartRedraw();
}

5. 事件转发

在AdminPanelOnEvent()函数中,若g_analyticsPanel处于可见状态,则将诸如点击或用户交互等事件传递给它。这样确保了该面板能够独立响应用户输入,而不会与主面板或其他子面板产生冲突。

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

6. 动态更新面板

在OnTick()函数内部,如果分析面板存在,则会定期调用其UpdatePanel()方法。这使得面板能够在EA运行期间动态更新其显示的分析数据或用户界面内容。

void OnTick()
  {

    if(g_analyticsPanel != NULL)  
    {
      g_analyticsPanel.UpdatePanel();
      
    }
          
  }

7. 清理工作

在OnDeinit()函数中,通过调用Destroy()方法并删除指针,正确销毁面板并释放内存。当EA被移除或重新编译时,这一操作对于避免内存泄漏至关重要。


测试

我的测试体验非常棒。看着面板上的数值随每次价格变动而更新,感觉几乎就像实盘交易一样。我使用综合策略成功进行了一笔交易,不过遗憾的是,当我开始录制时,信号已经转变为“无共识”状态。尽管如此,我仍迫不及待地想分享测试结果——请参见以下图片!

测试分析面板及新管理面板(New_Admin_Panel)的交易功能

分析面板功能演示测试

在上图中,每关闭一个持仓头寸,“持仓交易数”的值便会实时更新。我们初始持有21个头寸,最终剩余18个。

分析面板

所有面板均可从主面板(首页面板)访问



结论

我们通过集成分析面板成功扩展了程序功能。这一新增功能表明,采用与其他面板相同的模块化方法,还可以整合更多子面板。本系列文章至此(第九部分)结束。然而,这并不意味着工作已经完成——仍有多个方面有待完善。尽管如此,核心概念现已确立且阐述清晰。

本系列文章对初学者和经验丰富的开发者均具有不同层面的参考价值。其中包含许多值得借鉴的深刻见解。这些理念为未来更高级的开发奠定了坚实基础。

在呈现的测试结果中,我在图表上绘制了通道线,巧合的是,分析面板同时生成了买入信号。该信号由所有列示指标的共振效应产生,与通道支撑区完美契合。基于这种共振策略,我果断执行了这笔交易。测试在模拟账户进行——一如既往地,我强烈建议所有交易者在投入真金白银前先在模拟账户充分测试。

这一系统的优势在于,交易者可通过分析面板即时获取关键市场和账户信息,同时受益于基于共振原理的信号分析能力。有待改进的方面是该策略缺乏预警功能,可以添加通知功能将使系统更加人性化,确保不错过任何交易机会。

头文件和主程序代码已附于下方。欢迎在评论区分享您的观点和想法。下期再见——祝您开发顺利,交易成功!

文件名 说明
AnalyticsPanel.mqh 用于定义分析面板的结构与行为的头文件,该面板整合多个技术指标以生成基于共振原理的交易信号。
New_Admin_Panel.mqh MQL5 EA主程序,负责初始化并管理整个管理面板界面,包括导航功能、身份验证,以及整合通信、分析和交易管理等各类子面板。

返回内容目录

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17397

附加的文件 |
AnalyticsPanel.mqh (32.77 KB)
New_Admin_Panel.mq5 (19.61 KB)
最近评论 | 前往讨论 (1)
amrhamed83
amrhamed83 | 16 4月 2025 在 11:17
能否将所有文件一起放到附件中?
圆搜索算法(CSA) 圆搜索算法(CSA)
本文提出一种基于圆几何特性的新型元启发式优化算法——圆搜索算法(CSA)。该算法通过模拟切线方向上的点移动机制,在解空间中实现全局探索与局部开发的协同优化。
JSON 从入门到精通: 创建自己的 MQL5 版本 JSON 解读器 JSON 从入门到精通: 创建自己的 MQL5 版本 JSON 解读器
体验分步指南,创建自定义的 MQL5 版本 JSON 解析器,囊括对象和数组处理、错误检查、及序列化。通过这款灵活的解决方案,在 MetaTrader 5 中处理 JSON,获取桥接交易逻辑与结构化数据的实用见解。
您应当知道的 MQL5 向导技术(第 54 部分):搭配混合 SAC 和张量的强化学习 您应当知道的 MQL5 向导技术(第 54 部分):搭配混合 SAC 和张量的强化学习
软性参与者-评论者是一种强化学习算法,我们曾在之前的系列文章中考察过 Python 和 ONNX,作为高效的网络训练方式。我们重新审视该算法,意在利用张量,即 Python 中常用的计算图形。
将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) —— 测试交易策略 将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) —— 测试交易策略
随着当今人工智能的快速发展,语言模型(LLMs)是人工智能的重要组成部分,因此我们应该考虑如何将强大的 LLMs 整合到我们的算法交易中。对于大多数人来说,很难根据他们的需求微调这些强大的模型,在本地部署它们,然后将它们应用于算法交易。本系列文章将采取循序渐进的方法来实现这一目标。