风险管理(第二部分):在图形界面中实现手数计算·进阶篇
📘

风险管理(第二部分):在图形界面中实现手数计算·进阶篇

第 2/3 篇

「在 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 定初始项。

MQL5 / C++
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 两个工厂函数批量生成控件。外汇和贵金属波动大、杠杆高,这类面板只是把参数可视化,实际风控效果仍取决于你填的数值是否合理。

MQL5 / C++
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 跑一下,就能看到标签在图表左上角堆出来。

MQL5 / C++
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 计算右下角。

MQL5 / C++
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);
  }
逐行看:函数头定义按钮引用、名字、显示文字和左上角坐标,宽高给默认参数;x2/y2 由左上角加宽高得出右下角;buttom.Create 绑定到图表 ID 和子窗口;Text 设标签;Add 挂到面板容器,任何一步失败立即返回 false,全过才返回 true。
MQL5 / C++
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);
  }
编辑框这里 x2 固定 x1+100,名字自动拼 "Edit" 后缀避免和按钮重名;ReadOnly(false) 放开手动输入,最后才写初始文本,顺序反了可能覆盖不到。
MQL5 / C++
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))
下拉框靠 ArraySize 跑循环把字符串数组逐个 AddItem,索引用 i;SelectByText 按文本选中默认值,比按序号更不容易因数组变动点错。外汇与贵金属杠杆交易高风险,这类面板只辅助下单决策,不替代仓位计算。 开 MT5 新建 EA,把这三个函数塞进你的面板类,改下宏里的像素值就能直接跑出可交互界面。

MQL5 / C++
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 终端日志里只能看到一串无效指针警告。

MQL5 / C++
  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 才把计算锁进类里。下面这段代码就是面板初始化的骨架,逐行看坐标和默认值比读文档快。

MQL5 / C++
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 像素
}

常见问题

先声明私有成员保存控件指针,再用Create函数在客户区按坐标挂载三块面板,最后用标签和组合框填充内容即可。
把控件封装成函数,传入坐标和默认文本循环创建,用数组存指针方便后续读写数值。
小布可以读取你的品种页并解释面板各控件含义,还能提醒你手数计算的高风险点,省去自己翻代码。
多是坐标越界或父控件未先创建成功,检查客户区范围和调用顺序,确保父面板已挂载。
客户区左侧留100-200像素放面板,默认手数填1、止损点数填30,实测不遮挡主图且够用。