快捷手动交易工具箱:基本功能·综合运用
🧰

快捷手动交易工具箱:基本功能·综合运用

(3/3)·前面两篇铺垫了动机与架构,这一篇把 34 个环节串成能直接编译进 MT5 的实战工具箱

案例拆解 第 3/3 篇
很多手动交易者嘴上说要快,真到剥头皮行情里还在终端菜单里点三层才平仓,几秒之差就是利润变止损。把常用动作绑到单键和百分比手数,不是花活,是减少操作链路上的人为延迟。本篇把前面拆开的概念收拢成一套能直接跑的工具箱。

用键盘和按钮接管下单窗口

在 MT5 自定义面板里,图表事件是最直接的交易入口。下面这段类方法把「点按钮」和「敲键盘」都映射到具体窗口动作,省去鼠标在菜单里翻找的延迟。 事件分两类:自定义按钮点击走 CHARTEVENT_CUSTOM+ON_CLICK_BUTTON,键盘敲击走 CHARTEVENT_KEYDOWN。按钮 0 和 1 分别对应市价单与挂单窗口的 OpenWindow(),靠 lparam 比对的控件 Id 区分。 键盘映射有三个键:M 开市价单窗口(若挂单窗口开着先关掉),P 开挂单窗口(若市价单窗口开着先关掉),ESC 则谁可见关谁。这种互斥打开逻辑能避免两个下单框叠在一起挡价格。 外汇与贵金属杠杆高,快捷键下单快但滑点风险同步放大,实盘前务必在策略测试器用模拟品种把按键响应跑通。

MQL5 / C++
class="type">void CFastTrading::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam)
{
class=class="str">"cmt">//--- 按下按钮事件
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
   class=class="str">"cmt">//---
      if(lparam==m_order_button[class="num">0].Id())
         m_orders_windows[class="num">0].OpenWindow();
   class=class="str">"cmt">//---
      if(lparam==m_order_button[class="num">1].Id())
         m_orders_windows[class="num">1].OpenWindow();
   }
class=class="str">"cmt">//--- 按键事件
   if(id==CHARTEVENT_KEYDOWN)
   {
   class=class="str">"cmt">//--- 打市价单窗口
      if(lparam==KEY_M)
      {
         if(m_orders_windows[class="num">0].IsVisible())
         {
            m_orders_windows[class="num">0].CloseDialogBox();
         }
         else
         {
            if(m_orders_windows[class="num">1].IsVisible())
            {
               m_orders_windows[class="num">1].CloseDialogBox();
            }
            class=class="str">"cmt">//---
            m_orders_windows[class="num">0].OpenWindow();
         }
      }
   class=class="str">"cmt">//--- 开挂单窗口
      if(lparam==KEY_P)
      {
         if(m_orders_windows[class="num">1].IsVisible())
         {
            m_orders_windows[class="num">1].CloseDialogBox();
         }
         else
         {
            if(m_orders_windows[class="num">0].IsVisible())
            {
               m_orders_windows[class="num">0].CloseDialogBox();
            }
            class=class="str">"cmt">//---
            m_orders_windows[class="num">1].OpenWindow();
         }
      }
   }
class=class="str">"cmt">//--- 已存在订单窗口
   if(lparam==KEY_ESC)
   {
      if(m_orders_windows[class="num">0].IsVisible())
      {
         m_orders_windows[class="num">0].CloseDialogBox();
      }
      else if(m_orders_windows[class="num">1].IsVisible())
      {
         m_orders_windows[class="num">1].CloseDialogBox();
      }
   }
}

◍ 一键下单面板的框架与标签生成

在 MT5 自建交易面板时,CFastTrading 类用 CreateFrame 把买/卖模块画进窗口。函数先按 caption 判断是 Buy 还是 Sell,再分别上色:买框用 C'88,212,210'(青绿),卖框用 C'236,85,79'(红),背景统一走 m_background_color。 框架固定高 110 像素,开启 AutoXResizeMode 且右留白 10,保证窗口拉伸时模块跟着自适应。实际挂模块时,买入框 y_gap=20、卖出框 y_gap=160,两者 x_gap 都是 10,意味着卖块在买块下方约 140 像素处排开。 文本标签走 CreateLabel,宽锁 80、文字居中(IsCenterText=true),字体继承 m_base_font。下面这段是核心函数与调用,逐行拆完你就能直接抄进 EA 面板工程。 别把颜色硬编码当终点:RUSSIAN 宏只是切换 caption 文案,色值仍写死,多语种面板要另接调色板。

MQL5 / C++
class="type">bool CreateFrame(CWindow &window,CFrame &frame,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">class="kw">string caption,class="type">int w_number);

class="type">bool CFastTrading::CreateFrame(CWindow &window,CFrame &frame,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">class="kw">string caption,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储指向主控件的指针
  frame.MainPointer(window);
class=class="str">"cmt">//---
  class="type">color clrmain=clrNONE;
  if(caption==BUY_ORDER)
    clrmain=C&class="macro">#x27;class="num">88,class="num">212,class="num">210&class="macro">#x27;;
  else if(caption==SELL_ORDER)
    clrmain=C&class="macro">#x27;class="num">236,class="num">85,class="num">79&class="macro">#x27;;
class=class="str">"cmt">//---
  frame.YSize(class="num">110);
  frame.LabelColor(clrmain);
  frame.BorderColor(clrmain);
  frame.BackColor(m_background_color);
  frame.GetTextLabelPointer().BackColor(m_background_color);
  frame.Font(m_base_font);
  frame.FontSize(m_base_font_size);
  frame.AutoXResizeMode(true);
  frame.AutoXResizeRightOffset(class="num">10);
class=class="str">"cmt">//--- 创建一个控制元素
  if(!frame.CreateFrame(caption,x_gap,window.CaptionHeight()+y_gap))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组
  CWndContainer::AddToElementsArray(w_number,frame);
  class="kw">return(true);
}
class="macro">#define BUY_ORDER(m_language==RUSSIAN ? "Buy-ордер" : "Buy-order")
class="macro">#define SELL_ORDER(m_language==RUSSIAN ? "Sell-ордер" : "Sell-order")

class=class="str">"cmt">//--- 创建窗体
  if(!m_orders_windows[class="num">0].CreateWindow(m_chart_id,m_subwin,CAPTION_M_ORD_NAME,x,y))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 买入模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">0],class="num">10,class="num">20,BUY_ORDER,class="num">1))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 卖出模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">1],class="num">10,class="num">160,SELL_ORDER,class="num">1))
    class="kw">return(class="kw">false);
  class="kw">return(true);
}

class="type">bool CFastTrading::CreateLabel(CWindow &window,CTextLabel &text_label,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">class="kw">string label_text,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储窗口指针
  text_label.MainPointer(window);
class=class="str">"cmt">//---
  text_label.Font(m_base_font);
  text_label.FontSize(m_base_font_size);
  text_label.XSize(class="num">80);
  text_label.BackColor(m_background_color);
  text_label.IsCenterText(true);
class=class="str">"cmt">//--- 创建标签
  if(!text_label.CreateTextLabel(label_text,x_gap,window.CaptionHeight()+y_gap))
    class="kw">return(class="kw">false);

「买卖面板与多语标签的布局硬编码」

这段逻辑把交易面板的买卖两个模块直接写死在坐标上:买入框左上角定在 (10,20),卖出框下移到 (10,160),两者垂直间距 140 像素。标签列则按 20 左缩进、每列宽 80 加间距 20 递推,LOT/TP/SL 三列 x 坐标分别是 20、120、220。 宏定义里用 m_language 切换俄语和英文,俄语下 TP 显示「Тейк профит」、SL 显示「Стоп лосс」,英文则缩写。注意这些宏在 CreateLabel 调用之后才 #define,实际编译顺序依赖头文件展开位置,复制代码时若把片段单独拎出来会报未定义。 CreateSwitchButton 里按钮尺寸锁死为 80×24,配色用 clrSlateBlue 常态、clrIndigo 按下态。想改面板视觉密度,直接调 XSize/YSize 和那两个 color 变量比改坐标更快。外汇与贵金属 MT5 面板开发属高风险定制,参数误写可能导致下单界面错位。 别把坐标当自适应 上面所有 x/y 都是绝对值,换分辨率或 DPI 缩放后面板可能溢出窗口。验证办法:在 MT5 里把图表拖到 4K 屏,看卖出模块是否还完整可见。

MQL5 / C++
class=class="str">"cmt">//--- 向数据库添加一个指向元素的指针
  CWndContainer::AddToElementsArray(w_number,text_label);
  class="kw">return(true);
}
class=class="str">"cmt">//--- 买入模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">0],class="num">10,class="num">20,BUY_ORDER,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">0],class="num">20,class="num">30,LOT,class="num">1))
      class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">1],class="num">20+class="num">80+class="num">20,class="num">30,TP,class="num">1))
      class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">30,SL,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 卖出模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">1],class="num">10,class="num">160,SELL_ORDER,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">3],class="num">20,class="num">170,LOT,class="num">1))
      class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">4],class="num">20+class="num">80+class="num">20,class="num">170,TP,class="num">1))
      class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">5],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170,SL,class="num">1))
      class="kw">return(class="kw">false);
  class="kw">return(true);
}
class="macro">#define LOT(m_language==RUSSIAN ? "Лот" : "Lot")
class="macro">#define TP(m_language==RUSSIAN ? "Тейк профит" : "Take Profit")
class="macro">#define SL(m_language==RUSSIAN ? "Стоп лосс" : "Stop Loss")
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::CreateSwitchButton(CWindow &window,CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储窗口指针
  button.MainPointer(window);
  class="type">color baseclr=clrSlateBlue;
  class="type">color pressclr=clrIndigo;
class=class="str">"cmt">//--- 在创建之前设置属性
  button.XSize(class="num">80);
  button.YSize(class="num">24);
  button.Font(m_base_font);
  button.FontSize(m_base_font_size);

买卖面板按钮与标签的布局落点

在 MT5 自定义面板里,按钮的三态配色得一次性写死,否则 hover 和按下时闪色会显得很业余。下面这段把背景、边框、文字都绑定到 baseclr 和 pressclr,文字恒定白色且居中,同时开启 TwoState 让按钮具备开关属性。 button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); button.TwoState(true); 创建完后用 CreateButton 落到坐标 (x_gap, window.CaptionHeight()+y_gap),失败直接 return(false),成功再通过 CWndContainer::AddToElementsArray 把指针存进元素数组,后面事件分发才好找。 买卖模块用循环批量铺切换键最省事:买入区 y=60,三个按钮 x 分别是 20、120、220(步长 80+20);卖出区整体下移,y 从 170 起,按钮 y 取 200,索引 i 从 3 到 5 复用同一函数。标签 LOT/TP/SL 也按 20+(80+20)*n 的算法排,肉眼看是对齐的。 外汇与贵金属界面自动化改参数属于高风险操作,面板逻辑错误可能发出非预期下单指令,实盘前务必在策略测试器里用模拟账户跑通。

MQL5 / C++
  button.BackColor(baseclr);
  button.BackColorHover(baseclr);
  button.BackColorPressed(pressclr);
  button.BorderColor(baseclr);
  button.BorderColorHover(baseclr);
  button.BorderColorPressed(pressclr);
  button.LabelColor(clrWhite);
  button.LabelColorPressed(clrWhite);
  button.LabelColorHover(clrWhite);
  button.IsCenterText(true);
  button.TwoState(true);
class=class="str">"cmt">//--- 创建一个控制元素
  if(!button.CreateButton(text,x_gap,window.CaptionHeight()+y_gap))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 向数据库添加一个指向元素的指针
  CWndContainer::AddToElementsArray(w_number,button);
  class="kw">return(true);
}
...
class=class="str">"cmt">//--- 买入模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">0],class="num">10,class="num">20,BUY_ORDER,class="num">1))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">0],class="num">20,class="num">30,LOT,class="num">1))
    class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">1],class="num">20+class="num">80+class="num">20,class="num">30,TP,class="num">1))
    class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">30,SL,class="num">1))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换按钮
  for(class="type">int i=class="num">0; i<class="num">3; i++)
    if(!CreateSwitchButton(m_orders_windows[class="num">0],m_switch_button[i],"-",class="num">20+(class="num">80+class="num">20)*i,class="num">60,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 卖出模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">1],class="num">10,class="num">160,SELL_ORDER,class="num">1))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">3],class="num">20,class="num">170,LOT,class="num">1))
    class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">4],class="num">20+class="num">80+class="num">20,class="num">170,TP,class="num">1))
    class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">5],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170,SL,class="num">1))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换按钮
  for(class="type">int i=class="num">3; i<class="num">6; i++)
    if(!CreateSwitchButton(m_orders_windows[class="num">0],m_switch_button[i],"-",class="num">20+(class="num">80+class="num">20)*(i-class="num">3),class="num">170+class="num">30,class="num">1))
      class="kw">return(class="kw">false);
  class="kw">return(true);
}

