风险管理(第二部分):在图形界面中实现手数计算·综合运用
📘

风险管理(第二部分):在图形界面中实现手数计算·综合运用

第 3/3 篇

面板里手搭止损与仓位标签

这段代码片段来自一个风险管理面板的初始化逻辑,负责在 MT5 图表上逐行铺出“理想仓位”和“理想止损”两组界面元素。y1 坐标以 25、50、30 为步距累加,说明控件纵向排布留出了明确间隔,避免标签和按钮重叠。 先处理仓位显示:创建名为 L-Result-Lote 的标签,固定前缀文本 “Ideal Lot: ”,右侧再开一个空白标签 L-The-Result-lot 放计算结果,x 偏移 +65 像素。任何 CreateLabel 返回 false 就直接中断并返回 false,保证面板不全就整体不挂载。 止损区块从注释 // --- Stop Loss Calculation Section --- 开始。先建 “Get SL” 标题,紧跟一个下拉框 ORDER_TYPE_SL,默认塞入 ORDER_TYPE_BUY;随后 “Get ideal sl:” 标签配一个 “Get” 按钮(x+90),点击后由别处逻辑算值;最后 Ideal SL: 与空白结果标签同样错开 65 像素。外汇与贵金属波动剧烈,这类面板算出的“理想”值仅基于你设的风险参数,实盘止损可能被滑点击穿,属高风险操作。 最末的 Create() 函数把图表句柄、名称、子窗口与四角坐标传入,先调父类 CAppDialog::Create 再建客户区,两步任一失败即返回 false。复制这段代码到你的 EA 面板类里,把 x1/y1 起始值改成自己图表左边距,编译后能在 EURUSD 的 M5 上直接看到这套控件树。

MQL5 / C++
  y1 += class="num">25;
  if (!CreateLabel(m_label_result_lote, "L-Result-Lote", "Ideal Lot: ", x1, y1))
      class="kw">return false; class=class="str">"cmt">// Create the label for displaying the ideal lot
  if (!CreateLabel(m_label_the_result_lote, "L-The-Result-lot", "  ", x1 + class="num">65, y1))
      class="kw">return false; class=class="str">"cmt">// Create a label to display the calculated ideal lot
  y1 += class="num">50;
  class=class="str">"cmt">// --- Stop Loss Calculation Section ---
  if (!CreateLabel(m_label_get_sl, "L-Get-SL-Title", "Get SL", x1, y1))
      class="kw">return false; class=class="str">"cmt">// Create the label for stop loss calculation section
  if (!CreateComboBox(m_combobox_order_type_get_sl, "ORDER_TYPE_SL", elements_order_type, "ORDER_TYPE_BUY", x1 + class="num">50, y1))
      class="kw">return false; class=class="str">"cmt">// Create the combo box to select order type for stop loss calculation
  this.order_type_get_sl = ORDER_TYPE_BUY; class=class="str">"cmt">// Default order type
  y1 += class="num">30;
  if (!CreateLabel(m_label_lote, "L-LOTE", "Get ideal sl:", x1, y1))
      class="kw">return false; class=class="str">"cmt">// Create the label for getting the ideal stop loss
  if (!CreateButton(m_button_get_sl, "GET-SL", "Get", x1 + class="num">90, y1))
      class="kw">return false; class=class="str">"cmt">// Create the button to get the stop loss value
  y1 += class="num">25;
  if (!CreateLabel(m_label_result_sl, "L-Result-sl", "Ideal SL:", x1, y1))
      class="kw">return false; class=class="str">"cmt">// Create the label for displaying the ideal stop loss
  if (!CreateLabel(m_label_the_result_sl, "L-The-result-sl", "  ", x1 + class="num">65, y1))
      class="kw">return false; class=class="str">"cmt">// Create a label to display the calculated ideal stop loss
  class="kw">return true; class=class="str">"cmt">// If all components are successfully created
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| function to create the interface                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CRiskManagementPanel::Create(const class="type">long chart,const class="type">class="kw">string name,const class="type">int subwin,const class="type">int x1,const class="type">int y1,const class="type">int x2,const class="type">int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(false);
   if(!CreateAreaClientPanel())
      class="kw">return(false);
class=class="str">"cmt">//--- succeed
   class="kw">return(true);
  }

