风险管理(第二部分):在图形界面中实现手数计算·进阶篇
「在 MT5 里搭风险管理面板的控件骨架」
在 MetaEditor 新建一个 EA 模板后,先引五个标准库:Dialog、Edit、Label、Button、ComboBox,外加自定义的 Risk Management 头文件。没有这些 include,后面所有图形控件连编译都过不了。 控件尺寸用宏写死在文件头,方便统一调。EDIT_HEIGHT 和 BUTTON_HEIGHT 都是 20 像素,BUTTON_WIDTH 80,COMBO_BOX_WIDTH 拉到 200,COMBO_BOX_HEIGHT 也是 20。改这些数字就能整体缩放面板,不用逐个改函数。 订单类型用长度为 8 的字符串数组存,覆盖市价买、市价卖、四种挂单和两种止损限价单。这个数组直接喂给组合框,用户下拉选的就是这 8 个枚举文本。 面板主体类 CRiskManagementPanel 公有继承 CAppDialog。私有段放控件指针和用户输入的变量(每次风险、偏差、止损等),再加对象创建、组合框变更回调、标签刷新等函数;公有段只留构造析构、Create、OnEvent 和字符串转 ENUM_ORDER_TYPE。 控件不绑客户区就会原地失踪。CDialog::Add 有两个重载,收指针或引用,本质都是塞进 m_client_area 容器。漏掉这步,拖面板时按钮标签全留原位,交互直接崩。 四个基础控件的创建套路一致:算坐标→Create→设文本→Add 进客户区。标签静态显示,按钮带默认 80×20 尺寸可覆盖,编辑框宽固定 100、高走 EDIT_HEIGHT 且默认可写,组合框用 AddItem 按索引塞 elements_order_type 再用 SelectByText 定初始项。
class="macro">#include <Controls\Dialog.mqh> class="macro">#include <Controls\Edit.mqh> class="macro">#include <Controls\Label.mqh> class="macro">#include <Controls\Button.mqh> class="macro">#include <Risk Management.mqh> class="macro">#include <Controls\ComboBox.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| defines | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- for edits class="macro">#define EDIT_HEIGHT(class="num">20) class=class="str">"cmt">// edit height class=class="str">"cmt">//--- for buttons class="macro">#define BUTTON_WIDTH(class="num">80) class=class="str">"cmt">// size by X coordinate class="macro">#define BUTTON_HEIGHT(class="num">20) class=class="str">"cmt">// size by Y coordinate class=class="str">"cmt">//--- for combo box class="macro">#define COMBO_BOX_WIDTH(class="num">200) class=class="str">"cmt">// size by X coordinate class="macro">#define COMBO_BOX_HEIGHT(class="num">20) class=class="str">"cmt">// size by Y coordinate class="type">class="kw">string elements_order_type[class="num">8] = { "ORDER_TYPE_BUY", "ORDER_TYPE_SELL", "ORDER_TYPE_BUY_LIMIT", "ORDER_TYPE_SELL_LIMIT", "ORDER_TYPE_BUY_STOP", "ORDER_TYPE_SELL_STOP", "ORDER_TYPE_BUY_STOP_LIMIT", "ORDER_TYPE_SELL_STOP_LIMIT" }; class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Class CRiskManagementPanel | class=class="str">"cmt">//| This class inherits from CAppDialog and will define the panel for |
◍ 风险管理面板的私有成员与控件声明
在 MT5 的 EA 或指标工程里,把风控参数收进一个继承 CAppDialog 的自定义类,是避免主逻辑和 UI 纠缠的常见做法。下面这段类声明给出了面板内部该挂哪些控件、以及用户填完之后落到哪些变量里。 类 CRiskManagementPanel 以 private 区声明了 14 个界面对象:CLabel 负责静态文字,CEdit 负责可输入的风险比例、偏差、止损限制和 SL 数值,CComboBox 存订单类型,CButton 触发手数或 SL 的计算。 对应的 double / long 变量把用户输入固化下来:deviation 存偏差点数,stop_limit 存挂单限制,risk_per_operation 是单笔风险占权益比例,sl 是止损值,两个 ENUM_ORDER_TYPE 分别记录手数计算和 SL 计算所依据的订单类型。 面板还预留了 CreateAreaClientPanel 做整体布局,EditLabelResultSL / EditLabelResultLote 动态改写结果标签,以及 CreateEdit / CreateLabel 两个工厂函数批量生成控件。外汇和贵金属波动大、杠杆高,这类面板只是把参数可视化,实际风控效果仍取决于你填的数值是否合理。
class CRiskManagementPanel : class="kw">public CAppDialog { class="kw">private: CLabel m_label_risk_per_operation; CEdit m_edit_risk_per_operation; CLabel m_label_deviaiton; CEdit m_edit_deviaiton; CLabel m_label_stop_limit; CEdit m_edit_stop_limit; CLabel m_label_get_lote; CComboBox m_combobox_odertype_get_lot; CLabel m_label_sl; CEdit m_edit_sl; CButton m_buttom_get_lote; CLabel m_label_result_lote; CLabel m_label_the_result_lote; CLabel m_label_get_sl; CComboBox m_combobox_odertype_get_sl; CLabel m_label_lote; CButton m_buttom_get_sl; CLabel m_label_result_sl; CLabel m_label_the_result_sl; class=class="str">"cmt">// Variables to store the data entered by the user class="type">class="kw">double deviation; class=class="str">"cmt">// Stores deviation entered by the user class="type">class="kw">double stop_limit; class=class="str">"cmt">// Stores stop limit entered by the user class="type">class="kw">double risk_per_operation; class=class="str">"cmt">// Stores risk per operation entered by the user class="type">long sl; class=class="str">"cmt">// Stores stop loss value entered by the user ENUM_ORDER_TYPE order_type_get_sl; class=class="str">"cmt">// Stores the selected order type for stop loss ENUM_ORDER_TYPE order_type_get_lot; class=class="str">"cmt">// Stores the selected order type for lot size class=class="str">"cmt">//--- create labels and buttons class="type">bool CreateAreaClientPanel(); class=class="str">"cmt">//--- functions to edit labels dynamically class="type">void EditLabelResultSL(class="type">class="kw">string text); class="type">void EditLabelResultLote(class="type">class="kw">string text); class=class="str">"cmt">//--- create controls(buttons, labels, edits, combo boxes) class="type">bool CreateEdit(CEdit &m_edit, const class="type">class="kw">string name, const class="type">int x1, const class="type">int y1, class="type">class="kw">string initial_Text = ""); class="type">bool CreateLabel(CLabel &label, const class="type">class="kw">string name, const class="type">class="kw">string text, const class="type">int x1, const class="type">int y1); };
风控面板的控件挂载与标签生成
在 MT5 自定义面板里,CDialog 的 Add 方法把控件指针或引用塞进 m_client_area,这是所有子控件(按钮、下拉框、标签)能被统一绘制和事件分发的基础。看源码能发现它重载了两种形式:传指针和传引用,底层都调同一个 m_client_area.Add,省得你在外面判断对象生命周期。 CRiskManagementPanel 里 CreateLabel 是典型落地函数:写死标签宽 50 像素、高 20 像素(x2=x1+50, y2=y1+20),再依次调 label.Create、Text、Add。若任何一步返回 false 就直接退出,不抛异常——这意味着你若在循环里批量建 10 个标签,第 3 个坐标越界就会静默断在半路。 下拉框相关声明里 OnChangeComBoxOrderTypeGetLote / GetSL 暴露了面板用途:用户选订单类型后实时算手数和止损,外汇与贵金属波动大,这类自动算仓工具能降手动填错概率,但参数错了仍可能重仓,属高风险操作。 开 MT5 新建 EA 把下面代码贴进类实现,改 x1/y1 跑一下,就能看到标签在图表左上角堆出来。
class="type">bool CDialog::Add(CWnd *control) { class="kw">return(m_client_area.Add(control)); } class="type">bool CDialog::Add(CWnd &control) { class="kw">return(m_client_area.Add(control)); } class="type">bool CRiskManagementPanel::CreateLabel(CLabel &label,const class="type">class="kw">string name, const class="type">class="kw">string text, const class="type">int x1, const class="type">int y1) { class=class="str">"cmt">//--- coordinates class="type">int x2=x1+class="num">50; class="type">int y2=y1+class="num">20; class=class="str">"cmt">//--- create if(!label.Create(m_chart_id,name+"Label",m_subwin,x1,y1,x2,y2)) class="kw">return(false); if(!label.Text(text)) class="kw">return(false); if(!Add(label)) class="kw">return(false); class=class="str">"cmt">//--- succeed class="kw">return(true); }
「风控面板的控件封装手法」
在 MT5 自定义指标或 EA 里做图形化风控面板,最烦的就是反复写控件创建代码。把按钮、编辑框、下拉框各自包一层成员函数,调用时只需传坐标和名称,能省掉大量重复逻辑。 下面这段 C++ 风格的 MQL5 类方法,分别封装了三种基础控件。按钮默认宽高走宏 BUTTON_WIDTH / BUTTON_HEIGHT,编辑框写死右侧偏移 +100 像素、高度用 EDIT_HEIGHT,下拉框则按 COMBO_BOX_WIDTH / COMBO_BOX_HEIGHT 计算右下角。
class="type">bool CRiskManagementPanel::CreateButton(CButton &buttom,const class="type">class="kw">string name, const class="type">class="kw">string text, const class="type">int x1, const class="type">int y1,class="type">int x2_= BUTTON_WIDTH,class="type">int y2_ = BUTTON_HEIGHT) { class="type">int x2=x1+x2_; class="type">int y2=y1+y2_; class=class="str">"cmt">//--- create if(!buttom.Create(m_chart_id,name,m_subwin,x1,y1,x2,y2)) class="kw">return(false); if(!buttom.Text(text)) class="kw">return(false); if(!Add(buttom)) class="kw">return(false); class=class="str">"cmt">//--- succeed class="kw">return(true); }
class="type">bool CRiskManagementPanel::CreateEdit(CEdit &m_edit, const class="type">class="kw">string name, const class="type">int x1, const class="type">int y1, class="type">class="kw">string initial_Text = "") { class=class="str">"cmt">//--- coordinates class="type">int y2=y1+EDIT_HEIGHT; class="type">int x2 =x1 +class="num">100; class=class="str">"cmt">//--- create if(!m_edit.Create(m_chart_id,name+"Edit",m_subwin,x1,y1,x2,y2)) class="kw">return(false); class=class="str">"cmt">//--- allow editing the content if(!m_edit.ReadOnly(false)) class="kw">return(false); if(!Add(m_edit)) class="kw">return(false); m_edit.Text(initial_Text); class=class="str">"cmt">//--- succeed class="kw">return(true); }
class="type">bool CRiskManagementPanel::CreateComboBox(CComboBox &combo_box, const class="type">class="kw">string name, class="type">class="kw">string &elements[], class="type">class="kw">string initial_text, const class="type">int x1, const class="type">int y1) { class="type">int x2 = x1 + COMBO_BOX_WIDTH; class="type">int y2 = y1 + COMBO_BOX_HEIGHT; if (!combo_box.Create(m_chart_id, name, m_subwin, x1, y1, x2, y2)) class="kw">return (false); for (class="type">int i = class="num">0; i < ArraySize(elements); i++) { if (!combo_box.AddItem(elements[i], i)) class="kw">return (false); } combo_box.SelectByText(initial_text); if (!Add(combo_box))
class="type">bool CRiskManagementPanel::CreateButton(CButton &buttom,const class="type">class="kw">string name, const class="type">class="kw">string text, const class="type">int x1, const class="type">int y1,class="type">int x2_= BUTTON_WIDTH,class="type">int y2_ = BUTTON_HEIGHT) { class="type">int x2=x1+x2_; class="type">int y2=y1+y2_; class=class="str">"cmt">//--- create if(!buttom.Create(m_chart_id,name,m_subwin,x1,y1,x2,y2)) class="kw">return(false); if(!buttom.Text(text)) class="kw">return(false); if(!Add(buttom)) class="kw">return(false); class=class="str">"cmt">//--- succeed class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to create edits | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CRiskManagementPanel::CreateEdit(CEdit &m_edit, const class="type">class="kw">string name, const class="type">int x1, const class="type">int y1, class="type">class="kw">string initial_Text = "") { class=class="str">"cmt">//--- coordinates class="type">int y2=y1+EDIT_HEIGHT; class="type">int x2 =x1 +class="num">100; class=class="str">"cmt">//--- create if(!m_edit.Create(m_chart_id,name+"Edit",m_subwin,x1,y1,x2,y2)) class="kw">return(false); class=class="str">"cmt">//--- allow editing the content if(!m_edit.ReadOnly(false)) class="kw">return(false); if(!Add(m_edit)) class="kw">return(false); m_edit.Text(initial_Text); class=class="str">"cmt">//--- succeed class="kw">return(true); } class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Function to create the complex object: combo box | class=class="str">"cmt">//| This function creates a combo box with multiple selectable items. | class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">bool CRiskManagementPanel::CreateComboBox(CComboBox &combo_box, const class="type">class="kw">string name, class="type">class="kw">string &elements[], class="type">class="kw">string initial_text, const class="type">int x1, const class="type">int y1) { class="type">int x2 = x1 + COMBO_BOX_WIDTH; class="type">int y2 = y1 + COMBO_BOX_HEIGHT; if (!combo_box.Create(m_chart_id, name, m_subwin, x1, y1, x2, y2)) class="kw">return (false); for (class="type">int i = class="num">0; i < ArraySize(elements); i++) { if (!combo_box.AddItem(elements[i], i)) class="kw">return (false); } combo_box.SelectByText(initial_text); if (!Add(combo_box))
◍ 组合框创建函数的真假返回
在 MT5 自定义控件里,组合框(ComboBox)的创建函数通常以布尔值收尾,用来告诉调用方控件是否真正建出来了。
上面的片段里,若前置条件不满足或资源申请失败,函数直接 return (false),相当于这次创建作废;走到注释 //--- successfully created the combo box 之后才 return (true),说明控件句柄、样式、子项都已就位。
你在 EA 或指标里调这类函数时,务必判断返回值:拿到 false 就别往下绑事件,否则后续对空组合框操作会抛句柄错误,MT5 终端日志里只能看到一串无效指针警告。
class="kw">return (false); class=class="str">"cmt">//--- successfully created the combo box class="kw">return (true); }
客户区三块面板的落地坐标与默认参数
风险与订单管理面板的客户区不是一整块,而是按功能切成三段:通用配置放每次操作风险%、偏差点数、止损限值;手数计算段按风险与止损距离反推仓位;最下方止损计算段按可接受亏损百分比倒推止损点。这样切,交易者改一处不会误触另一处逻辑。 CreateAreaClientPanel 这个函数把面板锚在图表左上角 (11,15) 像素,往下每 30 像素排一行,段间用 50 像素空开。偏差默认填 100 点、止损限值默认 50 点,这两个值直接写进类成员,没从界面读,意味着你若不改 edit 框,后台就按这俩跑。外汇和贵金属波动大,100 点偏差在黄金上可能瞬间被打掉,实盘前务必重设。 手数段默认 ORDER_TYPE_BUY,SL 编辑框留空等用户写,点 Save 才把计算锁进类里。下面这段代码就是面板初始化的骨架,逐行看坐标和默认值比读文档快。
class="type">bool CRiskManagementPanel::CreateAreaClientPanel(class="type">void) { class="type">int x1 = class="num">11; class=class="str">"cmt">// 面板左上角初始 X 坐标,像素 class="type">int y1 = class="num">15; class=class="str">"cmt">// 面板左上角初始 Y 坐标,像素 class=class="str">"cmt">// --- 通用段:每次操作风险配置 --- if (!CreateLabel(m_label_risk_per_operation, "L-Risk-Per-operation", "Risk per operation %: ", x1, y1)) class="kw">return false; class=class="str">"cmt">// 建风险百分比标签,失败则返回 if (!CreateEdit(m_edit_risk_per_operation, "Risk-Per-operation", x1 + class="num">150, y1)) class="kw">return false; class=class="str">"cmt">// 建风险百分比输入框,X 右移 class="num">150 像素 y1 += class="num">30; class=class="str">"cmt">// Y 坐标下移 class="num">30 像素排下一行 if (!CreateLabel(m_label_deviation, "L-Deviation", "Deviation(Points):", x1, y1)) class="kw">return false; class=class="str">"cmt">// 建偏差标签 if (!CreateEdit(m_edit_deviation, "Deviation", x1 + class="num">150, y1, "class="num">100")) class="kw">return false; class=class="str">"cmt">// 建偏差输入框,默认文本 class="num">100 点 this.deviation = class="num">100; class=class="str">"cmt">// 类内偏差成员默认 class="num">100 y1 += class="num">30; if (!CreateLabel(m_label_stop_limit, "L-StopLimit", "Stop Limit(Points):", x1, y1)) class="kw">return false; class=class="str">"cmt">// 建止损限值标签 if (!CreateEdit(m_edit_stop_limit, "Stop Limit", x1 + class="num">150, y1, "class="num">50")) class="kw">return false; class=class="str">"cmt">// 建止损限值输入框,默认文本 class="num">50 点 this.stop_limit = class="num">50; class=class="str">"cmt">// 类内止损限值成员默认 class="num">50 y1 += class="num">50; class=class="str">"cmt">// --- 手数计算段 --- if (!CreateLabel(m_label_get_lote, "L-Get-Lote-Title", "Get Lote", x1, y1)) class="kw">return false; class=class="str">"cmt">// 建手数段标题 if (!CreateComboBox(m_combobox_order_type_get_lot, "ORDER_TYPE_LOT", elements_order_type, "ORDER_TYPE_BUY", x1 + class="num">60, y1)) class="kw">return false; class=class="str">"cmt">// 建订单类型下拉框,默认买 this.order_type_get_lot = ORDER_TYPE_BUY; class=class="str">"cmt">// 类内订单类型默认买 y1 += class="num">30; if (!CreateLabel(m_label_sl, "L-SL", "SL Point: ", x1, y1)) class="kw">return false; class=class="str">"cmt">// 建止损点标签 if (!CreateEdit(m_edit_sl, "WRITE-SL", x1 + class="num">60, y1)) class="kw">return false; class=class="str">"cmt">// 建止损点输入框,空值 if (!CreateButton(m_button_get_lote, "GET-LOTE", "Save", x1 + class="num">160 + class="num">5, y1)) class="kw">return false; class=class="str">"cmt">// 建保存按钮,X 在输入框右 class="num">165 像素 }