◍ 手搓交易面板的手数框与止盈框

在 MT5 自定义面板里,手数和止盈不是随便写个输入框就完事,边界值必须跟品种属性或业务规则绑死。下面两段函数分别构建了手数编辑框和止盈编辑框,可直接拷进你的 CWindow 派生类复用。 手数框的关键在动态边界:MaxValue 取 SYMBOL_VOLUME_MAX,MinValue 取 SYMBOL_VOLUME_MIN,StepValue 取 SYMBOL_VOLUME_STEP,这样无论切到欧美还是 XAUUSD,旋转调节都不会越界。初始化时直接塞入 SYMBOL_VOLUME_MIN 字符串,框体宽 80、高 24,字体走基类 m_base_font。 止盈框则是写死规则:最大值 9999、步长 1、最小值 0,默认填 150 点。注意这里没接 SymbolInfoDouble 的止损点位精度,若做贵金属短线,150 点可能偏小,按品种改默认值更稳。 两个框都开了 SpinEditMode(true) 支持上下微调,最后用 CWndContainer::AddToElementsArray 挂进元素数组统一管理。外汇与贵金属杠杆高,面板手数误触可能瞬间放大敞口,实盘前务必在策略测试器里点一遍边界值。

MQL5 / C++
class="type">bool CFastTrading::CreateLotEdit(CWindow &window,CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储指向主控件的指针
   text_edit.MainPointer(window);
class=class="str">"cmt">//--- 属性
   text_edit.XSize(class="num">80);
   text_edit.YSize(class="num">24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX));
   text_edit.StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP));
   text_edit.MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
   text_edit.SpinEditMode(true);
   text_edit.SetDigits(class="num">2);
   text_edit.GetTextBoxPointer().XGap(class="num">1);
   text_edit.GetTextBoxPointer().XSize(class="num">80);
class=class="str">"cmt">//--- 创建一个控制元素
   if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap))
      class="kw">return(class="kw">false);
   text_edit.SetValue(class="type">class="kw">string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)));
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组
   CWndContainer::AddToElementsArray(w_number,text_edit);
   class="kw">return(true);
}

class="type">bool CFastTrading::CreateTakeProfitEdit(CWindow &window,CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储指向主控件的指针
   text_edit.MainPointer(window);
class=class="str">"cmt">//--- 属性
   text_edit.XSize(class="num">80);
   text_edit.YSize(class="num">24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(class="num">9999);
   text_edit.StepValue(class="num">1);
   text_edit.MinValue(class="num">0);
   text_edit.SpinEditMode(true);
   text_edit.GetTextBoxPointer().XGap(class="num">1);
   text_edit.GetTextBoxPointer().XSize(class="num">80);
class=class="str">"cmt">//--- 创建一个控制元素
   if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap))
      class="kw">return(class="kw">false);
   text_edit.SetValue(class="type">class="kw">string(class="num">150));
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组
   CWndContainer::AddToElementsArray(w_number,text_edit);
   class="kw">return(true);
}

「止损输入框与买卖按钮的界面封装」

在 MT5 自定义面板里,止损位和一键买卖按钮是高频操作控件。下面这段封装把止损编辑框固定成 80×24 像素、最大值 9999、步进 1 的 SpinEdit,初始值直接写死 150,意味着新开面板默认会给 150 点止损的预设。 买卖按钮则走同一套属性模板:宽 120、高 40,文字居中且悬停/按下态都锁死底色。买按钮用 C'88,212,210'(青绿),卖按钮用 C'236,85,79'(红),白字,视觉上和市价方向强绑定,扫一眼就知道点哪边。 两个 Create 函数都把控件通过 AddToElementsArray 挂到窗口元素数组,w_number 是索引。你在 EA 里改初始止损只要动 SetValue(string(150)) 那个 150;想换按钮色直接改 baseclr 的 RGB 三元组,不用碰绘制逻辑。外汇和贵金属波动剧烈,这类一键面板容易误触,实盘前务必在策略测试器里用模拟账户验过交互。