「让面板标签和组合框跟着计算值走」

风控面板的易用性,一半靠动态刷新。用户切一次组合框,标签里的手数和止损就得立刻变,否则算得再准也没人信。下面这组成员函数就干这件事:把内部变量和界面控件绑起来。 EditLabelResultLote 和 EditLabelResultSL 是最轻量的写标签接口,各接收一个 string,直接喂给对应的标签控件。你可以把任何格式化后的数字(比如 "0.32" 或 "125.4")传进去,界面不闪、不卡。 组合框回调才是重点。OnChangeComBoxOrderTypeGetSL 在用户改「取SL用的订单类型」时触发,它遍历 elements_order_type 数组,用 m_combobox_order_type_get_sl.Select() 拿到当前文本,命中后调用 StringOrderTypeToEnum 写进 order_type_get_sl,并 Print 一条日志方便你在专家选项卡里核对。OnChangeComBoxOrderTypeGetLote 逻辑完全一致,只是目标变量换成 order_type_get_lot。 实盘里外汇和贵金属波动大、滑点随机,这类界面同步函数不牵涉下单,但能让你少看错一行数字。建议在 MT5 里接上日志后,手动切 5~6 次组合框,确认 Print 输出的枚举和标签文本一一对应再接计算核心。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to edit the text of the lot size label                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRiskManagementPanel::EditLabelResultLote(class="type">class="kw">string text)
{
  class=class="str">"cmt">// This function updates the text of the label that shows the ideal lot size.
  this.m_label_the_result_lote.Text(text); class=class="str">"cmt">// Set the new text to the label
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to edit the text of the stop loss label                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRiskManagementPanel::EditLabelResultSL(class="type">class="kw">string text)
{
  class=class="str">"cmt">// This function updates the text of the label that shows the ideal stop loss value.
  this.m_label_the_result_sl.Text(text); class=class="str">"cmt">// Set the new text to the stop loss label
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to update the variable that stores                      |
class=class="str">"cmt">//| the order type to obtain the ideal sl                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRiskManagementPanel::OnChangeComBoxOrderTypeGetSL(class="type">void)
{
  class=class="str">"cmt">// Iterate through the order types array to find the selected type
  for(class="type">int i = class="num">0; i < ArraySize(elements_order_type); i++)
  {
    class=class="str">"cmt">// If the selected order type matches the one in the array
    if(m_combobox_order_type_get_sl.Select() == elements_order_type[i])
    {
      class=class="str">"cmt">// Update the order type variable for stop loss
      this.order_type_get_sl = StringOrderTypeToEnum(m_combobox_order_type_get_sl.Select());
      Print("New order type for sl: ", EnumToString(this.order_type_get_sl)); class=class="str">"cmt">// Log the selected order type
      break;
    }
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to update the variable that stores                      |
class=class="str">"cmt">//| the order type to obtain the ideal lot                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CRiskManagementPanel::OnChangeComBoxOrderTypeGetLote(class="type">void)
{
  class=class="str">"cmt">// Iterate through the order types array to find the selected type
  for(class="type">int i = class="num">0; i < ArraySize(elements_order_type); i++)
  {
    class=class="str">"cmt">// If the selected order type matches the one in the array
    if(m_combobox_order_type_get_lot.Select() == elements_order_type[i])
    {
      class=class="str">"cmt">// Update the order type variable for lot size
      this.order_type_get_lot = StringOrderTypeToEnum(m_combobox_order_type_get_lot.Select());

◍ 把面板字符串映射回订单枚举

风控面板里下拉框选中的订单类型通常以字符串形式暂存,真正下发指令前必须还原成 MQL5 的 ENUM_ORDER_TYPE,否则 OrderSend 会直接拒单。 下面这段成员函数接收 const string 入参,逐条比对 8 种常见挂单与市价单字符串,命中即返回对应枚举;若全部不匹配则返回 WRONG_VALUE,调用方应据此拦截非法输入。 注意 WRONG_VALUE 在 MQL5 标准头中定义为 -1(UINT 上下文里是 0xFFFFFFFF),用它做哨兵值比返回 0 安全,因为 0 可能与其他隐式类型混淆。外汇与贵金属杠杆高,枚举错配会导致实际开仓方向偏离预期,复盘时务必先打印 OrderType 再发单。

MQL5 / C++
ENUM_ORDER_TYPE CRiskManagementPanel::StringOrderTypeToEnum(const class="type">class="kw">string OrderType)
{
  if(OrderType == "ORDER_TYPE_BUY")
    class="kw">return ORDER_TYPE_BUY;
  if(OrderType == "ORDER_TYPE_SELL")
    class="kw">return ORDER_TYPE_SELL;
  if(OrderType == "ORDER_TYPE_BUY_LIMIT")
    class="kw">return ORDER_TYPE_BUY_LIMIT;
  if(OrderType == "ORDER_TYPE_SELL_LIMIT")
    class="kw">return ORDER_TYPE_SELL_LIMIT;
  if(OrderType == "ORDER_TYPE_BUY_STOP")
    class="kw">return ORDER_TYPE_BUY_STOP;
  if(OrderType == "ORDER_TYPE_SELL_STOP")
    class="kw">return ORDER_TYPE_SELL_STOP;
  if(OrderType == "ORDER_TYPE_BUY_STOP_LIMIT")
    class="kw">return ORDER_TYPE_BUY_STOP_LIMIT;
  if(OrderType == "ORDER_TYPE_SELL_STOP_LIMIT")
    class="kw">return ORDER_TYPE_SELL_STOP_LIMIT;
  class="kw">return WRONG_VALUE;
}

用自定义事件接管面板按键

在 MQL5 控件库里,面板按钮的响应不走 CHARTEVENT_OBJECT_CLICK 这类默认事件,而是走项目 Includes\Controls\Defines.mqh 里自己定义的事件枚举。这个文件除了放控件配色等默认值,末尾集中声明了所有交互事件,是我们接管键盘与按钮逻辑的总入口。 写 OnChartEvent 之前得先认清楚这点:类里声明的 OnChartEvent 方法,最终要调起上一节那几个算手数、算止损、刷编辑框的函数,但触发源不是系统默认点击,而是 Defines.mqh 映射出来的自定义事件ID。 打开 Includes\Controls\Defines.mqh 翻到文件末尾,你能直接看到事件枚举的声明结构;改面板行为时,只动这里和对应的分发逻辑即可,别去碰 CHARTEVENT_OBJECT_CLICK 那条老路,否则事件会重复触发、编辑框被写两遍。外汇与贵金属品种波动大,面板算出的手数仅作参考,实盘前务必在 MT5 策略测试器里用历史 tick 验一遍事件分发是否漏触发。

「面板控件的三类关键事件怎么接」

在 MT5 自定义面板里,Defines.mqh 用宏把控件事件编号定死了,但实战中真正要盯的只有三个:ON_CLICK(按钮被点)、ON_CHANGE(下拉框值变)、ON_END_EDIT(编辑框输完)。它们各自对应 OnChartEvent 收到的 id 参数,且都必须叠加 CHARTEVENT_CUSTOM 才构成唯一标识,否则会跟系统图表事件撞车。 OnEvent 作为统一入口,拿到 id、lparam、dparam、sparam 四个参数。lparam 一般装控件 ID,这是判断“到底哪个组合框或按钮触发”的唯一依据;例如手数类型组合框变化时用 lparam == m_combobox_order_type_get_lot.Id() 拦截,再去调 OnChangeComBoxOrderTypeGetLote 更新内部 ENUM_ORDER_TYPE。 编辑框走的是 ON_END_EDIT 逻辑。用户填完“每次操作风险”“止损”等字段,if 里比对待 lparam 与控件 ID,命中就把字符串转成 double 或 int 并做规范化,内部变量才刷新——中途编辑不触发,能少算很多无效值。 按钮点击用 ON_CLICK 捕获。m_button_get_lote 按下就跑 GetIdealLot,m_button_get_sl 按下跑 CalculateSL,结果写进 EditLabelResultLote / EditLabelResultSL,随后 m_button_get_lote.Pressed(false) 把按钮弹起。外汇与贵金属波动剧烈,这类自动算出的手数、止损仅作参考,实盘仍可能因滑点偏离。 最后务必把事件回传给基类 CAppDialog,否则未被显式处理的焦点、拖拽等事件会丢。下面这段宏定义可直接抄进 Defines.mqh 对照看,ON_CLICK 是 0、ON_CHANGE 是 4、ON_END_EDIT 是 6,其余编号留着扩展用。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Events                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define ON_CLICK(class="num">0)    class=class="str">"cmt">// clicking on control event
class="macro">#define ON_DBL_CLICK(class="num">1)    class=class="str">"cmt">// class="type">class="kw">double clicking on control event
class="macro">#define ON_SHOW(class="num">2)    class=class="str">"cmt">// showing control event
class="macro">#define ON_HIDE(class="num">3)    class=class="str">"cmt">// hiding control event
class="macro">#define ON_CHANGE(class="num">4)    class=class="str">"cmt">// changing control event
class="macro">#define ON_START_EDIT(class="num">5)    class=class="str">"cmt">// start of editing event
class="macro">#define ON_END_EDIT(class="num">6)    class=class="str">"cmt">// end of editing event
class="macro">#define ON_SCROLL_INC(class="num">7)    class=class="str">"cmt">// increment of scrollbar event
class="macro">#define ON_SCROLL_DEC(class="num">8)    class=class="str">"cmt">// decrement of scrollbar event
class="macro">#define ON_MOUSE_FOCUS_SET(class="num">9)    class=class="str">"cmt">// the "mouse cursor entered the control" event
class="macro">#define ON_MOUSE_FOCUS_KILL(class="num">10)   class=class="str">"cmt">// the "mouse cursor exited the control" event
class="macro">#define ON_DRAG_START(class="num">11)   class=class="str">"cmt">// the "control dragging start" event
class="macro">#define ON_DRAG_PROCESS(class="num">12)   class=class="str">"cmt">// the "control is being dragged" event
class="macro">#define ON_DRAG_END(class="num">13)   class=class="str">"cmt">// the "control dragging end" event
class="macro">#define ON_BRING_TO_TOP(class="num">14)   class=class="str">"cmt">// the "mouse events priority increase" event
class="macro">#define ON_APP_CLOSE(class="num">100)  class=class="str">"cmt">// "closing the application" event
class=class="str">"cmt">//+------------------------------------------------------------------+
if(id == ON_CLICK + CHARTEVENT_CUSTOM && lparam == m_button_get_lote.Id())
{

◍ 风控面板的事件分发与仓位反推

在 MT5 自建 GUI 风控面板里,所有控件交互都收敛到 CRiskManagementPanel::OnEvent 这一个入口。它靠 id 与 lparam 区分事件类型:ON_CHANGE 管下拉框切换,ON_END_EDIT 管输入框落定,ON_CLICK 管按钮触发,三者都叠加 CHARTEVENT_CUSTOM 偏移以避开系统事件冲突。 风险额输入框的处理最值得盯:文本先转 double,再除以 100 乘账户余额,最后 NormalizeDouble 到 2 位小数。比如余额 10000、填 2 代表 2%,算出来 risk_per_operation = 200.00,直接 Print 到日志便于核对。 点“获取手数”按钮时,面板会调 GetIdealLot,把算好的风险额、SL 点数、订单类型丢进去,反推出 new_lot,并用 PrintFormat 打出“若失败亏损金额”的实测数值。外汇与贵金属杠杆高,SL 点数和手数错配可能在几秒内吞掉本金,参数务必先在模拟盘验证。 “获取 SL”按钮走另一条路:CalculateSL 根据风险额和当前偏离、stop_limit 反推理想止损点数,返回 long 型 new_sl 并写回标签。这段逻辑让你不必手算点位,但底层假设滑点可控,极端行情可能偏离。

MQL5 / C++
class="type">bool CRiskManagementPanel::OnEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam)
if(id == ON_CHANGE + CHARTEVENT_CUSTOM && lparam == m_combobox_order_type_get_lot.Id())
{
   OnChangeComBoxOrderTypeGetLote();
}
if(id == ON_CHANGE + CHARTEVENT_CUSTOM && lparam == m_combobox_order_type_get_sl.Id())
{
   OnChangeComBoxOrderTypeGetSL();
}
if(id == ON_END_EDIT + CHARTEVENT_CUSTOM && lparam == m_edit_risk_per_operation.Id())
{
   this.risk_per_operation = StringToDouble(m_edit_risk_per_operation.Text());
   this.risk_per_operation = NormalizeDouble((this.risk_per_operation / class="num">100.0) * AccountInfoDouble(ACCOUNT_BALANCE), class="num">2);
   Print("Edit Risk Per Operation: ", this.risk_per_operation);
}
if(id == ON_END_EDIT + CHARTEVENT_CUSTOM && lparam == m_edit_sl.Id())
{
   this.sl = StringToInteger(m_edit_sl.Text());
   Print("Edit SL: ", this.sl);
}
if(id == ON_END_EDIT + CHARTEVENT_CUSTOM && lparam == m_edit_deviation.Id())
{
   this.deviation = StringToDouble(m_edit_deviation.Text());
   Print("Edit Deviation: ", this.deviation);
}
if(id == ON_END_EDIT + CHARTEVENT_CUSTOM && lparam == m_edit_stop_limit.Id())
{
   this.stop_limit = StringToDouble(m_edit_stop_limit.Text());
   Print("Edit Stop Limit: ", this.stop_limit);
}
if(id == ON_CLICK + CHARTEVENT_CUSTOM && lparam == m_button_get_lote.Id())
{
   Print("Risk Per operation: ", this.risk_per_operation);
   Print("SL in points: ", this.sl);
   Print("Order type get lot: ", EnumToString(this.order_type_get_lot));
   class="type">class="kw">double new_lot;
   class="type">class="kw">double new_risk_per_operation;
   GetIdealLot(new_lot, GetMaxLote(this.order_type_get_lot), this.risk_per_operation, new_risk_per_operation, this.sl);
   PrintFormat("Loss in case the following operation fails, with the parameters: lot %.2f and stop loss of %i points will be %.2f ", new_lot, this.sl, new_risk_per_operation);
   EditLabelResultLote(DoubleToString(new_lot, class="num">2));
   m_button_get_lote.Pressed(false);
}
if(id == ON_CLICK + CHARTEVENT_CUSTOM && lparam == m_button_get_sl.Id())
{
   Print("Risk Per operation: ", this.risk_per_operation);
   Print("Order type get sl: ", EnumToString(this.order_type_get_lot));
   class="type">class="kw">double new_lot;
   class="type">long new_sl = CalculateSL(this.order_type_get_sl, this.risk_per_operation, new_lot, this.deviation, this.stop_limit);
   PrintFormat("For the risk per operation %.2f the chosen lot is %.2f and the ideal stop loss in points is %i", this.risk_per_operation, new_lot, new_sl);
   EditLabelResultSL(IntegerToString(new_sl));

按钮状态复位与事件回传

在自定义对话框里处理完按钮逻辑后,必须把 m_button_get_sl 的按压态清掉,否则界面会卡在“已按下”的视觉状态,下次点击可能不触发新事件。 代码中用 m_button_get_sl.Pressed(false) 显式复位,这一行紧接在业务分支结束的花括号之后,确保无论走哪个分支都能执行到。 最后 return CAppDialog::OnEvent(id, lparam, dparam, sparam) 把未消费的事件交回基类,保证对话框其他控件照常工作。外汇与贵金属面板开发属高风险定制,建议在 MT5 策略测试器以外先用实盘模拟账户验证按钮交互。

MQL5 / C++
   m_button_get_sl.Pressed(false);
}
class="kw">return(CAppDialog::OnEvent(id, lparam, dparam, sparam));

「把风控面板挂进EA生命周期」

要在MT5机器人里跑一个带界面的风险管理模块,第一步是在全局区把自定义类对象放出来。这样OnInit、OnDeinit和OnChartEvent三个系统函数都能直接摸到同一块面板实例,不用反复new。 OnInit里调用Create做实体化:图表ID填0代表主窗口,标题写"Test Risk Management",子窗口参数0(EA环境没有独立副图),左上角坐标(40,40),宽高380×380像素。若Create返回false,立刻return(INIT_FAILED),MT5日志会报初始化失败,机器人不会瞎跑。 面板活起来靠Run(),它把按钮、输入框这些控件的交互不挂起。退场时OnDeinit必须调Destroy,否则残留图形对象可能赖在图表上,下次加载EA容易叠层错位。 用户点按钮或改文本框,事件不会自动进面板——得在OnChartEvent里把id/lparam/dparam/sparam原样转交给risk_panel.ChartEvent。外汇与贵金属杠杆高,这类界面只是参数录入前端,实际下单前仍需人工核对止损位。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global Variables                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CRiskManagementPanel risk_panel; class=class="str">"cmt">// Declare the panel object globally
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class=class="str">"cmt">//--- Create the risk management panel
   if(!risk_panel.Create(class="num">0, "Test Risk Management", class="num">0, class="num">40, class="num">40, class="num">380, class="num">380)) 
      class="kw">return(INIT_FAILED); class=class="str">"cmt">// If panel creation fails, class="kw">return an error code
   
   class=class="str">"cmt">//--- Run the panel
   risk_panel.Run(); class=class="str">"cmt">// Start the graphical interface
   
   class=class="str">"cmt">//--- Return success
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   class=class="str">"cmt">//--- Destroy the risk management panel
   risk_panel.Destroy(reason); class=class="str">"cmt">// Free resources and remove the panel
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert chart event function                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,        class=class="str">"cmt">// Event ID
                  const class="type">long& lparam,  class=class="str">"cmt">// Event parameter of type class="type">long
                  const class="type">class="kw">double& dparam, class=class="str">"cmt">// Event parameter of type class="type">class="kw">double
                  const class="type">class="kw">string& sparam) class=class="str">"cmt">// Event parameter of type class="type">class="kw">string
  {
   class=class="str">"cmt">//--- Pass the chart event to the risk panel for processing
   risk_panel.ChartEvent(id, lparam, dparam, sparam);
  }

◍ 用 XAUUSD 实盘面板跑一遍手数计算

把 EA 加载到 XAUUSD(黄金)图表上,先确认账户没有未平仓位,这样面板输出不会被旧持仓干扰。调试阶段建议把账户余额、风险字段都摊开看,避免隐形参数吃掉了你的真实敞口。 在“每次操作风险”填 3%(按当前余额计),顺手把偏差和止损限额的编辑框也过一遍。选“根据止损点数算手数”,订单类型挑 ORDER_TYPE_BUY,止损给 500 点,点“保存”。面板回 0.03 手,专家选项卡补了一句:最大手数 0.45、风险 3.0%、若按此手数和止损亏掉是 15.45 美元。 再测另一条路——按每次操作风险反推止损。点“获取理想止损”那行的“获取”,出来止损位 1856 点,理想交易量 0.01 手,风险仍是 3.0%。黄金波动大、杠杆高,这类数字仅代表当下参数下的数学结果,实际滑点可能让亏损偏离。 开 MT5 把这两个按钮各点一次,比对你的账户余额算出的手数是否和面板一致;不一致就查偏差字段是不是被改过。

画得少,看得清

这套风险管理面板把最常被手工算错的两件事——手数和止损点位——收进了一个 EA 内嵌界面。核心逻辑落在 Risk_Management.mqh 里,EA 文件 Risk_Management_Panel.mq5(22.34 KB)只负责把编辑框、组合框和按钮的事件接到计算函数上,没有多余装饰。 从工程角度看,MQL5 的 CWndContainer 继承体系让自定义面板可以随窗口重绘自动布局,比早期用 OBJ_RECTANGLE 硬拼坐标稳得多。你打开 MT5 把两个文件丢进 Include 与 Experts 目录编译,就能在图表右侧看到可输入余额、风险百分比和止损距离的实时手数反馈。 外汇与贵金属杠杆高、滑点随机,任何面板算出的数值都只是概率参考,真下单前仍要核对点值和合约规模。下一步若想扩成多品种监控,直接在主类里复用这里的 CComboBox 品种枚举即可,不必重写布局代码。

常见问题

在图形面板放可编辑的止损输入框和手数标签,输入止损价后按风控公式反推手数并写回标签,外汇贵金属波动大需先确认账户杠杆与品种点值。
需在组合框的选中事件里重算点值与止损映射,并主动刷新标签文本;若没接选中事件,控件不会自动同步。
可以,小布盯盘已内置仓位与止损反推,打开对应品种页会自动给出现价下的建议手数与风控线,你只需核对账户设置。
给按钮绑自定义事件ID,在主循环里分发该事件到对应处理函数,注意区分点击、失去焦点、值变更三类事件避免漏接。
用仓位反推公式由手数与风险金额倒算止损点数,再映射回面板字符串显示;高风险品种反推前先校验最小止损限制。