创建一个人工交易助手·进阶篇
🛠️

创建一个人工交易助手·进阶篇

(2/3)· 从功能清单到 CAppDialog 派生类,手把手把按钮、输入框和位图按钮落进真实控件

进阶 第 2/3 篇
很多人工交易者把面板当成静态贴图,结果每次重开终端都要手填一遍止损点数和仓位。把参数持久化进控件对象,重启后直接续上,才是顺手工具该有的样子。

用 Create 画出趋势线对象的底层参数

在 MT5 的自定义图形对象类里,Create 方法决定了趋势线、通道等元素能否挂到指定图表上。它的参数顺序很死:先 chart ID,再对象 name,再 subwin 子窗口序号,最后两组坐标点 (x1,y1) 与 (x2,y2)。 很多人直接在主图(subwin=0)画,却忘了 name 必须全局唯一,否则后一次 Create 会静默覆盖前一条线。开 MT5 用脚本跑一下同名对象就能复现这个坑。 下面这段声明就是该类方法的原型,八个入参一个都不能省:

MQL5 / C++
class="kw">virtual class="type">bool Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int x2,class="kw">const class="type">int y2);

◍ 按类型分批初始化四组对象

Creat(...) 里要做的事很直接:把前面声明的全部对象逐一初始化。按前文统计,共声明了 4 类对象、合计 45 个,所以最清晰的做法是拆成 4 个私有过程,每类对象各走一个初始化流程,类内的初始化函数都放在 private 段。 有人会想把对象塞进数组统一管,但那样变量名就丢了和具体功能的语义绑定,后面调对象反而更绕。这里为了可读性和维护简单,刻意不用对象数组,45 个对象就老老实实按名字逐个 new。 你在 MT5 里写自己的 EA 时,如果对象数量上到几十个,也可以照这个思路分组初始化——先数清楚类型与总数,再决定要不要牺牲一点语法糖换代码透明度。外汇与贵金属品种波动剧烈,这类结构开销对实盘风控无直接影响,但乱了结构容易在紧急改参数时出错。

「面板文字标签的差异化与自动命名」

做交易面板时,CLabel 负责把信息文字画到图上。先分清哪些属性因元件而异:对象名、显示文本、坐标、相对锚点的对齐方式。其余统一的属性走函数参数,避免每个标签都手写一堆重复配置。 对象名必须全局唯一,这是 MT5 图表对象的硬约束。我倾向在程序里自动生成,而不是手动传字符串——用基类前缀拼上 'Label' 再加当前 OBJ_LABEL 总数,序列号自然不撞车。 文本、x/y 坐标和对齐方式用参数传入。对齐专门做了枚举 label_align:left=-1、right=1、center=0,读代码时一眼能懂。函数还收 chart、subwindow 和对象引用,方便挂到指定子窗口。 下面这段是实际创建逻辑。先拼名,再调父类 Create,失败直接返 false;接着写 Text,再用 ObjectSetInteger 按枚举映射 ANCHOR 常量;最后 Add 进容器。复制去 MT5 跑,注意 ObjectsTotal 的第三个参数用 OBJ_LABEL 才只数标签类。

MQL5 / C++
class="type">class="kw">string name=m_name+"Label"+(class="type">class="kw">string)ObjectsTotal(chart,-class="num">1,OBJ_LABEL);
  enum label_align
    {
      left=-class="num">1,
      right=class="num">1,
      center=class="num">0
    };