MQL5 / C++
class="type">bool CFastTrading::CreateStopLossEdit(CWindow &window,CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储指向主控件的指针
   text_edit.MainPointer(window);
class=class="str">"cmt">//--- 属性
   text_edit.XSize(class="num">80);
   text_edit.YSize(class="num">24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.MaxValue(class="num">9999);
   text_edit.StepValue(class="num">1);
   text_edit.MinValue(class="num">0);
   text_edit.SpinEditMode(true);
   text_edit.GetTextBoxPointer().XGap(class="num">1);
   text_edit.GetTextBoxPointer().XSize(class="num">80);
class=class="str">"cmt">//--- 创建一个控制元素
   if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap))
      class="kw">return(class="kw">false);
   text_edit.SetValue(class="type">class="kw">string(class="num">150));
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组
   CWndContainer::AddToElementsArray(w_number,text_edit);
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::CreateBuyButton(CWindow &window,CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储窗口指针
   button.MainPointer(window);
   class="type">color baseclr=C&class="macro">#x27;class="num">88,class="num">212,class="num">210&class="macro">#x27;;
class=class="str">"cmt">//--- 在创建之前设置属性
   button.XSize(class="num">120);
   button.YSize(class="num">40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
class=class="str">"cmt">//--- 创建一个控制元素
   if(!button.CreateButton(text,x_gap,y_gap))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 向数据库添加一个指向元素的指针
   CWndContainer::AddToElementsArray(w_number,button);
   class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::CreateSellButton(CWindow &window,CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储窗口指针
   button.MainPointer(window);
   class="type">color baseclr=C&class="macro">#x27;class="num">236,class="num">85,class="num">79&class="macro">#x27;;
class=class="str">"cmt">//--- 在创建之前设置属性
   button.XSize(class="num">120);
   button.YSize(class="num">40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
class=class="str">"cmt">//--- 创建一个控制元素
   if(!button.CreateButton(text,x_gap,y_gap))

市价单窗口的界面骨架怎么搭

在 MT5 自定义面板里做一键下单,第一步是把市价单窗口的容器和坐标定死。下面这段 CFastTrading::CreateMarketOrdersWindow 把窗口尺寸锁在 450×300(242+58),标题栏高 22,主色用了 C'87,128,255' 这种蓝,可拖动、不可最小化,属于典型的对话框式浮窗。 窗口左上角 x 取自下单按钮的 XGap(),y 则在按钮 YGap() 基础上加 60,避免和顶部按钮重叠。创建完后依次挂入买入模块、LOT/TP/SL 三个标签,以及 3 个减号切换按钮和手数、止盈、止损编辑框——任一控件创建失败就直接 return(false),保证界面原子性。 你把这串代码贴进 EA 的界面类,编译后开一个 XAUUSD 的 M5 图,大概率能看到一个 450 像素宽、可拖拽的市价单面板。外汇和贵金属波动剧烈,这种面板只解决下单效率,不替你判断方向,实盘前务必在策略测试器里跑一遍控件布局。

MQL5 / C++
class="type">bool CFastTrading::CreateMarketOrdersWindow(class="type">void)
{
class=class="str">"cmt">//--- 向窗口数组添加窗口指针
  CWndContainer::AddWindow(m_orders_windows[class="num">0]);
class=class="str">"cmt">//--- 属性
  m_orders_windows[class="num">0].XSize(class="num">450);
  m_orders_windows[class="num">0].YSize(class="num">242+class="num">58);
class=class="str">"cmt">//--- 坐标
  class="type">int x=m_order_button[class="num">0].XGap();
  class="type">int y=m_order_button[class="num">0].YGap()+class="num">60;
class=class="str">"cmt">//---
  class="type">color clrmain=C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;;
class=class="str">"cmt">//---
  m_orders_windows[class="num">0].CaptionHeight(class="num">22);
  m_orders_windows[class="num">0].IsMovable(true);
  m_orders_windows[class="num">0].CaptionColor(clrmain);
  m_orders_windows[class="num">0].CaptionColorLocked(clrmain);
  m_orders_windows[class="num">0].CaptionColorHover(clrmain);
  m_orders_windows[class="num">0].BackColor(m_background_color);
  m_orders_windows[class="num">0].BorderColor(clrmain);
  m_orders_windows[class="num">0].FontSize(m_base_font_size);
  m_orders_windows[class="num">0].Font(m_base_font);
  m_orders_windows[class="num">0].WindowType(W_DIALOG);
class=class="str">"cmt">//--- 创建窗体
  if(!m_orders_windows[class="num">0].CreateWindow(m_chart_id,m_subwin,CAPTION_M_ORD_NAME,x,y))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 买入模块
  if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">0],class="num">10,class="num">20,BUY_ORDER,class="num">1))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">0],class="num">20,class="num">30,LOT,class="num">1))
     class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">1],class="num">20+class="num">80+class="num">20,class="num">30,TP,class="num">1))
     class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">30,SL,class="num">1))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换按钮
  for(class="type">int i=class="num">0; i<class="num">3; i++)
     if(!CreateSwitchButton(m_orders_windows[class="num">0],m_switch_button[i],"-",class="num">20+(class="num">80+class="num">20)*i,class="num">60,class="num">1))
         class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
  if(!CreateLotEdit(m_orders_windows[class="num">0],m_lot_edit[class="num">0],class="num">20,class="num">60+class="num">34,class="num">1))
     class="kw">return(class="kw">false);
  if(!CreateTakeProfitEdit(m_orders_windows[class="num">0],m_tp_edit[class="num">0],class="num">20+(class="num">80+class="num">20),class="num">60+class="num">34,class="num">1))
     class="kw">return(class="kw">false);

◍ 买卖面板的控件坐标落位

这段初始化函数把下单窗口里的买、卖两个模块一次性铺开。买入区从 Y=60 的止损编辑框往下,到 Y=103 的买入按钮为止;卖出区则从 Y=160 的边框起,到 Y=242 的卖出按钮收尾,两块区域在垂直方向错开 100 像素,避免 MT5 子窗口里控件重叠。 坐标不是拍脑袋:LOT/TP/SL 三个标签固定在 Y=170,下方 30 像素放减号切换钮,再往下 35 像素才是可输入编辑框,也就是 Y=235 这一行。卖出按钮的 X 用 m_orders_windows[0].XSize()-(120+20) 动态右对齐,说明面板宽度变化时按钮始终贴右边距 20 像素。 宏 BUYm_language==RUSSIAN 切俄文“Купить”或英文“Buy”,实盘若面向多语种终端,改这里就能换显示文案,不用动创建逻辑。外汇与贵金属杠杆高,面板只是下单入口,任何一键成交都须自担滑点与断网风险。

MQL5 / C++
if(!CreateStopLossEdit(m_orders_windows[class="num">0],m_sl_edit[class="num">0],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">60+class="num">34,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 买入按钮
   if(!CreateBuyButton(m_orders_windows[class="num">0],m_buy_execute,BUY+"(B)",m_orders_windows[class="num">0].XSize()-(class="num">120+class="num">20),class="num">103,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 卖出模块
   if(!CreateFrame(m_orders_windows[class="num">0],m_frame[class="num">1],class="num">10,class="num">160,SELL_ORDER,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
   if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">3],class="num">20,class="num">170,LOT,class="num">1))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">4],class="num">20+class="num">80+class="num">20,class="num">170,TP,class="num">1))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">0],m_m_text_labels[class="num">5],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170,SL,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换按钮
   for(class="type">int i=class="num">3; i<class="num">6; i++)
      if(!CreateSwitchButton(m_orders_windows[class="num">0],m_switch_button[i],"-",class="num">20+(class="num">80+class="num">20)*(i-class="num">3),class="num">170+class="num">30,class="num">1))
         class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
   if(!CreateLotEdit(m_orders_windows[class="num">0],m_lot_edit[class="num">1],class="num">20,class="num">170+class="num">30+class="num">35,class="num">1))
      class="kw">return(class="kw">false);
   if(!CreateTakeProfitEdit(m_orders_windows[class="num">0],m_tp_edit[class="num">1],class="num">20+class="num">80+class="num">20,class="num">170+class="num">30+class="num">35,class="num">1))
      class="kw">return(class="kw">false);
   if(!CreateStopLossEdit(m_orders_windows[class="num">0],m_sl_edit[class="num">1],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170+class="num">30+class="num">35,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 卖出按钮
   if(!CreateSellButton(m_orders_windows[class="num">0],m_sell_execute,SELL+"(S)",m_orders_windows[class="num">0].XSize()-(class="num">120+class="num">20),class="num">242,class="num">1))
      class="kw">return(class="kw">false);
   class="kw">return(true);
}
class="macro">#define BUY(m_language==RUSSIAN ? "Купить" : "Buy")

「一键切换下单面板的手数或点数模式」

快速交易面板的 6 个切换按钮在 GUI 创建完成后就被写入初始标签:索引 0 和 3 显示 LOT(手数),其余 4 个显示 POINTS(点数),这一映射在 ON_END_CREATE_GUI 事件里通过 SetButtonParam 逐一定死。 点击行为由 ButtonSwitch 接管:按钮未按下时回退到 state1,按下后切到 state2。实际绑定里,0 号和 3 号在 LOT 与 PERC_DEPO(占存款百分比)之间翻转,1、2、4、5 号则在 POINTS 与 PRICE(价格)之间翻转。 下面这段是面板事件处理的核心片段,注意高亮部分就是点击后批量切换标签的调用: #define SELL (m_language==RUSSIAN ? "Продать" : "Sell") //+------------------------------------------------------------------+

//设置按钮文字

//+------------------------------------------------------------------+ void CFastTrading::SetButtonParam(CButton &button,string text) { button.LabelText(text); button.Update(true); } //--- UI 创建完毕 if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { //--- SetButtonParam(m_switch_button[0],LOT); SetButtonParam(m_switch_button[1],POINTS); SetButtonParam(m_switch_button[2],POINTS); SetButtonParam(m_switch_button[3],LOT); SetButtonParam(m_switch_button[4],POINTS); SetButtonParam(m_switch_button[5],POINTS); } #define POINTS (m_language==RUSSIAN ? "Пункты" : "Points") //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ void CFastTrading::ButtonSwitch(CButton &button,long lparam,string state1,string state2) { if(lparam==button.Id()) { if(!button.IsPressed()) SetButtonParam(button,state1); else SetButtonParam(button,state2); } } //--- 按钮事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- if(lparam==m_order_button[0].Id()) m_orders_windows[0].OpenWindow(); //--- if(lparam==m_order_button[1].Id()) m_orders_windows[1].OpenWindow(); //--- ButtonSwitch(m_switch_button[0],lparam,LOT,PERC_DEPO); ButtonSwitch(m_switch_button[1],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[2],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[3],lparam,LOT,PERC_DEPO); ButtonSwitch(m_switch_button[4],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[5],lparam,POINTS,PRICE); } #define PERC_DEPO (m_language==RUSSIAN ? "% Депозит" : "% Deposit") #define PRICE (m_language==RUSSIAN ? "Цена" : "Price") //+------------------------------------------------------------------+ 逐行拆解:SetButtonParam 只是把文字塞进按钮 LabelText 并强制 Update(true) 重绘;ON_END_CREATE_GUI 分支在初始化时确定 6 个按钮的静态文案;ButtonSwitch 用 IsPressed() 判断态翻转,点击时 0/3 号切 LOT↔%Deposit,其余切 Points↔Price。 在 MT5 里接这套逻辑时,若你面板按钮数不是 6 个,直接改数组长度与 ButtonSwitch 调用行数即可,外汇与贵金属品种波动大,面板切到 Price 模式下单前务必核对实时报价,杠杆交易风险偏高。

MQL5 / C++
class="macro">#define SELL(m_language==RUSSIAN ? "Продать" : "Sell")
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 设置按钮文字                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::SetButtonParam(CButton &button,class="type">class="kw">string text)
{
   button.LabelText(text);
   button.Update(true);
}
class=class="str">"cmt">//--- UI 创建完毕
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
   class=class="str">"cmt">//---
      SetButtonParam(m_switch_button[class="num">0],LOT);
      SetButtonParam(m_switch_button[class="num">1],POINTS);
      SetButtonParam(m_switch_button[class="num">2],POINTS);
      SetButtonParam(m_switch_button[class="num">3],LOT);
      SetButtonParam(m_switch_button[class="num">4],POINTS);
      SetButtonParam(m_switch_button[class="num">5],POINTS);
   }
class="macro">#define POINTS(m_language==RUSSIAN ? "Пункты" : "Points")
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::ButtonSwitch(CButton &button,class="type">long lparam,class="type">class="kw">string state1,class="type">class="kw">string state2)
{
   if(lparam==button.Id())
   {
      if(!button.IsPressed())
         SetButtonParam(button,state1);
      else
         SetButtonParam(button,state2);
   }
}
class=class="str">"cmt">//--- 按钮事件
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
   class=class="str">"cmt">//---
      if(lparam==m_order_button[class="num">0].Id())
         m_orders_windows[class="num">0].OpenWindow();
      class=class="str">"cmt">//---
      if(lparam==m_order_button[class="num">1].Id())
         m_orders_windows[class="num">1].OpenWindow();
      class=class="str">"cmt">//---
      ButtonSwitch(m_switch_button[class="num">0],lparam,LOT,PERC_DEPO);
      ButtonSwitch(m_switch_button[class="num">1],lparam,POINTS,PRICE);
      ButtonSwitch(m_switch_button[class="num">2],lparam,POINTS,PRICE);
      ButtonSwitch(m_switch_button[class="num">3],lparam,LOT,PERC_DEPO);
      ButtonSwitch(m_switch_button[class="num">4],lparam,POINTS,PRICE);
      ButtonSwitch(m_switch_button[class="num">5],lparam,POINTS,PRICE);
   }
class="macro">#define PERC_DEPO(m_language==RUSSIAN ? "% Депозит" : "% Deposit")
class="macro">#define PRICE(m_language==RUSSIAN ? "Цена" : "Price")
class=class="str">"cmt">//+------------------------------------------------------------------+

手数框与止盈框的开关联动逻辑

在自制 MT5 交易面板里,用开关按钮切换手数输入模式是很实用的细节。下面这段 CFastTrading 的成员函数演示了:当第 i 组开关被按下,手数编辑框立刻变成整数档位(0 位小数、步长 1、区间 1~100、默认 2 手);松开则恢复成按品种真实合约量限制的小数手数。 LotMarketSwitch 里对 m_lot_edit[i] 的设定直接写死上限 100、下限 1,这意味着若经纪商允许的单笔上限超过 100,界面会强制截断,实盘前需按账户杠杆核对。 TakeMarketSwitch 则处理止盈框:开关按下时抓取当前卖价 tick.ask 填进 m_tp_edit[i],小数位跟随 _Digits、步长用 _Point。注意这里把止盈预设成市价卖价,仅适合做空快捷单,做多时需自行改读 tick.bid。 两个函数都只循环 i<2,说明面板最多支持两组独立订单参数;改循环上限或数组长度才能扩到更多。外汇与贵金属波动剧烈,这类一键填值仅降低操作延迟,不消除滑点与断连风险,参数务必在策略测试器先跑一遍。

MQL5 / C++
class="type">void CFastTrading::LotMarketSwitch(class="type">long lparam)
{
   for(class="type">int i=class="num">0; i<class="num">2; i++)
   {
      if(lparam==m_switch_button[i*class="num">3].Id())
      {
         if(m_switch_button[i*class="num">3].IsPressed())
         {
            m_lot_edit[i].SetDigits(class="num">0);          class=class="str">"cmt">// 手数框显示0位小数(整数手)
            m_lot_edit[i].StepValue(class="num">1);          class=class="str">"cmt">// 每次加减步长为1手
            m_lot_edit[i].MaxValue(class="num">100);         class=class="str">"cmt">// 最大允许填100手
            m_lot_edit[i].MinValue(class="num">1);           class=class="str">"cmt">// 最小必须1手
            m_lot_edit[i].SetValue(class="type">class="kw">string(class="num">2));   class=class="str">"cmt">// 默认给2手
            m_lot_edit[i].GetTextBoxPointer().Update(true); class=class="str">"cmt">// 刷新文本框
         }
         else
         {
            m_lot_edit[i].SetDigits(class="num">2);          class=class="str">"cmt">// 恢复2位小数
            m_lot_edit[i].StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP)); class=class="str">"cmt">// 步长=品种最小成交量步长
            m_lot_edit[i].MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));   class=class="str">"cmt">// 最小=品种限制
            m_lot_edit[i].MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX));   class=class="str">"cmt">// 最大=品种限制
            m_lot_edit[i].SetValue(class="type">class="kw">string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN))); class=class="str">"cmt">// 默认填最小手数
            m_lot_edit[i].GetTextBoxPointer().Update(true);
         }
      }
   }
}

class="type">void CFastTrading::TakeMarketSwitch(class="type">long lparam)
{
   for(class="type">int i=class="num">0; i<class="num">2; i++)
   {
      if(lparam==m_switch_button[class="num">3*i+class="num">1].Id())
      {
         if(m_switch_button[class="num">3*i+class="num">1].IsPressed())
         {
            class="type">MqlTick tick;
            if(SymbolInfoTick(Symbol(),tick))
            {
               m_tp_edit[i].SetDigits(_Digits);          class=class="str">"cmt">// 止盈框小数位同品种
               m_tp_edit[i].StepValue(_Point);           class=class="str">"cmt">// 步长用一个点到价
               m_tp_edit[i].SetValue(class="type">class="kw">string(tick.ask));  class=class="str">"cmt">// 止盈预设为当前卖价
               m_tp_edit[i].GetTextBoxPointer().Update(true);
            }
         }
      }
   }
}

◍ 止盈止损输入框的两种刻度切换

快速交易面板里,止盈/止损编辑框有两种工作状态:按下切换键时显示实时报价精度,弹起时退回整数点数模式。TakeMarketSwitch 与 StopMarketSwitch 的逻辑完全对称,只是分别操作 m_tp_edit 和 m_sl_edit。 以止损为例,按键 m_switch_button[3*i+2] 被按下时,先取 SymbolInfoTick 的 bid 值,再把 m_sl_edit[i] 的显示位数设为 _Digits、步进设为 _Point、数值设为 tick.bid 字符串,最后 Update(true) 刷新文本框。此时输入框绑定的是真实市场价格,贵金属与外汇品种因 _Digits 不同(如 XAUUSD 多为 2 位、EURUSD 多为 5 位),界面刻度会自动跟随。 按键弹起则走 else 分支:SetDigits(0) 去掉小数位,StepValue(1) 以 1 点为步长,SetValue("150") 直接填 150 点固定止损。这套逻辑意味着用户不盯盘时也能用整数点预设风险,但外汇/贵金属杠杆高,150 点是否合适要看品种波动,盲目沿用可能放大回撤。 OnInitTrading 里把 array_used_symbols 传给 m_trade.SetUsedSymbols,并 TradingSetAsyncMode(false) 强制同步发单。开 MT5 把这段挂到 EA 初始化,能直接验证切换键对报价精度的响应延迟通常在 1 个 tick 内。

MQL5 / C++
class="type">void CFastTrading::StopMarketSwitch(class="type">long lparam)
{
  for(class="type">int i=class="num">0;i<class="num">2;i++)
  {
    if(lparam==m_switch_button[class="num">3*i+class="num">2].Id())
    {
      if(m_switch_button[class="num">3*i+class="num">2].IsPressed())
      {
        class="type">MqlTick tick;
        if(SymbolInfoTick(Symbol(),tick))
        {
          m_sl_edit[i].SetDigits(_Digits);
          m_sl_edit[i].StepValue(_Point);
          m_sl_edit[i].SetValue(class="type">class="kw">string(tick.bid));
          m_sl_edit[i].GetTextBoxPointer().Update(true);
        }
      }
      else
      {
        m_sl_edit[i].SetDigits(class="num">0);
        m_sl_edit[i].StepValue(class="num">1);
        m_sl_edit[i].SetValue(class="type">class="kw">string(class="num">150));
        m_sl_edit[i].GetTextBoxPointer().Update(true);
      }
    }
  }
}

