
在MQL5中创建交易管理员面板(第八部分):分析面板
引言
本文标志着管理员面板EA中第三个子面板的开发,重点是打破现有局限并增强其功能。虽然现有设计已支持通信和交易管理,但今天的扩展引入了统计工具,以简化对关键市场指标的分析。通过自动化研究和计算,这些工具消除了对手动方法的依赖,为交易管理员简化了流程。受饼图所展示数据的简洁性和清晰性的启发,我们将专注于交易绩效分布的两个关键方面:盈亏比和交易类型分类。这些指标能让人迅速洞察交易成功情况以及在不同资产类别(如外汇、股票或期权)间的交易分配情况。
分析面板利用实时数据可视化来解决手动分析的效率低下问题。通过引入饼图,该面板使用户能够快速评估其盈亏比和交易类型分布,从而在决策过程中毫无延迟。此功能标志着效率上的巨大飞跃,使管理员能够准确、迅速地做出明智决策。
在本次开发中,我们将利用MQL5的PieChart和ChartCanvas类来自动化这些流程,从而展示高级统计工具的潜力。通过此增强功能,管理员面板EA演进成一个更强大的系统,一目了然地提供有价值的信息,同时彰显了本开发系列工具的教育价值和实际效益。
为了确保项目的成功,我将以下子主题作为我们的核心内容:
分析面板概述
分析面板将是一个动态且交互式的界面,旨在提供对交易绩效和活动分布的可视化信息。在今天的讨论中,该面板包含两个主要的饼图:盈亏饼图,用于展示盈利和亏损交易的比例;以及交易类型分布图,用于将交易分类为外汇、股票和期货。这些图表无缝集成到面板中,提供了简洁直观的布局,便于解读。通过利用来自交易历史的实时数据,分析面板提供了交易结果的全面快照,使用户能够评估交易表现。
分析面板可以通过增加更多的可视化和指标来进一步增强,从而对交易绩效和活动提供更全面的分析。以下是一些可以集成的功能:
- 绩效折线图
- 交易量柱状图
- 盈利指标表
- 表现最佳资产板块
- 交易时间热力图
- 盈/亏连续追踪器
- 风险敞口图表
- 可自定义警报和阈值
- 集成情绪分析
- 交互式过滤器等
换句话说,分析面板提供了交易绩效的全面概览,使用户能够识别模式、评估盈利能力并监控随时间推移的进展。这有助于交易者随时了解自己的优势和劣势,确保他们能够根据数据驱动的方式调整策略。现在,我们到了对旧版管理员面板进行升级的阶段。我也花时间优化了之前代码的每一行,以确保其易于理解。
使用CDialog类搭建分析面板
升级一个大型程序常常让人感到不知所措,尤其是当它涉及重复之前已完成的任务时。这就是为什么拥有一个定义明确的模板来管理程序的布局和结构至关重要。有了可靠的模板并通过反复使用,它就会内化到您的工作流程中,使您能够执行完整的开发任务而无需不断查阅参考。我们的程序确实已经变得非常庞大,为了管理这种复杂性,我专注于我想要实现的目标及其流程,并将这些目标与程序的模块化部分相结合。
例如,考虑我们的管理员主面板,它在程序启动后充当中央界面。从那里,我们可以访问其他面板。为了包含一个分析面板,我设想在管理员主面板中设置一个按钮,点击该按钮即可创建并打开分析面板。在分析面板内部,我设想了针对其特定用途的控制按钮和功能。有了这个愿景,开发过程就获得了清晰的方向和明确的起点。让我带你了解一下我们采用的方法。
长话短说,从这里开始;
首先,我们考虑包含将要使用的必要类:
#include <Controls\Dialog.mqh> #include <Controls\Button.mqh>
分析面板的对话框和按钮的声明:
// Global variables for the Analytics Panel CDialog analyticsPanel; // The main panel for analytics CButton adminHomeAnalyticsButton; // Button for accessing analytics from the Home panel CButton minimizeAnalyticsButton; // Minimize button for the Analytics panel CButton closeAnalyticsButton; // Close button for the Analytics panel CButton analyticsPanelAccessButton; // The first Button that will take us a next step
分析面板的创建与按钮处理程序:
为了在点击analyticsPanelAccessButton时创建分析面板,我们通过一个按钮处理函数来实现此功能。其他面板也采用了类似的方法。在此前的版本中,这些面板是在初始化阶段就被创建并隐藏,这给初始化函数带来了不必要的负载。而现在,面板改为在点击相应按钮时按需动态创建,从而优化了性能和资源使用。下面是展示这一改进的代码片段:
//+------------------------------------------------------------------+ //| Analytics Panel Event Handling | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { if (sparam == "AnalyticsPanelAccessButton") { analyticsPanel.Show(); adminHomePanel.Hide(); if (!analyticsPanel.Create(ChartID(), "Analytics Panel", 0, 500, 450, 1280, 650) || !CreateAnalyticsPanelControls()) {} CreateAnalyticsPanel(); } else if (sparam == "MinimizeAnalyticsButton") { analyticsPanel.Hide(); adminHomePanel.Show(); } else if (sparam == "CloseAnalyticsButton") { analyticsPanel.Destroy(); adminHomePanel.Show(); } } }
此代码片段在OnChartEvent函数中处理与分析面板相关的事件,特别是当检测到用户点击图表对象(CHARTEVENT_OBJECT_CLICK)时。如果点击了“分析面板访问按钮”,则会显示分析面板,隐藏管理员主面板,并通过CreateAnalyticsPanel函数动态创建分析面板及其控件。如果点击了“最小化分析按钮”,则会隐藏分析面板,并再次显示管理员主面板。最后,如果点击了“关闭分析按钮”,则会完全销毁分析面板,并恢复显示管理员主面板。这种动态处理机制确保了能够根据用户的操作来显示或隐藏相应的面板,从而提升了功能性和用户体验。
创建分析面板控件:
此代码片段定义了一个名为CreateAnalyticsPanelControls的函数,该函数用于初始化并向MQL5应用程序中的分析面板添加控件。函数首先通过ChartID()获取当前图表的ID,并尝试在指定坐标处创建一个最小化按钮(minimizeAnalyticsButton)。如果创建失败,它会记录一条错误信息并返回 false。如果成功,该按钮会被标记为下划线(“_”)并被添加到analyticsPanel容器中。类似地,它会在另一组坐标处创建一个标记为 “X” 的关闭按钮(closeAnalyticsButton),并遵循相同的错误检查流程。函数的末尾是一个占位符注释,提示了可以在此处添加其他与分析相关的控件(例如图表或输入元素)。如果所有控件都创建成功,该函数将返回 true。
bool CreateAnalyticsPanelControls() { long chart_id = ChartID(); // Create Minimize Button if (!minimizeAnalyticsButton.Create(chart_id, "MinimizeAnalyticsButton", 0, 210, -22, 240, 0)) { Print("Failed to create minimize button for Analytics Panel"); return false; } minimizeAnalyticsButton.Text("_"); analyticsPanel.Add(minimizeAnalyticsButton); // Create Close Button if (!closeAnalyticsButton.Create(chart_id, "CloseAnalyticsButton", 0, 240, -22, 270, 0)) { Print("Failed to create close button for Analytics Panel"); return false; } closeAnalyticsButton.Text("X"); analyticsPanel.Add(closeAnalyticsButton); // Add additional controls specific to analytics as needed // For example, charts, labels, or input elements for data representation return true; }
我们还将许多面板的创建工作从初始化函数中移出,改为通过按钮处理程序来触发。自从做出这项改动后,我注意到智能交易系统的性能有了显著提升,尤其是在各个面板之间进行切换时的速度。
下面是整合后的OnChartEvent事件处理函数:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { if (sparam == "HomeButtonComm") { adminHomePanel.Show(); communicationsPanel.Hide(); } else if (sparam == "HomeButtonTrade") { adminHomePanel.Show(); tradeManagementPanel.Hide(); } else if (sparam == "AdminHomeAnalyticsButton") { adminHomePanel.Show(); analyticsPanel.Hide(); } else if (sparam == "MinimizeAnalyticsButton") { analyticsPanel.Hide(); adminHomePanel.Show(); } else if (sparam == "CloseAnalyticsButton") { analyticsPanel.Destroy(); adminHomePanel.Show(); } else if (sparam == "TradeMgmtAccessButton") { tradeManagementPanel.Show(); adminHomePanel.Hide(); if (!tradeManagementPanel.Create(ChartID(), "Trade Management Panel", 0, 500, 30, 1280, 170) || !CreateTradeManagementControls()) {} } else if (sparam == "CommunicationsPanelAccessButton") { communicationsPanel.Show(); adminHomePanel.Hide(); if (!communicationsPanel.Create(ChartID(), "Communications Panel", 0, 20, 150, 490, 650) || !CreateCommunicationsPanelControls()) {} } else if (sparam == "CloseHomeButton") { adminHomePanel.Destroy(); } else if (sparam == "MinimizeHomeButton") { adminHomePanel.Hide(); maximizeHomeButton.Show(); } else if (sparam == "MaximizeHomeButton") { adminHomePanel.Show(); maximizeHomeButton.Show(); } else if (sparam == "AnalyticsPanelAccessButton") { analyticsPanel.Show(); adminHomePanel.Hide(); if (!analyticsPanel.Create(ChartID(), "Analytics Panel", 0, 500, 450, 1280, 650) || !CreateAnalyticsPanelControls()) {}; CreateAnalyticsPanel(); } else if (sparam == "ShowAllButton") { analyticsPanel.Show(); communicationsPanel.Show(); tradeManagementPanel.Show(); adminHomePanel.Hide(); } else if (sparam == "MinimizeComsButton") { OnMinimizeComsButtonClick(); } else if (sparam == "CloseComsButton") { communicationsPanel.Destroy(); } else if (sparam == "LoginButton") { OnLoginButtonClick(); } else if (sparam == "CloseAuthButton") { OnCloseAuthButtonClick(); } else if (sparam == "TwoFALoginButton") { OnTwoFALoginButtonClick(); } else if (sparam == "Close2FAButton") { OnClose2FAButtonClick(); } } switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeComsButton") OnMinimizeComsButtonClick(); else if (sparam == "CloseComsButton") OnCloseComsButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -1) { long index = StringToInteger(StringSubstr(sparam, 18)); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } }
最后,我还考虑了对管理员主面板进行调整,如下面这段代码片段中所示的坐标和宽度:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!ShowAuthenticationPrompt()) { Print("Authorization failed. Exiting..."); return INIT_FAILED; } if (!adminHomePanel.Create(ChartID(), "Admin Home Panel", 0, 30, 80,330, 550)) { Print("Failed to create Admin Home Panel"); return INIT_FAILED; } if (!CreateAdminHomeControls()) { Print("Home panel control creation failed"); return INIT_FAILED; } adminHomePanel.Hide(); // Hide home panel by default on initialization return INIT_SUCCEEDED; }
获取用于显示的交易历史数据
现在,我们需要从终端历史记录中获取数据,以便在我们新面板中通过饼图进行展示。GetTradeData函数旨在分析历史交易数据,并将其分类到特定类别中,为详细的交易表现分析奠定基础。该函数首先初始化盈利、亏损以及三种交易类型(外汇、股票和期货)的计数器。该函数依赖于HistorySelect函数来检索从交易账户历史开始到当前时间的交易数据。如果此选择过程失败,函数会记录一条错误信息并退出,以确保在历史数据不可用时程序的健壮性。然后,它会遍历所有可用的交易记录,使用 HistoryDealGetTicket 来检索每笔交易的唯一标识符。对于每一笔有效交易,该函数通过分析其利润值来评估其盈利能力,并根据利润为正或为负来增加盈利或亏损的计数器。
交易的分类是由其交易品种决定的。外汇交易通过其品种名称中不包含点(.)来识别,而股票和期货交易则通过检查品种的SYMBOL_PATH属性来进行分类。品种的组名决定了它属于“股票”还是“期货”类别。这一步骤确保了交易能够根据其金融工具被准确地分组。通过汇总这些信息,该函数提供了交易表现的全面分解,可用于进一步的分析或可视化。
在今天的场景中,此函数可以被集成到分析面板中,用于生成一个饼图,展示交易类别的分布以及盈亏比率。例如,交易者可以使用该函数来可视化其交易情况:60%是外汇交易,30%是股票交易,10%是期货交易,而其整体成功率为70%。这些信息对于评估交易表现和识别需要改进的领域具有不可估量的价值。此外,该函数的实时数据分析能力使其适用于创建响应式仪表板,帮助交易者根据历史趋势调整策略。
除了当前的实现,GetTradeData函数还有着更广泛的应用潜力。它可以被扩展以分析特定的时间范围,或纳入额外的指标,例如平均利润或回撤。然后,这些数据可以被集成到外部工具中,例如用于预测分析的机器学习模型,或用于投资者演示的交互式报告。通过这些扩展,该函数将成为一个多功能工具,既适用于个人交易者,也适用于旨在最大化效率和盈利能力的更大规模的交易系统。
下面是已实现的代码:
//+------------------------------------------------------------------+ //| 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++; } } } } } }
实现PieChart和ChartCanvas类来展示数据
我们从包含以下类库开始:
#include <Canvas\Charts\PieChart.mqh> #include <Canvas\Charts\ChartCanvas.mqh>
自定义饼图类:
我们首先从基类CPieChart派生出了CCustomPieChart类。其目的是暴露出受保护的DrawPie方法,该方法通常在父类外部是无法访问的。通过创建一个封装了 DrawPie 的公共方法 DrawPieSegment,我们获得了动态绘制单个饼图区段的灵活性。当在CAnalyticsChart类的DrawPieChart方法中实现自定义渲染逻辑时,这一点尤其有用。这一步骤确保了我们对每个饼图扇区的视觉表现拥有精细的控制,从而让我们能够为分析面板构建更具动态性和视觉定制效果的饼图。
//+------------------------------------------------------------------+ //| 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 } };
分析图表类:
接下来,我们扩展了CWnd 类,以创建一个专门的图表容器 CAnalyticsChart。在这个类中,我们将CCustomPieChart作为一个成员变量进行集成,使其能够作为绘制饼图的基础。我们实现了诸如CreatePieChart这样的方法,用于在指定区域内初始化饼图控件;同时还实现了SetPieChartData方法,用于将数据值、标签和颜色关联到图表上。此外,DrawPieChart方法被精心编码,用于根据数据集计算每个区段的角度跨度,并调用DrawPieSegment进行渲染。通过实现这一逻辑,我们确保了饼图可以被动态地绘制出来,并以一种视觉上引人入胜的方式反映其底层数据。
//+------------------------------------------------------------------+ //| 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)); } };
创建分析面板函数:
为了将所有部分整合在一起,我们编写了CreateAnalyticsPanel函数来实现分析面板的创建。首先,我们使用GetTradeData函数获取了交易数据——例如盈利、亏损和交易类型的数量。然后,我们为不同的可视化效果实例化了两个CAnalyticsChart对象。对于第一个图表,我们使用获取的盈利/亏损数据来设置一个标记为“盈利与亏损饼图”的饼图。类似地,对于第二个图表,我们使用交易类型数据来创建一个“交易类型分布”饼图。通过为每个图表调用 SetPieChartData 和 DrawPieChart,我们动态地渲染了它们,并将它们添加到 analyticsPanel 中。这种方法使我们能够将代码分解为模块化和可重用的组件,从而确保了代码的清晰性和可维护性。
//+------------------------------------------------------------------+ //| Create Analytics Panel | //+------------------------------------------------------------------+ void CreateAnalyticsPanel() { int wins, losses, forexTrades, stockTrades, futuresTrades; GetTradeData(wins, losses, forexTrades, stockTrades, futuresTrades); // Declare pieChart1 and pieChart2 as local variables CAnalyticsChart pieChart1; CAnalyticsChart pieChart2; // Win vs Loss Pie Chart if (!pieChart1.CreatePieChart("Win vs. Loss Pie Chart", 20, 20, 300, 300)) { Print("Error creating Win/Loss Pie Chart"); return; } double winLossValues[] = {wins, losses}; string winLossLabels[] = {"Wins", "Losses"}; uint winLossColors[] = {clrGreen, clrRed}; pieChart1.SetPieChartData(winLossValues, winLossLabels, winLossColors); pieChart1.DrawPieChart(winLossValues, winLossColors, 150, 150, 140); // Add pieChart1 to the analyticsPanel analyticsPanel.Add(pieChart1); // Trade Type Pie Chart if (!pieChart2.CreatePieChart("Trade Type Distribution", 350, 20, 300, 300)) { Print("Error creating Trade Type Pie Chart"); return; } double tradeTypeValues[] = {forexTrades, stockTrades, futuresTrades}; string tradeTypeLabels[] = {"Forex", "Stocks", "Futures"}; uint tradeTypeColors[] = {clrBlue, clrOrange, clrYellow}; pieChart2.SetPieChartData(tradeTypeValues, tradeTypeLabels, tradeTypeColors); pieChart2.DrawPieChart(tradeTypeValues, tradeTypeColors, 500, 150, 140); // Add pieChart2 to the analyticsPanel analyticsPanel.Add(pieChart2); // Show the analyticsPanel analyticsPanel.Show(); }
我们这样做的原因:
通过以这种方式编写系统代码,我们确保了图表的创建既具有动态性又具有灵活性。派生 CCustomPieChart 使我们能够控制饼图的渲染,而 CAnalyticsChart 则允许我们将饼图功能封装到一个自包含的类中。这使得添加新图表或调整其行为变得容易,且不会影响程序的其他部分。例如,在今天的项目中,如果我们想为净值曲线分析添加另一个图表,我们可以毫不费力地重用相同的 CAnalyticsChart 结构。这种模块化方法不仅简化了开发过程,也使分析面板在未来进行功能增强时具有高度的可扩展性。
防止数组越界错误:
为了防止在PieChart.mqh文件的CPieChart::DrawPie方法中出现“数组越界”错误,我们添加了一个范围检查,以确保在访问CPoint数组(p[]之前,其 索引 (idx + 1) 在数组的边界之内。此保护措施确保了数组在使用前具有合适的大小,并防止了无效操作。如果索引超出范围,函数会提前退出并打印一条错误信息以便调试。此外,在饼图渲染期间,CPoint数组会被适当地调整大小以容纳所有的饼图区段,确保数据结构始终足够大以进行计算。新增的条件 if (idx + 1 >= ArraySize(p)) 会检查下一个索引是否有效,如果无效,它将打印错误信息并提前返回,以防止后续处理。此项检查防止了函数尝试访问越界的数组元素,从而避免了该错误的发生。
if (idx + 1 >= ArraySize(p)) { Print("Array out of range error: idx = ", idx, ", ArraySize = ", ArraySize(p)); return; }
请注意,为了防止在测试EA期间出现前面提到的错误,我们必须对内置的饼图类进行修改。
//+------------------------------------------------------------------+ //| Draw pie | //+------------------------------------------------------------------+ void CPieChart::DrawPie(double fi3, double fi4, int idx, CPoint &p[], const uint clr) { // Ensure array index is within bounds if (idx + 1 >= ArraySize(p)) { Print("Array out of range error: idx = ", idx, ", ArraySize = ", ArraySize(p)); return; } //--- draw arc Arc(m_x0, m_y0, m_r, m_r, fi3, fi4, p[idx].x, p[idx].y, p[idx + 1].x, p[idx + 1].y, clr); //--- variables int x3 = p[idx].x; int y3 = p[idx].y; int x4 = p[idx + 1].x; int y4 = p[idx + 1].y; //--- draw radii if (idx == 0) Line(m_x0, m_y0, x3, y3, clr); if (idx != m_data_total - 1) Line(m_x0, m_y0, x4, y4, clr); //--- fill double fi = (fi3 + fi4) / 2; int xf = m_x0 + (int)(0.99 * m_r * cos(fi)); int yf = m_y0 - (int)(0.99 * m_r * sin(fi)); Fill(xf, yf, clr); //--- for small pie if (fi4 - fi3 <= M_PI_4) Line(m_x0, m_y0, xf, yf, clr); }
测试新功能
在本节中,我们将展示我们功能优化的成果,呈现程序的更新版本及其特性。以下是一系列说明改进之处的图片。首先,我们重点展示了重新设计的管理员主面板以及新添加的按钮。接下来,我们展示了分析面板,其中包含交易数据分布的可视化图表。随后,我们展示了管理员面板的完整视图,其中所有子面板均可见。最后,我们包含了一个动画屏幕录制,演示了应用程序在终端图表上的部署过程,以实现无缝集成。
新的管理员主界面
分析面板
管理员面板 V1.23 完整视图
Boom 300 指数,H4:管理员面板 V1.24 EA 启动
结论
在今天的讨论中,我们探讨了 MQL5 中高级功能的开发,重点是集成新的类和技术以增强管理员面板及其功能。通过利用 MQL5 的强大功能,我们成功地实现了动态面板和像饼图这样的数据驱动可视化,以有效地展示交易表现。该项目展示了 MQL5 在创建面向交易者和开发者的复杂、用户友好工具方面的巨大潜力。
新实现的分析面板为交易者提供了关于其交易表现的可操作洞察,同时为开发者提供了一个可以在此基础上构建的坚实框架。通过解决面板混乱、对象分层和动态控件创建等挑战,我们为一个更高效、更直观的界面奠定了基础。这些改进是基础性的,也是未来创新的垫脚石。开发者可以扩展此框架,以纳入更多的分析功能、交互特性,甚至是全新的功能。所附的源文件和图片展示了我们努力的成果,为其他人探索 MQL5 的无限可能性提供了灵感。祝您交易愉快,并希望本指南能激发您的创造力,进一步推进您的项目!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16356
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



你好,我很想看到这个作品,但在编译时出现了一些错误。我错过了什么?
你好,我很想看到这个作品,但在编译时出现了一些错误。我错过了什么?
嘿,我的好朋友!
在本系列的第三部分,我们扩展了对话框类,使其包含主题管理功能。要解决你遇到的错误,只需下载 扩展文件并复制到相应位置即可:
完成后,请尝试再次编译。错误应该已经解决。如果遇到其他错误,请随时联系我们。