class="type">bool CTradePanel::CreateLabel(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CLabel &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,label_align align)
  {
   class=class="str">"cmt">// 所有对象必须使用独立的名称
   class="type">class="kw">string name=m_name+"Label"+(class="type">class="kw">string)ObjectsTotal(chart,-class="num">1,OBJ_LABEL);
   class=class="str">"cmt">//--- 调用创建函数
   if(!object.Create(chart,name,subwindow,x,y,class="num">0,class="num">0))
     {
      class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 设置文字
   if(!object.Text(text))
     {
      class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 把文字在对话框网格中对齐
   ObjectSetInteger(chart,object.Name(),OBJPROP_ANCHOR,(align==left ? ANCHOR_LEFT_UPPER : (align==right ? ANCHOR_RIGHT_UPPER : ANCHOR_UPPER)));
   class=class="str">"cmt">//--- 把对象加到控件中
   if(!Add(object))
     {
      class="kw">return class="kw">false;
     }
   class="kw">return true;
  }

用 CButton 搭可点按钮的初始化坑点

CButton 用来画带文字的长方按钮,我们面板上的下单、平仓标准键就归它管。它继承自父类,文字默认居中,所以创建时不用再手动对齐,只要把按钮宽高通过参数传进去就行。 和纯标签不同,按钮有「按下 / 抬起」两种状态,被按下的还能锁死。实际面板里我们不希望按钮卡在锁定态,所以在初始化阶段统一关掉 Locking,并强制 Pressed(false) 回到抬起。 下面这段 CreateButton 是实打实能贴进 CTradePanel 类用的。名字用 m_name+"Button" 拼上当前 OBJ_BUTTON 总数,保证每个按钮独立不重名;若任意一步 Create / Text / Pressed / Add 返回 false,整个函数直接退回 false,调用方拿不到按钮对象就得查图表子窗口权限。 外汇与贵金属杠杆高,面板只是辅助发单,按钮逻辑出错可能漏单或重复发单,上 MT5 前先开策略测试器点一遍。

MQL5 / C++
class="type">bool CTradePanel::CreateButton(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CButton &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size)
  {
   class=class="str">"cmt">// 所有对象必须使用独立的名称
   class="type">class="kw">string name=m_name+"Button"+(class="type">class="kw">string)ObjectsTotal(chart,-class="num">1,OBJ_BUTTON);
   class=class="str">"cmt">//--- 调用创建函数
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
       class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 设置文字
   if(!object.Text(text))
     {
       class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 设置按钮的解锁标志
   object.Locking(class="kw">false);
   class=class="str">"cmt">//--- 把按钮标志设为抬起
   if(!object.Pressed(class="kw">false))
     {
       class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 把对象加到控件中
   if(!Add(object))
     {
       class="kw">return class="kw">false;
     }
   class="kw">return true;
  }

◍ 用 CEdit 搭可改写的输入格

在 MT5 面板里做交易量、止损止盈点数、风险百分比的录入框,CEdit 是最直接的控件类。它和按钮、标签的创建流程一致,但初始化时必须显式声明文字对齐方式,否则默认排布会让数字看起来歪斜难读。 要记住一点:CEdit 收发的一切内容都被当成字符串。你往框里塞 0.1 手或 50 点,得先走 DoubleToString 或 IntegerToString 转成文本,直接传数值类型会在编译或显示环节出问题。 按钮有按下抬起两种态,CEdit 没有,但它支持运行期锁成只读。实例里我们故意设 ReadOnly(false),让用户在面板跑起来后仍能改参数,这一步漏掉就会得到一排灰掉改不动的框。 下面这段 CreateEdit 是实际可用的封装:用 ObjectsTotal 拼唯一名避免重名,居中对齐,关掉只读,再挂进控件树。复制到 EA 的 panel 类里,把 m_name 换成你的前缀就能直接编译验证。

MQL5 / C++
class="type">bool CTradePanel::CreateEdit(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CEdit &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size)
  {
  class=class="str">"cmt">// 所有对象必须使用独立的名称
  class="type">class="kw">string name=m_name+"Edit"+(class="type">class="kw">string)ObjectsTotal(chart,-class="num">1,OBJ_EDIT);
  class=class="str">"cmt">//--- 调用创建函数
  if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
    {
      class="kw">return class="kw">false;
    }
  class=class="str">"cmt">//--- 设置文字
  if(!object.Text(text))
    {
      class="kw">return class="kw">false;
    }
  class=class="str">"cmt">//--- 在编辑框中对齐文字
  if(!object.TextAlign(ALIGN_CENTER))
    {
      class="kw">return class="kw">false;
    }
  class=class="str">"cmt">//--- 把只读标志设为 class="kw">false
  if(!object.ReadOnly(class="kw">false))
    {
      class="kw">return class="kw">false;
    }
  class=class="str">"cmt">//--- 把对象加到控件中
  if(!Add(object))
    {
      class="kw">return class="kw">false;
    }
  class="kw">return true;
  }

「用位图按钮替代标准控件」

做自定义交易面板时,标准按钮在视觉传达上往往不够直观。CBmpButton 类走的是图形对象路线,用 BMP 图片表达按下 / 抬起状态,而不是靠文字标签。在我们的面板里,它承担了三类交互:止损 / 获利 / 风险单位切换的单选(资金、点数或百分比)、控制止损获利水平显示的复选框,以及增减交易量的步进按钮。 和 CButton 相比,操作逻辑接近,核心差异在于状态切换传的是图形资源而非字符串。为让成品单文件发布,所有按钮图片都通过 #resource 预编译指令嵌进 exe,路径指向 Include\Controls\res 下的六张图(RadioButton 开 / 关、CheckBox 开 / 关、SpinInc、SpinDec)。 注意一个行为细节:除手数加减按钮外,其余元件都需保持按下或抬起的锁定状态,因此初始化函数要额外传入 lock 参数。创建函数本身必须声明在类的 private 段,外部只调封装好的 CreateBmpButton。 下面这段是实际建按钮的核心,坐标用 Y_STEP 和 CONTROLS_BUTTON_SIZE 推算,名称靠 ObjectsTotal 计数防重名: #resource "\\Include\\Controls\\res\\RadioButtonOn.bmp" #resource "\\Include\\Controls\\res\\RadioButtonOff.bmp" #resource "\\Include\\Controls\\res\\CheckBoxOn.bmp" #resource "\\Include\\Controls\\res\\CheckBoxOff.bmp" #resource "\\Include\\Controls\\res\\SpinInc.bmp" #resource "\\Include\\Controls\\res\\SpinDec.bmp" //+------------------------------------------------------------------+

//创建图形(BMP)按钮

//+------------------------------------------------------------------+ bool CTradePanel::CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock) { // 所有对象必须使用独立的名称 string name=m_name+"BmpButton"+(string)ObjectsTotal(chart,-1,OBJ_BITMAP_LABEL); //--- 计算坐标 uint y1=(uint)(y-(Y_STEP-CONTROLS_BUTTON_SIZE)/2); uint y2=y1+CONTROLS_BUTTON_SIZE; //--- 调用创建函数 if(!object.Create(m_chart_id,name,m_subwin,x-CONTROLS_BUTTON_SIZE,y1,x,y2)) return(false); //--- 把 BMP 图片复制到按钮状态 if(!object.BmpNames(BmpOFF,BmpON)) return(false); //--- 把对象加到控件中 if(!Add(object)) return(false); //--- 把锁定标志设为 true object.Locking(lock); //--- 成功 return(true); } private: //--- 创建标签对象 bool CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align); //--- 创建按钮 bool CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size); //--- 创建编辑框对象 bool CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size); //--- 创建图形按钮 逐行看:前六行 #resource 把六张位图编译进程序;CreateBmpButton 先用 ObjectsTotal 拼出唯一名,避免图表上重名冲突;y1/y2 按步进和尺寸反推命中区;object.Create 用位图标签画出来;BmpNames 绑定关 / 开两态图;Add 入控件树;Locking(lock) 决定它会不会记住被点下的状态。手数加减传 false,单选复选传 true,面板状态就不会乱跳。 开 MT5 把 res 目录放对位置直接编译,改 CONTROLS_BUTTON_SIZE 就能试不同点击热区;外汇贵金属面板涉及下单,实盘前务必在策略测试器跑通,这类自定义控件在高分屏 DPI 下可能偏位,属于已知需自查的风险点。

MQL5 / C++
class="macro">#resource "\\Include\\Controls\\res\\RadioButtonOn.bmp"
class="macro">#resource "\\Include\\Controls\\res\\RadioButtonOff.bmp"
class="macro">#resource "\\Include\\Controls\\res\\CheckBoxOn.bmp"
class="macro">#resource "\\Include\\Controls\\res\\CheckBoxOff.bmp"
class="macro">#resource "\\Include\\Controls\\res\\SpinInc.bmp"
class="macro">#resource "\\Include\\Controls\\res\\SpinDec.bmp"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建图形(BMP)按钮                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTradePanel::CreateBmpButton(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CBmpButton &object,class="kw">const class="type">uint x,class="kw">const class="type">uint y,class="type">class="kw">string BmpON,class="type">class="kw">string BmpOFF,class="type">bool lock)
  {
  class=class="str">"cmt">// 所有对象必须使用独立的名称
  class="type">class="kw">string name=m_name+"BmpButton"+(class="type">class="kw">string)ObjectsTotal(chart,-class="num">1,OBJ_BITMAP_LABEL);
  class=class="str">"cmt">//--- 计算坐标
  class="type">uint y1=(class="type">uint)(y-(Y_STEP-CONTROLS_BUTTON_SIZE)/class="num">2);
  class="type">uint y2=y1+CONTROLS_BUTTON_SIZE;
  class=class="str">"cmt">//--- 调用创建函数
  if(!object.Create(m_chart_id,name,m_subwin,x-CONTROLS_BUTTON_SIZE,y1,x,y2))
     class="kw">return(class="kw">false);
  class=class="str">"cmt">//--- 把 BMP 图片复制到按钮状态
  if(!object.BmpNames(BmpOFF,BmpON))
     class="kw">return(class="kw">false);
  class=class="str">"cmt">//--- 把对象加到控件中
  if(!Add(object))
     class="kw">return(class="kw">false);
  class=class="str">"cmt">//--- 把锁定标志设为 true
  object.Locking(lock);
class=class="str">"cmt">//--- 成功
  class="kw">return(true);
  }
class="kw">private:
  class=class="str">"cmt">//--- 创建标签对象
  class="type">bool           CreateLabel(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CLabel &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,label_align align);
  class=class="str">"cmt">//--- 创建按钮
  class="type">bool           CreateButton(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CButton &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size);
  class=class="str">"cmt">//--- 创建编辑框对象
  class="type">bool           CreateEdit(class="kw">const class="type">long chart,class="kw">const class="type">int subwindow,CEdit &object,class="kw">const class="type">class="kw">string text,class="kw">const class="type">uint x,class="kw">const class="type">uint y,class="kw">const class="type">uint x_size,class="kw">const class="type">uint y_size);
  class=class="str">"cmt">//--- 创建图形按钮
把面板状态交给小布盯盘同步
这些控件初始化和参数存储逻辑,小布盯盘的 AIGC 已内置到品种页的侧边助手,打开对应外汇或贵金属页面就能看到实时面板态,你只管调策略。

常见问题

优先确认 Dialog.mqh 已包含且 Creat() 在 OnInit 返回前调用,控件对象声明顺序错乱也会导致空指针。
多是 Edit 控件回写事件没绑定到 CTradePanel 的更新函数,需在初始化过程里显式挂接。
目前小布盯盘内置的是通用面板态展示,自定义 mq5 的私有参数需按它暴露的接口传,否则只显示标准字段。
CBmpButton 走位图重绘,视觉反馈更自由但占用稍高;CButton 原生绘制更轻,适合纯文字触发。
不必,用交易品种属性做分支即可,但贵金属点差跳空频繁,仓位计算要额外加风险概率约束。