class="type">void CFastTrading::OnInitTrading()
{
  class="type">class="kw">string array_used_symbols[];
  CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols);
  m_trade.SetUsedSymbols(array_used_symbols);
  m_trade.TradingOnInit();
  m_trade.TradingSetMagic(m_magic_number);
  m_trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG);
  m_trade.TradingSetAsyncMode(class="kw">false);
  m_trade.TradingSetCorrectTypeExpiration();
  m_trade.TradingSetCorrectTypeFilling();
}

「买单调用的多分支触发逻辑」

在自建面板里挂买单,核心入口是 CFastTrading::SetBuyOrder。它同时监听图表自定义按钮点击事件(CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 且 lparam 等于买按钮 Id)和键盘 KEY_B 按键,两个来源任一命中就进入下单流程。 手数计算先看 m_switch_button[0] 是否按下:按下时走 LotPercent,按账户净值百分比换算手数;没按则 NormalizeLot 直接规范输入框里的绝对值。外汇和贵金属杠杆高,手数换算错一格就可能让回撤失控,建议先在策略测试器里用 0.01 手跑通再放大。 TP/SL 的传递类型由另外两组开关决定:m_switch_button[1] 管 TP 用 double 还是 int,m_switch_button[2] 管 SL 同逻辑。四种组合对应四段 if,例如两者都按就用 double 型 tp/sl 调 OpenBuy;只有 [1] 按则 TP 为 double、SL 为 int。这样设计能让用户输入「30」代表 30 点,也能输「0.0030」代表绝对价,但代码里没做越界校验,复制去用时自己补一句范围判断。

MQL5 / C++
CFastTrading::CFastTrading(class="type">void)
{
  OnInitTrading();
}
class="type">bool CFastTrading::SetBuyOrder(class="type">int id,class="type">long lparam)
{
  if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) ||
      (id==CHARTEVENT_KEYDOWN && lparam==KEY_B))
  {
    class="type">class="kw">double lot;
    if(m_switch_button[class="num">0].IsPressed())
      lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">0].GetValue()));
    else
      lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">0].GetValue()));
    if(m_switch_button[class="num">1].IsPressed() && m_switch_button[class="num">2].IsPressed())
    {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">0].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">0].GetValue());
      if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
        class="kw">return(true);
    }
    else if(!m_switch_button[class="num">1].IsPressed() && !m_switch_button[class="num">2].IsPressed())
    {
      class="type">int tp=class="type">int(m_tp_edit[class="num">0].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">0].GetValue());
      if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
        class="kw">return(true);
    }
    else if(m_switch_button[class="num">1].IsPressed() && !m_switch_button[class="num">2].IsPressed())
    {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">0].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">0].GetValue());
      if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
        class="kw">return(true);
    }
    else if(!m_switch_button[class="num">1].IsPressed() && m_switch_button[class="num">2].IsPressed())
    {
      class="type">int tp=class="type">int(m_tp_edit[class="num">0].GetValue());

卖单触发里的止损止盈双形态处理

面板卖单函数 SetSellOrder 通过图表事件或快捷键 S 拉起下单逻辑,先根据 switch_button[3] 状态决定手数走百分比还是绝对值归一化。 止损止盈的传入形态由 switch_button[4]、[5] 两个开关组合控制:都按下时 sl/tp 按 double 从编辑框读;都不按下时按 int 读;只开其一则混合 int 与 double。这种分支意味着你在 UI 上切开关,底层 OpenSell 收到的参数类型就变,EA 不会自动帮你统一。 外汇与贵金属杠杆高,SL/TP 单位搞错可能直接吃满波动,上 MT5 把这几颗按钮状态打印出来比对最稳妥。 下面这段是卖单分支的核心代码片段,注意 sl/tp 的变量类型随开关翻转:

if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id())

(id==CHARTEVENT_KEYDOWN && lparam==KEY_S)) { double lot; if(m_switch_button[3].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[1].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[1].GetValue())); if(m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed()) { double tp=double(m_tp_edit[1].GetValue()); double sl=double(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[1].GetValue()); int sl=int(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[1].GetValue()); double sl=double(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed()) {

MQL5 / C++
if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id()) ||
   (id==CHARTEVENT_KEYDOWN && lparam==KEY_S))
{
   class="type">class="kw">double lot;
   if(m_switch_button[class="num">3].IsPressed())
      lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[class="num">1].GetValue()));
   else
      lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">1].GetValue()));
   if(m_switch_button[class="num">4].IsPressed() && m_switch_button[class="num">5].IsPressed())
   {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">1].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">1].GetValue());
      if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         class="kw">return(true);
   }
   else if(!m_switch_button[class="num">4].IsPressed() && !m_switch_button[class="num">5].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">1].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">1].GetValue());
      if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         class="kw">return(true);
   }
   else if(!m_switch_button[class="num">4].IsPressed() && m_switch_button[class="num">5].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">1].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">1].GetValue());
      if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         class="kw">return(true);
   }
   else if(m_switch_button[class="num">4].IsPressed() && !m_switch_button[class="num">5].IsPressed())
   {

◍ 按净值百分比算手数的实战函数

在 MT5 面板上做一键卖出时,止损点数从编辑框取整、止盈从双精度框读取,再交给 CTrade 去发单;但手数不能写死,得跟着账户余额走。下面这段 LotPercent 就是按「净值 × 百分比 ÷ 单手保证金」倒推可下的最大手数。 外汇和贵金属杠杆高,按百分比开仓可能在极端波动下迅速放大回撤,实盘前务必在策略测试器里用小资金验证。 [CODE]double CFastTrading::LotPercent(string symbol,ENUM_ORDER_TYPE trade_type,double price,double percent) { double margin=0.0;

if(symbol==""price<=0.0percent<1percent>100)

return(0.0);

if(!OrderCalcMargin(trade_type,symbol,1.0,price,margin)margin<0.0)

return(0.0); if(margin==0.0) return(0.0); double volume=NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)*percent/100.0/margin,2); double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); if(stepvol>0.0) volume=stepvol*MathFloor(volume/stepvol); double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); if(volume<minvol) volume=0.0; double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); if(volume>maxvol) volume=maxvol; return(volume); }[/CODE] 逐行看:先拦掉空品种、非正价格和不在 1~100 的百分比例;用 OrderCalcMargin 算 1 手保证金,失败或为零直接返 0。接着用余额乘百分比除以保证金,NormalizeDouble 到 2 位小数。 之后按 SYMBOL_VOLUME_STEP 向下取整到合约步长,再卡 SYMBOL_VOLUME_MIN 和 SYMBOL_VOLUME_MAX。返回的手数可能倾向安全,但经纪商规则变动时仍可能返回 0,调用方要判空。 挂单窗口那头,CreatePendingWindows 把 600×580 的容器挂到按钮下方 60 像素处,标题栏高 22、可拖动,主色写死 C'31,209,111'(草绿)。这种硬坐标在 4K 屏上可能偏位,自己改 XGap/YGap 更稳。

MQL5 / C++
class="type">class="kw">double CFastTrading::LotPercent(class="type">class="kw">string symbol,ENUM_ORDER_TYPE trade_type,class="type">class="kw">double price,class="type">class="kw">double percent)
{
  class="type">class="kw">double margin=class="num">0.0;
  if(symbol=="" || price<=class="num">0.0 || percent<class="num">1 || percent>class="num">100)
    class="kw">return(class="num">0.0);
  if(!OrderCalcMargin(trade_type,symbol,class="num">1.0,price,margin) || margin<class="num">0.0)
    class="kw">return(class="num">0.0);
  if(margin==class="num">0.0)
    class="kw">return(class="num">0.0);
  class="type">class="kw">double volume=NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)*percent/class="num">100.0/margin,class="num">2);
  class="type">class="kw">double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
  if(stepvol>class="num">0.0)
    volume=stepvol*MathFloor(volume/stepvol);
  class="type">class="kw">double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
  if(volume<minvol)
    volume=class="num">0.0;
  class="type">class="kw">double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
  if(volume>maxvol)
    volume=maxvol;
  class="kw">return(volume);
}

「挂单面板的 Buy Stop 模块怎么拼」

在 MT5 自定义面板里搭一个 Buy Stop 挂单区,本质是往第二个订单窗口对象里逐个塞控件。下面这段初始化代码把边框色、字号、字体和窗口类型先定死,再依次创建对话框、框架与各类标签。 窗口属性先设:边框取主色 clrmain,字号走基准 m_base_font_size,字体用 m_base_font,类型指定为 W_DIALOG 浮动框。随后 CreateWindow 用图表 ID、子窗口号和标题常量 CAPTION_P_ORD_NAME 在坐标 (x,y) 生成实体,失败直接 return false 中断。 控件布局有固定间距规律:标签横向以 80+20=100 像素为步长。PRICE 标签在 (20,60),LOT 在偏移 100 后的 (120,30),TP 在再偏移 100 的 (220,30),SL 在 (320,30);编辑框统一下沉 35 像素到 y=95 一线,与标签上下对齐。 三个切换按钮用 for 循环批量建,i 从 0 到 2,x 坐标 120+100*i,文本暂用 "-"。最后 Buy Stop 执行按钮贴右边:x 取窗口宽减 140,y=103,标题写 "Buy Stop ( 1 )"。外汇与贵金属挂单受滑点和休市影响,这类面板只解决录入效率,不保证成交价。 把下面代码直接粘进你的 COrdersWindows 派生类,改 m_subwin 和 x,y 就能在实盘图表看到这个挂单块。

MQL5 / C++
  m_orders_windows[class="num">1].BorderColor(clrmain);
  m_orders_windows[class="num">1].FontSize(m_base_font_size);
  m_orders_windows[class="num">1].Font(m_base_font);
  m_orders_windows[class="num">1].WindowType(W_DIALOG);
