MQL5交易管理面板开发(第九部分):代码组织(4):交易管理面板类
内容
概述
在之前的讨论中,我们提出“代码组织”是让交易管理员面板项目顺利、可扩展扩容的关键策略,并遵循“关注点分离”原则。这一方法使我能够专注于新管理面板的每一个子面板,确保程序保持模块化与良好结构。通过独立开发每个功能,我们可以对其进行精炼,以提供最优性能。
此前,我们的交易面板功能有限,但如今已比以往任何时候都更强大。其在同一界面内既能快速执行交易,又内置风险管理。还可直接设置挂单,简化交易流程。
本次更新解决了两大难题:
- 构建大型、组织良好的程序,使其更易开发与维护。
- 确保在集成通信界面、交易管理界面(不久的将来还会有分析界面)的一体化EA中,能够快速访问交易操作。
下面,我将概述本次讨论内容,并说明我们如何一步步完成这些改进。
讨论概述
我们文章的核心目标,是通过把MQL5运用到各类实际项目,让其真正“落地好用”。今天,我们将探讨交易管理面板类的开发,请记住,在MQL5中,类头文件包含同类变量的声明。在此基础上,面板打算纳入的所有交易功能,都将继承自内建的类头文件,如CTrade、CDialog、CLabel与CEdit等。
待类全部开发完成后,我们会把其方法集成进主程序——NewAdminPanel EA。如果不考虑测试结果、不附上源文件,我们的讨论就不算完整,允许您查看实现、借鉴思路,并手动改写代码,优化自己的项目。
现阶段,我决定把“主页面板”的创建集中到主程序里,因为这并未显著增加代码长度。尽管先前方案也有优点,但我选择此结构以减少依赖、简化开发。我的目标是让主程序保持聚焦,同时为每个具体功能使用专门类。现在,核心界面元素直接在主程序中生成,使得设计更精简高效。因此,我们不再使用在NewAdminPanel中调用AdminHomeDialog类的方法。
下图展示了本次讨论结束时我们将创建的成果。但这只是起点——一旦建成,它便成为未来增强与改进的坚实基础。
交易管理面板(讨论结束时)
本项目优势
我们正在开发的工具提供以下关键收益:
- 快速交易 —— 高效执行订单
- 风险管理 —— 下单前预设止损/止盈,并用挂单提前规划交易
- 通信功能 —— 通过早前搭建的通信面板与其他交易者互发消息
- 深入学习MQL5 —— 更深入了解MQL5面向对象编程
- 社区互动 —— 在评论区分享您的见解与体验
- 可复用性 —— 交易管理面板类可用于其他项目
交易管理面板类(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控件的结构化集合。单独组织这些元素,确保界面与订单逻辑清晰分离。
//+------------------------------------------------------------------+ //| 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. 用逻辑分区构建用户界面
井然有序的界面决定一切。我们不再随机散落按钮和输入框,而是把面板划分为三大主区。
- 第一区为市场价快捷订单,用户可一键买卖、设置手数,并配置止损/止盈水平。
- 第二区为挂单管理。这里,用户可以指定价格、到期时间和订单类型(如买入限价单或卖出止损单)。
- 第三区为批量操作,例如一键全平、只平盈利单或删除全部挂单。功能分区一目了然,界面导航清晰直观。
//+------------------------------------------------------------------+ //| 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代码保持整洁。如果想改动按钮或标签的外观,只需更新一处函数,而无需在代码库里修改几十行。
//--- 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. 市价单的校验与执行
下单前,系统先验证输入,防止出错。检查手数是否在允许范围,确认止损/止盈相对市价设置正确。
如果输入有效,交易执行对象立即处理订单。否则弹窗提醒,阻止错误订单下发。这一校验步骤至关重要,可以避免代价高昂的错误,确保面板可靠运行。
//+------------------------------------------------------------------+ //| 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. 使用附加参数管理挂单
挂单比市价单需要更多的设置。用户必须指定入场价与到期时间,因此我们扩展校验函数来处理这些输入。
辅助函数负责解析到期设置——无论用户选择“固定时间”还是“长期有效(GTC)”。该结构确保挂单在提交前符合完整规则。
//+------------------------------------------------------------------+ //| 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. 增加重置与可见性控制,提升可用性
为改善用户体验,我们新增两项贴心小功能。第一项是一键清空所有输入框,让面板回到默认状态。这样可以方便用户在修改后重新开始。
第二项是允许用户随时显示或隐藏整个面板。这样既可以使工作区保持整洁,同时又能在需要时将面板立刻调出。
//+------------------------------------------------------------------+ //| 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. 实现交易面板的点击交互
现在,有趣的部分来了。我们创建了一个管理交易面板生命周期的函数。
- 如果交易面板不存在,那么我们来创建。
- 如果面板已存在,那么我们切换其显示或隐藏状态。
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在Codebase发布的信息仪表板源代码,其思路对本项目助益良多。请大胆扩展并修改随附代码,打磨您的MQL5技能,创建更强大的工具。
| 文件 | 描述 |
|---|---|
| TradeManagementPanel.mqh | 类头文件源代码:为New_Admin_Panel EA提供高效的交易操作图形界面。还可以集成在其他EA中。 |
| New_Admin_Panel.mq5 | New_Admin_Panel EA源代码:作为交易平台内的集中式管理中枢,经编译后可在交易平台中运行,负责交易执行、通信交互、数据分析及其他管理功能。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17396
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
您应当知道的 MQL5 向导技术(第 53 部分):市场促进指数
风险管理(第一部分):建立风险管理类的基础知识
精通日志记录(第六部分):数据库日志存储方案
MQL5中的高级内存管理与优化技术