class=class="str">"cmt">//--- 创建窗体
  if(!m_orders_windows[class="num">1].CreateWindow(m_chart_id,m_subwin,CAPTION_P_ORD_NAME,x,y))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//---BUY-STOP 模块
  if(!CreateFrame(m_orders_windows[class="num">1],m_frame[class="num">2],class="num">10,class="num">20,BUYSTOP_ORDER,class="num">2))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
  if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">0],class="num">20,class="num">60,PRICE,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">1],class="num">20+class="num">80+class="num">20,class="num">30,LOT,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">30,TP,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">3],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">30,SL,class="num">2))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换
  for(class="type">int i=class="num">0; i<class="num">3; i++)
     if(!CreateSwitchButton(m_orders_windows[class="num">1],m_p_switch_button[i],"-",class="num">120+(class="num">80+class="num">20)*i,class="num">60,class="num">2))
        class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
  if(!CreatePriceEdit(m_orders_windows[class="num">1],m_pr_edit[class="num">0],class="num">20,class="num">60+class="num">35,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateLotEdit(m_orders_windows[class="num">1],m_lot_edit[class="num">2],class="num">20+(class="num">80+class="num">20),class="num">60+class="num">35,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateTakeProfitEdit(m_orders_windows[class="num">1],m_tp_edit[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">60+class="num">35,class="num">2))
     class="kw">return(class="kw">false);
  if(!CreateStopLossEdit(m_orders_windows[class="num">1],m_sl_edit[class="num">2],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">60+class="num">35,class="num">2))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//--- Buy Stop 下单按钮
  if(!CreateBuyButton(m_orders_windows[class="num">1],m_buystop_execute,"Buy Stop( class="num">1 )",m_orders_windows[class="num">1].XSize()-(class="num">120+class="num">20),class="num">103,class="num">2))
     class="kw">return(class="kw">false);
class=class="str">"cmt">//---SELL-STOP 模块

挂单面板的 Sell Stop 与 Buy Limit 控件布局

这段初始化逻辑在第二个挂单窗口里铺了两块区域:Sell Stop 模块从 Y=160 起,Buy Limit 模块从 Y=300 起,两者共用同一窗口对象 m_orders_windows[1]。 Sell Stop 先建外框 CreateFrame(...,160,SELLSTOP_ORDER,2),任一控件创建失败直接 return(false),保证面板不完整时绝不继续。标题行四个标签分别落在 X=20、X=120、X=220、X=320,Y 基准 170,间距严格按 80+20 推算。 切换按钮用 for(i=3;i<6;i++) 循环生成三个 CreateSwitchButton,X 坐标 120+(80+20)*(i-3),也就是 120、220、320,Y 比标题低 30。编辑框再下沉 35 像素,价格框 Y=170+30+35=235,Lot/TP/SL 依次右移一个 100 像素单元。 下单按钮 CreateSellButton 贴右边:XSize()-(120+20),Y=242,文字写死 "Sell Stop ( 2 )"。随后 Buy Limit 外框在 Y=300 起,标题标签 m_p_text_labels[8] 放在 X=20,Y=330——比外框顶多 30,和前面 Sell Stop 的标签偏移规律一致。 外汇与贵金属挂单界面涉及杠杆与滑点,实盘前务必在 MT5 策略测试器用可视化面板跑一遍,确认坐标无重叠。

MQL5 / C++
if(!CreateFrame(m_orders_windows[class="num">1],m_frame[class="num">3],class="num">10,class="num">160,SELLSTOP_ORDER,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">4],class="num">20,class="num">170+class="num">30,PRICE,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">5],class="num">20+class="num">80+class="num">20,class="num">170,LOT,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">6],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170,TP,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">7],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">170,SL,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换
   for(class="type">int i=class="num">3; i<class="num">6; i++)
      if(!CreateSwitchButton(m_orders_windows[class="num">1],m_p_switch_button[i],"-",class="num">120+(class="num">80+class="num">20)*(i-class="num">3),class="num">170+class="num">30,class="num">2))
         class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
   if(!CreatePriceEdit(m_orders_windows[class="num">1],m_pr_edit[class="num">1],class="num">20,class="num">170+class="num">30+class="num">35,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLotEdit(m_orders_windows[class="num">1],m_lot_edit[class="num">3],class="num">20+(class="num">80+class="num">20),class="num">170+class="num">30+class="num">35,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateTakeProfitEdit(m_orders_windows[class="num">1],m_tp_edit[class="num">3],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">170+class="num">30+class="num">35,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateStopLossEdit(m_orders_windows[class="num">1],m_sl_edit[class="num">3],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">170+class="num">30+class="num">35,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- Sell Stop 下单按钮
   if(!CreateSellButton(m_orders_windows[class="num">1],m_sellstop_execute,"Sell Stop( class="num">2 )",m_orders_windows[class="num">1].XSize()-(class="num">120+class="num">20),class="num">242,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//---BUY-LIMIT 模块
   if(!CreateFrame(m_orders_windows[class="num">1],m_frame[class="num">4],class="num">10,class="num">300,BUYLIMIT_ORDER,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">8],class="num">20,class="num">330,PRICE,class="num">2))

◍ 挂单面板的标签与编辑框布局

这段逻辑在第二个订单窗口里继续堆挂单模块,先给 Buy Limit 区补文本标签:索引 9~11 的标签分别落在 x=120、220、320,y=310,内容对应手数、止盈、止损,任意一处创建失败就直接 return(false) 中断。 紧接着是三个减号切换按钮,用 for(i=6;i<9) 循环生成,x 坐标按 120+(80+20)*(i-6) 递推,y 固定在 330,间距 100 像素一排摆开。 编辑框部分从 y=365 开始:价格框在 x=20,手数框 x=120,TP 框 x=220,SL 框 x=320,全部挂到 m_orders_windows[1] 上,失败即退出。 Buy Limit 执行按钮贴在窗口右缘,x 取窗口宽度减 140,y=382,文案写 "Buy Limit ( 3 )";随后 SELL-LIMIT 模块从 y=440 起帧,标题标签 12~14 在 y=470/450 错开排布。外汇与贵金属挂单界面涉及杠杆与滑点,实盘前务必在 MT5 策略测试器核对坐标溢出。

MQL5 / C++
   class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">9],class="num">20+class="num">80+class="num">20,class="num">310,LOT,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">10],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">310,TP,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">11],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">310,SL,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换
   for(class="type">int i=class="num">6; i<class="num">9; i++)
      if(!CreateSwitchButton(m_orders_windows[class="num">1],m_p_switch_button[i],"-",class="num">120+(class="num">80+class="num">20)*(i-class="num">6),class="num">330,class="num">2))
         class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
   if(!CreatePriceEdit(m_orders_windows[class="num">1],m_pr_edit[class="num">2],class="num">20,class="num">365,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLotEdit(m_orders_windows[class="num">1],m_lot_edit[class="num">4],class="num">20+(class="num">80+class="num">20),class="num">365,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateTakeProfitEdit(m_orders_windows[class="num">1],m_tp_edit[class="num">4],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">365,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateStopLossEdit(m_orders_windows[class="num">1],m_sl_edit[class="num">4],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">365,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- Buy Limit 下单按钮
   if(!CreateBuyButton(m_orders_windows[class="num">1],m_buylimit_execute,"Buy Limit( class="num">3 )",m_orders_windows[class="num">1].XSize()-(class="num">120+class="num">20),class="num">382,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//---SELL-LIMIT 模块
   if(!CreateFrame(m_orders_windows[class="num">1],m_frame[class="num">5],class="num">10,class="num">440,SELLLIMIT_ORDER,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 标题
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">12],class="num">20,class="num">470,PRICE,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">13],class="num">20+class="num">80+class="num">20,class="num">450,LOT,class="num">2))
      class="kw">return(class="kw">false);
   if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">14],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">450,TP,class="num">2))

「挂单面板的编辑框与按钮布局」

这段逻辑在 Sell Limit 挂单窗口里批量创建文本标签、切换按钮和价格编辑控件。坐标用 20+(80+20)*n 的算式递推,说明每个控件占 80 宽、20 间距,第 4 列标签 x 坐标是 260,y 固定在 450;编辑行 y 统一为 505,比标签低 55 像素,肉眼能看出分层。 CreatePriceEdit 里把 StepValue 设成 _Point、SpinEditMode 开 true,意味着在 MT5 面板里点上下箭头会以最小报价单位步进。SetDigits(_Digits) 保证小数位跟品种一致,XAUUSD 显示 2 位、EURUSD 显示 5 位,不会手抖输错位数。 最后用 SymbolInfoTick 抓 bid 填进价格框,挂单面板打开瞬间就带当前买价,省得手动敲。外汇和贵金属波动快,这类一键带价功能只降低操作延迟,不改善胜率,实盘仍属高风险。

MQL5 / C++
  class="kw">return(class="kw">false);
  if(!CreateLabel(m_orders_windows[class="num">1],m_p_text_labels[class="num">15],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">450,SL,class="num">2))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 切换
  for(class="type">int i=class="num">9; i<class="num">12; i++)
    if(!CreateSwitchButton(m_orders_windows[class="num">1],m_p_switch_button[i],"-",class="num">120+(class="num">80+class="num">20)*(i-class="num">9),class="num">470,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑
  if(!CreatePriceEdit(m_orders_windows[class="num">1],m_pr_edit[class="num">3],class="num">20,class="num">505,class="num">2))
    class="kw">return(class="kw">false);
  if(!CreateLotEdit(m_orders_windows[class="num">1],m_lot_edit[class="num">5],class="num">20+(class="num">80+class="num">20),class="num">505,class="num">2))
    class="kw">return(class="kw">false);
  if(!CreateTakeProfitEdit(m_orders_windows[class="num">1],m_tp_edit[class="num">5],class="num">20+(class="num">80+class="num">20)*class="num">2,class="num">505,class="num">2))
    class="kw">return(class="kw">false);
  if(!CreateStopLossEdit(m_orders_windows[class="num">1],m_sl_edit[class="num">5],class="num">20+(class="num">80+class="num">20)*class="num">3,class="num">505,class="num">2))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//--- Sell Limit 下单按钮
  if(!CreateSellButton(m_orders_windows[class="num">1],m_selllimit_execute,"Sell Limit( class="num">4 )",m_orders_windows[class="num">1].XSize()-(class="num">120+class="num">20),class="num">522,class="num">2))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//---
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::CreatePriceEdit(CWindow &window,CTextEdit &text_edit,class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="type">int w_number)
{
class=class="str">"cmt">//--- 存储指向主控件的指针
  text_edit.MainPointer(window);
class=class="str">"cmt">//--- 属性
  text_edit.XSize(class="num">80);
  text_edit.YSize(class="num">24);
  text_edit.Font(m_base_font);
  text_edit.FontSize(m_base_font_size);
  text_edit.StepValue(_Point);
  text_edit.SpinEditMode(true);
  text_edit.SetDigits(_Digits);
  text_edit.GetTextBoxPointer().XGap(class="num">1);
  text_edit.GetTextBoxPointer().XSize(class="num">80);
class=class="str">"cmt">//--- 创建一个控制元素
  if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap))
    class="kw">return(class="kw">false);
class=class="str">"cmt">//---
  class="type">MqlTick tick;
  if(SymbolInfoTick(Symbol(),tick))
    text_edit.SetValue(class="type">class="kw">string(tick.bid));
class=class="str">"cmt">//--- 将对象添加到对象组的通用数组
  CWndContainer::AddToElementsArray(w_number,text_edit);

挂单面板的手数切换逻辑

GUI 创建收尾时,系统通过 CHARTEVENT_CUSTOM+ON_END_CREATE_GUI 事件批量初始化两套开关按钮。m_switch_button 数组共 6 个元素,下标 0/3 绑 LOT、1/2/4/5 绑 POINTS;m_p_switch_button 扩展到 12 个元素,规律是每 3 个一组里第 1 个(0、3、6、9)设 LOT,其余两个设 POINTS。 随后在事件回调里用 ButtonSwitch 逐个绑定联动参数:LOT 类按钮关联 PERC_DEPO,POINTS 类关联 PRICE。你在 MT5 里改这组映射,就能让面板按仓位百分比或点数两种模式切换显示。 挂单手数切换函数 LotPendingSwitch 用循环处理 4 组挂单。当检测到 lparam 命中 m_p_switch_button[3*i] 且按钮处于按下态,对应编辑框 m_lot_edit[i+2] 被重设:小数位 0、步进 1、最大值 100、最小值 1,并默认填 "2"。外汇与贵金属波动剧烈,这类一键切手数功能仅降低操作摩擦,不预示任何收益,实际风险仍由持仓者自担。

MQL5 / C++
if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
  {
   SetButtonParam(m_switch_button[class="num">0],LOT);
   SetButtonParam(m_switch_button[class="num">1],POINTS);
   SetButtonParam(m_switch_button[class="num">2],POINTS);
   SetButtonParam(m_switch_button[class="num">3],LOT);
   SetButtonParam(m_switch_button[class="num">4],POINTS);
   SetButtonParam(m_switch_button[class="num">5],POINTS);
   SetButtonParam(m_p_switch_button[class="num">0],LOT);
   SetButtonParam(m_p_switch_button[class="num">1],POINTS);
   SetButtonParam(m_p_switch_button[class="num">2],POINTS);
   SetButtonParam(m_p_switch_button[class="num">3],LOT);
   SetButtonParam(m_p_switch_button[class="num">4],POINTS);
   SetButtonParam(m_p_switch_button[class="num">5],POINTS);
   SetButtonParam(m_p_switch_button[class="num">6],LOT);
   SetButtonParam(m_p_switch_button[class="num">7],POINTS);
   SetButtonParam(m_p_switch_button[class="num">8],POINTS);
   SetButtonParam(m_p_switch_button[class="num">9],LOT);
   SetButtonParam(m_p_switch_button[class="num">10],POINTS);
   SetButtonParam(m_p_switch_button[class="num">11],POINTS);
  }
   ButtonSwitch(m_p_switch_button[class="num">0],lparam,LOT,PERC_DEPO);
   ButtonSwitch(m_p_switch_button[class="num">1],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">2],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">3],lparam,LOT,PERC_DEPO);
   ButtonSwitch(m_p_switch_button[class="num">4],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">5],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">6],lparam,LOT,PERC_DEPO);
   ButtonSwitch(m_p_switch_button[class="num">7],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">8],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">9],lparam,LOT,PERC_DEPO);
   ButtonSwitch(m_p_switch_button[class="num">10],lparam,POINTS,PRICE);
   ButtonSwitch(m_p_switch_button[class="num">11],lparam,POINTS,PRICE);
class="type">void CFastTrading::LotPendingSwitch(class="type">long lparam)
{
  for(class="type">int i=class="num">0; i<class="num">4; i++)
  {
   if(lparam==m_p_switch_button[class="num">3*i].Id())
   {
    if(m_p_switch_button[class="num">3*i].IsPressed())
     {
      m_lot_edit[i+class="num">2].SetDigits(class="num">0);
      m_lot_edit[i+class="num">2].StepValue(class="num">1);
      m_lot_edit[i+class="num">2].MaxValue(class="num">100);
      m_lot_edit[i+class="num">2].MinValue(class="num">1);
      m_lot_edit[i+class="num">2].SetValue(class="type">class="kw">string(class="num">2));

◍ 挂单面板的手数与止盈联动逻辑

这段面板代码处理两类输入控件的动态绑定:手数编辑框(m_lot_edit)随交易品种成交量规则刷新,止盈编辑框(m_tp_edit)随开关状态在市价与固定值间切换。 手数框分支里,若条件不成立则强制 2 位小数、步长取 SYMBOL_VOLUME_STEP、上下限取 SYMBOL_VOLUME_MIN / MAX,并把初值设为最小手数。外汇与贵金属品种的最小成交量常为 0.01,最大可能到数百手,面板若不按 SymbolInfoDouble 拉取就会填错导致下单价拒。 TakePendingSwitch 里对 4 组挂单按钮轮询,lparam 命中 m_p_switch_button[3*i+1] 且被按下时,用 SymbolInfoTick 取实时 ask,把 m_tp_edit 精度设为 _Digits、步长设为 _Point、值设为当前卖价。开关弹起则退回 0 位整数、步长 1、默认值 150——这里 150 是占位点数,实盘需按品种波动重设。 让小布替你跑这套:把上面两段直接贴进 CFastTrading 类,编译后拖一个 XAUUSD 到图表,点挂单开关看止盈框是否在 ask 与 150 之间跳变,手数框下限是否显 0.01。

MQL5 / C++
m_lot_edit[i+class="num">2].GetTextBoxPointer().Update(true);
    }
    else
    {
      m_lot_edit[i+class="num">2].SetDigits(class="num">2);
      m_lot_edit[i+class="num">2].StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP));
      m_lot_edit[i+class="num">2].MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN));
      m_lot_edit[i+class="num">2].MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX));
      m_lot_edit[i+class="num">2].SetValue(class="type">class="kw">string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)));
      m_lot_edit[i+class="num">2].GetTextBoxPointer().Update(true);
    }
  }
}
class="type">void CFastTrading::TakePendingSwitch(class="type">long lparam)
{
  for(class="type">int i=class="num">0; i<class="num">4; i++)
  {
    if(lparam==m_p_switch_button[class="num">3*i+class="num">1].Id())
    {
      if(m_p_switch_button[class="num">3*i+class="num">1].IsPressed())
      {
        class="type">MqlTick tick;
        if(SymbolInfoTick(Symbol(),tick))
        {
          m_tp_edit[i+class="num">2].SetDigits(_Digits);
          m_tp_edit[i+class="num">2].StepValue(_Point);
          m_tp_edit[i+class="num">2].SetValue(class="type">class="kw">string(tick.ask));
          m_tp_edit[i+class="num">2].GetTextBoxPointer().Update(true);
        }
      }
      else
      {
        m_tp_edit[i+class="num">2].SetDigits(class="num">0);
        m_tp_edit[i+class="num">2].StepValue(class="num">1);
        m_tp_edit[i+class="num">2].SetValue(class="type">class="kw">string(class="num">150));
        m_tp_edit[i+class="num">2].GetTextBoxPointer().Update(true);
      }
    }
  }
}

「挂单止损输入框的两种状态切换」

在自建的快捷交易面板里,止损(Stop)相关的挂单输入框通常不是固定格式。代码里用 StopPendingSwitch 处理按钮 m_p_switch_button[3*i+2] 的按下状态:按下时把第 i+2 个编辑框切到价格模式,松开则回到点数模式。 按下状态分支中,先取当前品种 tick,再把 m_sl_edit[i+2] 的小数位设为 _Digits、步进设为 _Point,并填入 tick.bid 字符串,最后强制重绘文本框。这里直接绑定卖价,意味着 Buy Stop 的止损参考会跟随实时报价跳动。 未按下时,编辑框小数位归零、步进为 1、默认值写死为字符串 "150",也就是默认 150 点止损。外汇与贵金属杠杆高,这种写死默认值在极端跳空时可能触发远超预期的亏损,实盘前建议改为读取品种止损最小限制。 下面这段是原文核心逻辑,逐行拆完可直接丢进 MT5 的 EA 面板类里验证: void CFastTrading::StopPendingSwitch(long lparam) —— 函数接收图表事件参数 lparam for(int i=0; i<4; i++) —— 遍历 4 个挂单子面板 if(lparam==m_p_switch_button[3*i+2].Id()) —— 匹配到第 i 组里的止损切换按钮 if(m_p_switch_button[3*i+2].IsPressed()) —— 按钮处于按下(价格模式) MqlTick tick; —— 声明报价结构 if(SymbolInfoTick(Symbol(),tick)) —— 成功获取当前品种 tick m_sl_edit[i+2].SetDigits(_Digits); —— 编辑框小数位对齐品种精度 m_sl_edit[i+2].StepValue(_Point); —— 最小步进为一个 point m_sl_edit[i+2].SetValue(string(tick.bid)); —— 填入当前买价 m_sl_edit[i+2].GetTextBoxPointer().Update(true); —— 立即刷新控件 else —— 按钮弹起(点数模式) m_sl_edit[i+2].SetDigits(0); —— 无小数 m_sl_edit[i+2].StepValue(1); —— 步进为 1 点 m_sl_edit[i+2].SetValue(string(150)); —— 默认 150 点 m_sl_edit[i+2].GetTextBoxPointer().Update(true); —— 刷新 同文件里 LotPendingSwitch、TakePendingSwitch 也用同样 lparam 分发,说明三种切换共用一套点击事件路由,改其中一个的匹配索引要同步核对另两个,否则面板会串功能。

MQL5 / C++
class="type">void CFastTrading::StopPendingSwitch(class="type">long lparam)
{
   for(class="type">int i=class="num">0; i<class="num">4; i++)
   {
      if(lparam==m_p_switch_button[class="num">3*i+class="num">2].Id())
      {
         if(m_p_switch_button[class="num">3*i+class="num">2].IsPressed())
         {
            class="type">MqlTick tick;
            if(SymbolInfoTick(Symbol(),tick))
            {
               m_sl_edit[i+class="num">2].SetDigits(_Digits);
               m_sl_edit[i+class="num">2].StepValue(_Point);
               m_sl_edit[i+class="num">2].SetValue(class="type">class="kw">string(tick.bid));
               m_sl_edit[i+class="num">2].GetTextBoxPointer().Update(true);
            }
         }
         else
         {
            m_sl_edit[i+class="num">2].SetDigits(class="num">0);
            m_sl_edit[i+class="num">2].StepValue(class="num">1);
            m_sl_edit[i+class="num">2].SetValue(class="type">class="kw">string(class="num">150));
            m_sl_edit[i+class="num">2].GetTextBoxPointer().Update(true);
         }
      }
   }
}

挂单参数混用价格与点数时的下单分支

这段逻辑处理的是 BuyStop 挂单的下发,核心难点在于 TP/SL 输入既可能是绝对价格(double),也可能是点数(int),由两个开关按钮 m_p_switch_button[1] 和 [2] 分别控制 TP、SL 的模式。 手数计算先走分支:若满足百分比手数条件,调用 LotPercent 按当前 SYMBOL_ASK 与编辑框 m_lot_edit[2] 的值算量;否则用 NormalizeLot 对输入手数做规范化。价格 pr 直接取 m_pr_edit[0] 的字符串值转 double。 四个 if-else 分支穷举了开关组合:两个都按下的话 TP、SL 均作 double 处理;都没按下则均作 int(点数);只按其一则对应项用 double、另一项用 int。每种情况都调 m_trade.PlaceBuyStop(lot, Symbol(), pr, sl, tp, m_magic_number),成功即 return true。 在 MT5 里验证时,注意 PlaceBuyStop 的 sl/tp 类型必须和账户执行模式匹配——若经纪商要求止损止盈用绝对价,传 int 点数会触发 4756 错误(TRADE_RETCODE_INVALID_STOPS)。外汇与贵金属杠杆高,挂单未成交前也占用保证金计算,误设大点数 SL 可能让可用保证金瞬间吃紧。

MQL5 / C++
lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">2].GetValue()));
   else
      lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">2].GetValue()));
   class=class="str">"cmt">//---
   class="type">class="kw">double pr=class="type">class="kw">double(m_pr_edit[class="num">0].GetValue());
   class=class="str">"cmt">//---
   if(m_p_switch_button[class="num">1].IsPressed() && m_p_switch_button[class="num">2].IsPressed())
   {
       class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">2].GetValue());
       class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">2].GetValue());
       if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
          class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">1].IsPressed() && !m_p_switch_button[class="num">2].IsPressed())
   {
       class="type">int tp=class="type">int(m_tp_edit[class="num">2].GetValue());
       class="type">int sl=class="type">int(m_sl_edit[class="num">2].GetValue());
       if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
          class="kw">return(true);
   }
   else if(m_p_switch_button[class="num">1].IsPressed() && !m_p_switch_button[class="num">2].IsPressed())
   {
       class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">2].GetValue());
       class="type">int sl=class="type">int(m_sl_edit[class="num">2].GetValue());
       if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
          class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">1].IsPressed() && m_p_switch_button[class="num">2].IsPressed())
   {
       class="type">int tp=class="type">int(m_tp_edit[class="num">2].GetValue());
       class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">2].GetValue());
       if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
          class="kw">return(true);
   }
   }
   class="kw">return(class="kw">false);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::SetSellStopOrder(class="type">int id,class="type">long lparam)
{
   if(!m_orders_windows[class="num">1].IsVisible())
      class="kw">return(class="kw">false);

◍ 挂卖停单的触发与手数止盈止损分支

这段逻辑处理的是 SellStop 挂单的下发:既可以通过自定义按钮点击事件(ON_CLICK_BUTTON 且 lparam 匹配卖停按钮 Id)触发,也可以在键盘按下 KEY_2 时直接发起,相当于给交易者留了鼠标和快捷键两条路。 手数计算先看第 3 个开关按钮是否按下:按下就按账户净值百分比换算(LotPercent 配合 SYMBOL_BID 与编辑框数值),没按就走 NormalizeLot 做标准手数规范化。挂单价取自 m_pr_edit[1],也就是界面上那格价格输入。 止盈止损的处理分四种组合,由第 4、5 两个开关决定 TP/SL 是价格绝对值(double)还是点数(int):两者都按是价格,都不按是点数,只开其一则对应项用价格、另一项用点数。外汇与贵金属杠杆高,这种混合模式若填错单位,可能瞬间放大滑点与穿仓风险。 每一支分支都调用 m_trade.PlaceSellStop,成功就 return(true) 结束事件,全没成则落到最外层的 return(false)。开 MT5 把这段塞进面板事件函数,按 F2 编译后点按钮或敲 2 键即可验证挂单是否按预期出现。

MQL5 / C++
if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sellstop_execute.Id()) ||
      (id==CHARTEVENT_KEYDOWN && lparam==KEY_2))
  {
   class=class="str">"cmt">//---
   class="type">class="kw">double lot;
   if(m_p_switch_button[class="num">3].IsPressed())
      lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[class="num">3].GetValue()));
   else
      lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">3].GetValue()));
   class=class="str">"cmt">//---
   class="type">class="kw">double pr=class="type">class="kw">double(m_pr_edit[class="num">1].GetValue());
   class=class="str">"cmt">//---
   if(m_p_switch_button[class="num">4].IsPressed() && m_p_switch_button[class="num">5].IsPressed())
   {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">3].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">3].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">4].IsPressed() && !m_p_switch_button[class="num">5].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">3].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">3].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   else if(m_p_switch_button[class="num">4].IsPressed() && !m_p_switch_button[class="num">5].IsPressed())
   {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">3].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">3].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">4].IsPressed() && m_p_switch_button[class="num">5].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">3].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">3].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
  }
  class="kw">return(class="kw">false);

「挂单面板里 BuyLimit 的触发与手数分支」

在自建交易面板里,BuyLimit 挂单不是点一下就发,而是先卡两道门:右侧挂单窗口必须可见,否则直接返回 false;触发源要么是自定义按钮事件(按钮 Id 匹配),要么是键盘 KEY_3。 手数计算分两条路:第 6 号开关按下时走 LotPercent,按账户净值百分比换算;否则走 NormalizeLot 做标准手数规范化。价格取第 2 号价格编辑框的字符串转 double,这一步没做越界检查,实盘里填错就可能挂到离谱价位。 TP/SL 的单位切换靠第 7、8 号开关组合:两个都按 = 双 double(价格绝对值);都不按 = 双 int(点数);只按 7 = TP 用价格、SL 用点数;只按 8 = TP 用点数、SL 用价格。四种组合各自调一次 PlaceBuyLimit,成功才返 true。 外汇和贵金属杠杆高,这种面板挂单若开关状态看错,SL 点数和价格混用会瞬间放大风险,上 MT5 前先把 m_p_switch_button 索引和你面板上实际控件对一遍。

MQL5 / C++
class="type">bool CFastTrading::SetBuyLimitOrder(class="type">int id,class="type">long lparam)
{
   if(!m_orders_windows[class="num">1].IsVisible())
      class="kw">return(class="kw">false);
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buylimit_execute.Id()) ||
       (id==CHARTEVENT_KEYDOWN && lparam==KEY_3))
   {
      class=class="str">"cmt">//---
      class="type">class="kw">double lot;
      if(m_p_switch_button[class="num">6].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[class="num">4].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">4].GetValue()));
      class=class="str">"cmt">//---
      class="type">class="kw">double pr=class="type">class="kw">double(m_pr_edit[class="num">2].GetValue());
      class=class="str">"cmt">//---
      if(m_p_switch_button[class="num">7].IsPressed() && m_p_switch_button[class="num">8].IsPressed())
      {
         class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">4].GetValue());
         class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">4].GetValue());
         if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number))
            class="kw">return(true);
      }
      else if(!m_p_switch_button[class="num">7].IsPressed() && !m_p_switch_button[class="num">8].IsPressed())
      {
         class="type">int tp=class="type">int(m_tp_edit[class="num">4].GetValue());
         class="type">int sl=class="type">int(m_sl_edit[class="num">4].GetValue());
         if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number))
            class="kw">return(true);
      }
      else if(m_p_switch_button[class="num">7].IsPressed() && !m_p_switch_button[class="num">8].IsPressed())
      {
         class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">4].GetValue());
         class="type">int sl=class="type">int(m_sl_edit[class="num">4].GetValue());
         if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number))
            class="kw">return(true);
      }
      else if(!m_p_switch_button[class="num">7].IsPressed() && m_p_switch_button[class="num">8].IsPressed())
      {
         class="type">int tp=class="type">int(m_tp_edit[class="num">4].GetValue());

挂单触发里的手数换算与止损分叉

这段逻辑处理的是 Sell Limit 窗口之外另一种挂单——在图表按键或快捷键 KEY_4 触发时,进入 Sell Stop 的下单分支。先判断订单窗口 m_orders_windows[1] 是否可见,不可见直接返回 false,避免盲下。 手数计算分两条路:开关按钮 m_p_switch_button[9] 按下时走 LotPercent,按账户净值百分比换算;否则走 NormalizeLot,把编辑框 m_lot_edit[5] 里的字符串直接规范成可下单手数。价格取 m_pr_edit[3],与 Buy Limit 那套对称。 止损止盈的类型组合才是容易踩坑的地方。按钮 10 和 11 同时按下,tp/sl 都按 double 从编辑框读;都不按,则强制转 int,意味着你填 15.5 也会被截成 15 点。一按一不按时,tp 用 double、sl 用 int,三组分支最终都调 PlaceSellStop。外汇和贵金属杠杆高,这种隐式类型截断可能让止损比预期偏宽,实盘前建议在 MT5 策略测试器里打印 sl 实际值。

MQL5 / C++
class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">4].GetValue());
if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number))
   class="kw">return(true);
   }
  }
  class="kw">return(class="kw">false);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CFastTrading::SetSellLimitOrder(class="type">int id,class="type">long lparam)
{
  if(!m_orders_windows[class="num">1].IsVisible())
   class="kw">return(class="kw">false);
  if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_selllimit_execute.Id()) ||
     (id==CHARTEVENT_KEYDOWN && lparam==KEY_4))
  {
   class=class="str">"cmt">//---
   class="type">class="kw">double lot;
   if(m_p_switch_button[class="num">9].IsPressed())
      lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[class="num">5].GetValue()));
   else
      lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[class="num">5].GetValue()));
   class=class="str">"cmt">//---
   class="type">class="kw">double pr=class="type">class="kw">double(m_pr_edit[class="num">3].GetValue());
   class=class="str">"cmt">//---
   if(m_p_switch_button[class="num">10].IsPressed() && m_p_switch_button[class="num">11].IsPressed())
   {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">5].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">5].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">10].IsPressed() && !m_p_switch_button[class="num">11].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">5].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">5].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   else if(m_p_switch_button[class="num">10].IsPressed() && !m_p_switch_button[class="num">11].IsPressed())
   {
      class="type">class="kw">double tp=class="type">class="kw">double(m_tp_edit[class="num">5].GetValue());
      class="type">int sl=class="type">int(m_sl_edit[class="num">5].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))

◍ 挂单与键盘微调价格的事件分发

面板里卖stop挂单的触发靠两个开关按钮的状态组合:当第11号被按下、第10号松开时,从编辑框读入 tp 和 sl,再调 PlaceSellStop 下卖stop单,成功就返回 true。这种用按钮矩阵隔离订单类型的写法,能避免市价单和挂单逻辑互相串。 OnEvent 是图表事件的统一入口,里面依次调用 SetBuyOrder、SetSellOrder 以及六类挂单设置函数(buy/sell stop、buy/sell limit)。也就是说,无论你在面板上点按钮还是敲键盘,所有下单意图都汇总到这一处再分流。 ArrowSwitch 负责方向键改值:遍历前4个价格编辑框,若正处焦点且收到 KEY_UP,价格按 StepValue()*10 往上跳;KEY_DOWN 则同幅度往下减。手数框另走一套循环(0到5),逻辑对称。外汇与贵金属杠杆高,这类快捷键改值若步长设太大,可能瞬间把止损拉到危险距离,实盘前先在策略测试器里用最小账户验证。

MQL5 / C++
class="kw">return(true);
   }
   else if(!m_p_switch_button[class="num">10].IsPressed() && m_p_switch_button[class="num">11].IsPressed())
   {
      class="type">int tp=class="type">int(m_tp_edit[class="num">5].GetValue());
      class="type">class="kw">double sl=class="type">class="kw">double(m_sl_edit[class="num">5].GetValue());
      if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         class="kw">return(true);
   }
   }
   class="kw">return(class="kw">false);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件应答                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam)
{
class=class="str">"cmt">//---
   SetBuyOrder(id,lparam);
   SetSellOrder(id,lparam);
class=class="str">"cmt">//---
   SetBuyStopOrder(id,lparam);
   SetSellStopOrder(id,lparam);
   SetBuyLimitOrder(id,lparam);
   SetSellLimitOrder(id,lparam);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::ArrowSwitch(class="type">long lparam)
{
class=class="str">"cmt">//--- 价格
   for(class="type">int i=class="num">0; i<class="num">4; i++)
   {
      if(m_pr_edit[i].GetTextBoxPointer().TextEditState())
      {
         if(lparam==KEY_UP)
         {
            class=class="str">"cmt">//--- 取新值
            class="type">class="kw">double value=StringToDouble(m_pr_edit[i].GetValue())+m_pr_edit[i].StepValue()*class="num">10;
            class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
            m_pr_edit[i].SetValue(DoubleToString(value),class="kw">false);
         }
         else if(lparam==KEY_DOWN)
         {
            class=class="str">"cmt">//--- 取新值
            class="type">class="kw">double value=StringToDouble(m_pr_edit[i].GetValue())-m_pr_edit[i].StepValue()*class="num">10;
            class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
            m_pr_edit[i].SetValue(DoubleToString(value),class="kw">false);
         }
      }
   }
class=class="str">"cmt">//--- 手数
   for(class="type">int i=class="num">0; i<class="num">6; i++)
   {
      if(m_lot_edit[i].GetTextBoxPointer().TextEditState())
      {
         if(lparam==KEY_UP)
         {
            class=class="str">"cmt">//--- 取新值

「手敲方向键批量改仓位的坑」

在自定义面板里用键盘上下键调手数、止盈止损,是常见的交互省事法。上面这段逻辑给手数框绑了 KEY_UP / KEY_DOWN:每次按键不是按 StepValue 走一步,而是直接乘 10 跳,比如 StepValue 是 0.01,按一下就加减 0.1 手。 止盈止损部分同样套路,6 个 m_tp_edit 和 6 个 m_sl_edit 各自判断 TextEditState(),只有当前正在编辑的框才响应按键,避免误改别的行。乘 10 的跳幅对黄金这类点值敏感的品种,可能让 SL 瞬间跨过 broker 的止损距离限制,MT5 会静默拒绝 SetValue 后的下单。 跑起来前先把 StepValue 和乘子确认清楚。若你面板手数最小步长 0.01,乘 10 后单键跨度 0.1,连按 5 下就到 0.5 手,外汇和贵金属都是高杠杆高风险,仓位跳太快容易在情绪下单时吃亏。

MQL5 / C++
class="type">class="kw">double value=StringToDouble(m_lot_edit[i].GetValue())+m_lot_edit[i].StepValue()*class="num">10;
class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
m_lot_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 else if(lparam==KEY_DOWN)
 {
 class=class="str">"cmt">//--- 取新值
 class="type">class="kw">double value=StringToDouble(m_lot_edit[i].GetValue())-m_lot_edit[i].StepValue()*class="num">10;
 class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
 m_lot_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 }
class=class="str">"cmt">//--- 止盈,止损
 for(class="type">int i=class="num">0; i<class="num">6; i++)
 {
 class=class="str">"cmt">//---
 if(m_tp_edit[i].GetTextBoxPointer().TextEditState())
{
 if(lparam==KEY_UP)
 {
 class=class="str">"cmt">//--- 取新值
 class="type">class="kw">double value=StringToDouble(m_tp_edit[i].GetValue())+m_tp_edit[i].StepValue()*class="num">10;
 class=class="str">"cmt">//--- Increase by one step and check for exceeding the limit
 m_tp_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 else if(lparam==KEY_DOWN)
 {
 class=class="str">"cmt">//--- 取新值
 class="type">class="kw">double value=StringToDouble(m_tp_edit[i].GetValue())-m_tp_edit[i].StepValue()*class="num">10;
 class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
 m_tp_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 }
 class=class="str">"cmt">//---
 if(m_sl_edit[i].GetTextBoxPointer().TextEditState())
{
 if(lparam==KEY_UP)
 {
 class=class="str">"cmt">//--- 取新值
 class="type">class="kw">double value=StringToDouble(m_sl_edit[i].GetValue())+m_sl_edit[i].StepValue()*class="num">10;
 class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
 m_sl_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 else if(lparam==KEY_DOWN)
 {
 class=class="str">"cmt">//--- 取新值
 class="type">class="kw">double value=StringToDouble(m_sl_edit[i].GetValue())-m_sl_edit[i].StepValue()*class="num">10;
 class=class="str">"cmt">//--- 逐步递增并检查是否超出限制
 m_sl_edit[i].SetValue(DoubleToString(value),class="kw">false);
 }
 }
 }
}
class=class="str">"cmt">//--- 按键
 if(id==CHARTEVENT_KEYDOWN)
 {
 class=class="str">"cmt">//---

一键平掉当前图表盈利持仓的实现

在 MT5 面板里常会看到「平盈利」按钮,其背后就是 CFastTrading 类的 CloseAllMarketProfit 方法。它同时绑定了自定义按钮点击(m_order_button[2])和键盘 C 键,触发后只处理当前图表品种、且魔幻数字匹配的持仓。 函数先取 PositionsTotal() 得到持仓总数,再从尾到头倒序遍历。之所以倒序,是因为平仓后持仓池索引会变化,正向删容易漏单。 内层判断很直接:magic==m_magic_number 且 symbol==Symbol() 且 profit+swap>0 才进入平仓分支。把掉期算进盈利阈值,能避免表面上正浮盈、实际被 swap 吃成负的小品种(如部分贵金属隔夜单)被误平。外汇与贵金属杠杆高,swap 在跨周时会跳变,这类逻辑最好先在策略测试器跑一遍。 下面这段是方法头部与遍历骨架,逐行拆完你就能照抄到自己的 EA 里。

MQL5 / C++
class="type">void CFastTrading::CloseAllMarketProfit(class="type">int id,class="type">long lparam)
{
  if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">2].Id()) ||
     (id==CHARTEVENT_KEYDOWN && lparam==KEY_C))
  {
   class=class="str">"cmt">//--- 声明请求和结果
   class="type">MqlTradeRequest request;
   class="type">MqlTradeResult  result;
   class="type">int total=PositionsTotal(); class=class="str">"cmt">// 持仓数量
   class=class="str">"cmt">//--- 遍历持仓
   for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
   {
     class=class="str">"cmt">//--- 订单参数
     class="type">class="kw">ulong  position_ticket=PositionGetTicket(i);                                                  class=class="str">"cmt">// 单号
     class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL);                                    class=class="str">"cmt">// 品种
     class="type">int    digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS);                          class=class="str">"cmt">// 小数点位数
     class="type">class="kw">ulong  magic=PositionGetInteger(POSITION_MAGIC);                                              class=class="str">"cmt">// 魔幻数字
     class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME);                                             class=class="str">"cmt">// 持仓量
     class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);                class=class="str">"cmt">// 持仓类型
     class="type">class="kw">double profit=PositionGetDouble(POSITION_PROFIT);
     class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP);
     class=class="str">"cmt">//--- 如果魔幻数字匹配
     if(magic==m_magic_number)
      if(position_symbol==Symbol())
       if(profit+swap>class="num">0)
       {

◍ 用市价反手平仓的底层请求构造

在 MT5 的 EA 里做一键平仓,核心是先清空 MqlTradeRequest 与 MqlTradeResult 两个结构体,再填参数。下面这段逻辑针对已有持仓做市价反向成交,等于平掉原单:买仓用 BID 价发 SELL,卖仓用 ASK 价发 BUY。 请求里 deviation 设成 5 点,意味着允许成交价与当前报价偏离最多 5 个点;magic 用类内成员变量区分策略批次。若 OrderSend 返回 false,直接把 GetLastError 的错误码 PrintFormat 出来,方便在专家日志里抓连不上交易服务器或禁盘时段的问题。 外汇与贵金属属高杠杆品种,这类一键脚本在重大数据行情可能滑点远超 5 点,触发 partial fill 或 reject,实盘前务必在策略测试器用真实点差回放验证。

MQL5 / C++
ZeroMemory(request);
ZeroMemory(result);
request.action   =TRADE_ACTION_DEAL;      class=class="str">"cmt">// 交易操作类型
request.position =position_ticket;        class=class="str">"cmt">// 单号
request.symbol   =position_symbol;        class=class="str">"cmt">// 品种
request.volume   =volume;                 class=class="str">"cmt">// 持仓量
request.deviation=class="num">5;                      class=class="str">"cmt">// 允许的价格偏离
request.magic    =m_magic_number;         class=class="str">"cmt">// 魔幻数字
if(type==POSITION_TYPE_BUY)
{
   request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID);
   request.type =ORDER_TYPE_SELL;
}
else
{
   request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK);
   request.type =ORDER_TYPE_BUY;
}
if(!OrderSend(request,result))
   PrintFormat("OrderSend error %d",GetLastError());

「用持仓遍历砍掉亏损加隔夜费的单」

在 MT5 的 EA 里想自动清理「浮亏加隔夜费」的仓位,核心是先拿到全部持仓再逐个筛。下面这段从持仓池倒序遍历,把每张单的 ticket、品种、魔术数字、成交量、盈亏与 swap 一次性读出来,方便后续判断。 筛选逻辑只认三件事:魔术数字匹配本 EA、品种等于当前图表品种、且 profit+swap 小于 0。外汇与贵金属杠杆高,swap 在周三夜往往翻三倍,这类单子持仓越久越容易把小亏拖成大坑,自动识别有必要。 命中条件后把 request 和 result 先 ZeroMemory 清空,再填 TRADE_ACTION_DEAL、position、symbol、volume,等于告诉交易服务器「按原量平掉这张指定单」。开 MT5 把 m_magic_number 换成你 EA 的实际值,就能直接验证平仓请求是否构造正确。

MQL5 / C++
class="type">MqlTradeRequest request;
class="type">MqlTradeResult   result;
ZeroMemory(request);
ZeroMemory(result);
class="type">int total=PositionsTotal(); class=class="str">"cmt">// 持仓数量
class=class="str">"cmt">//--- 遍历持仓
for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
{
   class=class="str">"cmt">//--- 订单参数
   class="type">class="kw">ulong  position_ticket=PositionGetTicket(i);                                                                     class=class="str">"cmt">// 单号
   class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL);                                                       class=class="str">"cmt">// 品种
   class="type">int    digits=(class="type">int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS);                                             class=class="str">"cmt">// 小数点位数
   class="type">class="kw">ulong  magic=PositionGetInteger(POSITION_MAGIC);                                                                 class=class="str">"cmt">// 魔幻数字
   class="type">class="kw">double volume=PositionGetDouble(POSITION_VOLUME);                                                               class=class="str">"cmt">// 持仓量
   class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);                                  class=class="str">"cmt">// 持仓类型
   class="type">class="kw">double profit=PositionGetDouble(POSITION_PROFIT);
   class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP);
   class=class="str">"cmt">//--- 如果魔幻数字匹配
   if(magic==m_magic_number)
     if(position_symbol==Symbol())
       if(profit+swap<class="num">0)
       {
         class=class="str">"cmt">//--- 将请求和结果值清零
         ZeroMemory(request);
         ZeroMemory(result);
         class=class="str">"cmt">//--- 设置操作参数
         request.action   =TRADE_ACTION_DEAL;     class=class="str">"cmt">// 交易操作类型
         request.position =position_ticket;       class=class="str">"cmt">// 单号
         request.symbol   =position_symbol;       class=class="str">"cmt">// 品种
         request.volume   =volume;                class=class="str">"cmt">// 持仓量

平仓与撤单的请求构造细节

反向平仓时价格取价逻辑很直接:持有多单就用 SYMBOL_BID 发 SELL,持有空单则用 SYMBOL_ASK 发 BUY,deviation 设 5 点容忍滑点,magic 绑定自建标识便于后续过滤。 OrderSend 失败不要静默吞掉,PrintFormat 打出 GetLastError 代码,在 MT5 Experts 日志里能直接定位是报价过期还是禁止交易时段。外汇与贵金属杠杆高,这类即时平仓在重大数据行情可能滑点超 5 点而被拒。 撤挂单函数 DeleteAllPending 支持按钮点击(自定义图表事件)或快捷键 R 触发,遍历 OrdersTotal 倒序取 ticket,只处理 magic 匹配的单子,避免误删手动单。 request 和 result 先用 ZeroMemory 清结构体,是防止上一次交易残留字段污染新请求的基本动作,开 MT5 把这段抄进 EA 初始化逻辑就能验证。

MQL5 / C++
request.deviation=class="num">5;                     class=class="str">"cmt">// 允许的价格偏离
request.magic     =m_magic_number;           class=class="str">"cmt">// 魔幻数字
class=class="str">"cmt">//--- 根据持仓类型设置订单价格和类型
if(type==POSITION_TYPE_BUY)
{
   request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID);
   request.type =ORDER_TYPE_SELL;
}
else
{
   request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK);
   request.type =ORDER_TYPE_BUY;
}
class=class="str">"cmt">//--- 发送请求
if(!OrderSend(request,result))
   PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// 如果无法发出请求,则输出错误代码

class="type">void CFastTrading::DeleteAllPending(class="type">int id,class="type">long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">4].Id()) ||
       (id==CHARTEVENT_KEYDOWN && lparam==KEY_R))
   {
      class=class="str">"cmt">//--- 声明并初始化交易请求和交易结果
      class="type">MqlTradeRequest request;
      class="type">MqlTradeResult  result;
      class=class="str">"cmt">//--- 遍历所有已下挂单
      for(class="type">int i=OrdersTotal()-class="num">1; i>=class="num">0; i--)
      {
         class="type">class="kw">ulong  order_ticket=OrderGetTicket(i);            class=class="str">"cmt">// 单号
         class="type">class="kw">ulong  magic=OrderGetInteger(ORDER_MAGIC);        class=class="str">"cmt">// 魔幻数字
         class=class="str">"cmt">//--- 如果魔幻数字匹配
         if(magic==m_magic_number)
         {
            class=class="str">"cmt">//--- 请求和结果值清零
            ZeroMemory(request);

◍ 挂单与持仓的批量撤删逻辑

在 MT5 的 EA 事件回调里,图表交互触发的交易动作被拆成了几组函数调用:先处理市价单的买卖挂单,再处理止盈止损类的挂单,最后集中处理平仓与删除。 上面这段截取自 OnEvent 方法,能看到 CloseAllMarketProfit、CloseAllMarketLoss 与 DeleteAllPending 三个调用被归在同一组,说明盈利单、亏损单和所有挂单的清理可以由同一个图表事件 ID 驱动。 删除挂单的核心请求长这样:先用 ZeroMemory 清空 result 结构体,再把 request.action 设为 TRADE_ACTION_REMOVE,并填入待删单的 order_ticket,最后用 OrderSend 发出。若返回失败,GetLastError 会给出具体错误码,方便在日志里定位是单号失效还是交易环境拒绝。 外汇与贵金属品种在重大数据前常出现挂单被服务器批量拒绝的现象,跑这段代码前建议在策略测试器用历史 tick 验证 ticket 有效性,避免实盘误删。

MQL5 / C++
ZeroMemory(result);
class=class="str">"cmt">//--- 设置操作参数
request.action= TRADE_ACTION_REMOVE;           class=class="str">"cmt">// 交易操作类型
request.order = order_ticket;                  class=class="str">"cmt">// 单号
class=class="str">"cmt">//--- 发送请求
if(!OrderSend(request,result))
   PrintFormat("OrderSend error %d",GetLastError()); class=class="str">"cmt">// 如果无法发出请求,则输出错误代码
}
   }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 图表事件应答                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CFastTrading::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam)
{
class=class="str">"cmt">//---
  SetBuyOrder(id,lparam);
  SetSellOrder(id,lparam);
class=class="str">"cmt">//---
  SetBuyStopOrder(id,lparam);
  SetSellStopOrder(id,lparam);
  SetBuyLimitOrder(id,lparam);
  SetSellLimitOrder(id,lparam);
class=class="str">"cmt">//---
  CloseAllMarketProfit(id,lparam);
  CloseAllMarketLoss(id,lparam);
class=class="str">"cmt">//---
  DeleteAllPending(id,lparam);

「把文件丢进终端根目录就完事」

这套指标与脚本的附件已打包齐全,按原目录结构摆放即可。核心动作只有一件:把 MQL5 文件夹整体复制到 MT5 的终端根目录里,覆盖同名路径,不用逐个文件挑拣。 定位终端根目录最快的办法是在 MT5 里按 Ctrl+Shift+D,会直接弹出数据文件夹;也可以右键托盘图标走「打开数据文件夹」菜单一律可达。 放错位置是最常见的运行失败原因——若脚本没出现在导航器里,先核对 MQL5 是否落在终端根而非随便一个盘符。外汇与贵金属品种波动剧烈,加载任何第三方工具前建议先用模拟账户验证稳定性。

最后一句大实话

整套多币种信号监视做下来,代码、库、界面都只是骨架,真正决定账户起伏的还是你按下确认键那一瞬间。外汇和贵金属杠杆高、跳空频繁,再漂亮的面板也替你扛不住黑天鹅,信号提示之后留三秒给自己做人工过滤更稳妥。 论坛里那句「得先会用手算才用计算器」不是废话:机器人把筛选跑完了,最终进出场权责仍在你。把这篇的 MQL5 文件夹路径和库依赖在 MT5 根目录核对一遍,能跑通再谈优化,比追新版本号实在。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到你的快捷成交热键命中率和误操作分布,你专注决策而不是复盘按键日志。

常见问题

账户余额百分比在两种订单里都解决的是「按风险敞口下单」而非「按固定手数拍脑袋」,实现层抽成同一个输入控件就能少写一套校验。
目前小布盯盘负责的是盘面诊断与异常提醒,快捷下单仍建议在 MT5 内用本篇方案绑定热键,两者互补不冲突。
点数在跳空后相对价位更稳,但跨品种点值不同,实盘里倾向用点数做模板、用价位做最终确认。
EA 类脚本放 Experts 目录才能被终端识别加载,子文件夹只是避免和别的工程混文件,编译路径不受影响。
很多编译失败其实源于改了文件没重启终端或路径指错,收尾补这一节是把「能跑」的最后一公里补上。