从头开始开发智能交易系统(第 27 部分):面向未来((II)·进阶篇
📘

从头开始开发智能交易系统(第 27 部分):面向未来((II)·进阶篇

第 2/3 篇

◍ 指标挂件尺寸的宏定义逻辑

这段 MQL5 片段用宏把浮动指标(IndicatorFloat)的多种事件视图做了统一切换:关闭时返回 false,正常挂载时依次对 CLOSE、MOVE、EDIT、GROUND、LINE 五类事件调用 macroSwapAtFloat,并把 EDIT 控件设为可写,最后返回 true。 紧接着的 macroCreateIndicator 宏按指标类型决定背景尺寸:结果类(IT_RESULT)背景 100×34,挂单类(IT_PENDING)144×22,其余默认 92×22;编辑框固定 60×14。这些像素值在 MT5 对象坐标系里直接影响面板占位,调参时若改了背景宽高,需要同步核对 LINE 与 EDIT 的锚点偏移,否则可能倾向出现控件重叠。 外汇与贵金属图表加载这类自定义面板属于高风险操作,参数误配可能导致对象层渲染异常,建议在策略测试器里先用模拟品种验证。

MQL5 / C++
class="kw">return class="kw">false;
}
         macroSwapAtFloat(it, EV_CLOSE);
         macroSwapAtFloat(it, EV_MOVE);
         macroSwapAtFloat(it, EV_EDIT);
         macroSwapAtFloat(it, EV_GROUND);
         macroSwapAtFloat(it, EV_LINE);
         m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), class="kw">false);
      }
      class="kw">return true;
   }
class="macro">#undef macroSwapAtFloat
class="macro">#define macroCreateIndicator(A, B, C, D)    {    \
      m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);    \
      m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);    \
      m_BackGround.Size(sz0, (A == IT_RESULT ? class="num">100 : (A == IT_PENDING ? class="num">144 : class="num">92)), (A == IT_RESULT ? class="num">34 : class="num">22));    \
      m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, class="num">0.0);    \
      m_EditInfo1.Size(sz0, class="num">60, class="num">14);

「挂单面板上按状态切换控件」

在 MT5 自定义面板里,常需要根据挂单当前状态决定显示哪种控件:非成交态放一个可拖拽按钮,成交态则换成只读的盈亏编辑框。 下面这段代码就是典型分支:当 A 不等于 IT_RESULT 时,用 Wingdings 字体画一个代号 "u"、字号 17 的按钮,尺寸锁在 21×23 像素;若已到成交态,则建一个 60×14 的编辑框承载利润数据。 宏 macroInfoBase 进一步把品种类型按钮的创建收敛成一行:根据买卖方向自动套用 def_BtnInfoBuy / def_BtnInfoSell 两种预设样式。开 MT5 把这段塞进你的 CApp 类,改 EV_MOVE 和 EV_PROFIT 的挂载名就能直接看到面板元素随状态切换。外汇与贵金属波动剧烈,这类 UI 逻辑只解决操作效率,不暗示任何方向概率。

MQL5 / C++
if (A != IT_RESULT)      {
      m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", class="num">17, C);
      m_BtnMove.Size(sz0, class="num">21, class="num">23);
      }else      {
      m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, class="num">0.0);
      m_EditInfo2.Size(sz0, class="num">60, class="num">14);  }
}

class="macro">#define macroInfoBase(A)      {
      m_BtnInfoType.Create(ticket, sz0 = macroMountName(ticket, A, EV_TYPE), def_BtnInfoBuy, def_BtnInfoSell);

挂单面板的按钮与指标挂载

这段逻辑负责把每笔订单的买卖方向与日内/波段属性,映射到图表上的状态按钮。先通过 SetStateButton 把 m_Selection 里的布尔值写进按钮,bIsBuy 控制方向态、bIsDayTrade 控制周期态,肉眼一眼能分清当前挂的是哪类单。 CreateIndicator 用 switch 分流三种指标类型:IT_TAKE 取森绿/深绿双色,IT_STOP 取砖红/栗色,IT_PENDING 用矢车菊蓝配暗金,并额外挂一个启用态勾选按钮,默认 true 即显示。 注意 macroInfoBase(IT_PENDING) 只在挂单分支调用,意味着只有 pending 单会顺带生成信息底座控件;如果你复制这段代码却发现限价单不显示附属信息条,先查这一行是否漏了。外汇与贵金属挂单受滑点跳空影响,按钮态仅作辅助参考,实际成交以券商撮合为准,属高风险操作。

MQL5 / C++
m_BtnInfoType.SetStateButton(sz0, m_Selection.bIsBuy);
m_BtnInfo_DS.Create(ticket, sz0 = macroMountName(ticket, A, EV_DS), def_BtnDayTrade, def_BtnSwing);
m_BtnInfo_DS.SetStateButton(sz0, m_Selection.bIsDayTrade);
}
class="type">void CreateIndicator(class="type">class="kw">ulong ticket, eIndicatorTrade it)
{
   class="type">class="kw">string sz0;

   class="kw">switch (it)
   {
      case IT_TAKE     : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
      case IT_STOP     : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
      case IT_PENDING:
         macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
         m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
         m_BtnCheck.SetStateButton(sz0, true);
         macroInfoBase(IT_PENDING);

◍ 用宏把持仓面板的纵向坐标一次性对齐

这段逻辑出现在自定义交易面板里,核心是用宏把同一条持仓(ticket)下不同控件在 Y 轴上的位置统一设成变量 y。 先撤销前面用过的 macroInfoBase 和 macroCreateIndicator,再定义 macroSetAxleY(A)。这个宏会一口气把背景块、交易线、关闭按钮、移动按钮(非结果行)、编辑框等全部挂到同一个 y 值上,避免每行控件上下错位。 注意分支:当 A 不等于 IT_RESULT 时,移动按钮按偏移 1 对齐;当 A 等于 IT_RESULT 时,改用 m_EditInfo2 显示盈亏。编辑框 m_EditInfo1 的偏移规则是结果行取 -1、其余取 0,挂单行还会额外摆一个勾选按钮。 开 MT5 把这段宏贴进你的面板类,改 y 变量就能整体升降某一行;外汇和贵金属杠杆高,面板只是辅助,下单前仍要人工核对。

MQL5 / C++
class="macro">#undef macroInfoBase
class="macro">#undef macroCreateIndicator
class="macro">#define macroSetAxleY(A)            {                                                                     \
   m_BackGround.PositionAxleY(macroMountName(ticket, A, EV_GROUND), y);                                    \
   m_TradeLine.PositionAxleY(macroMountName(ticket, A, EV_LINE), y);                                       \
   m_BtnClose.PositionAxleY(macroMountName(ticket, A, EV_CLOSE), y);                                       \
   if (A != IT_RESULT)m_BtnMove.PositionAxleY(macroMountName(ticket, A, EV_MOVE), y, class="num">1);                   \
   else m_EditInfo2.PositionAxleY(macroMountName(ticket, A, EV_PROFIT), y, class="num">1);                             \
   m_EditInfo1.PositionAxleY(macroMountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -class="num">1 : class="num">0));           \
   if (A == IT_PENDING) m_BtnCheck.PositionAxleY(macroMountName(ticket, A, EV_CHECK), y);                  \

「挂单与成交面板的纵向错位逻辑」

在 MT5 自定义控件里,挂单(IT_PENDING)和成交(IT_RESULT)两类信息块共用同一条纵向基准线 y,但文字与箭头按钮不能重叠。代码用三行把按钮和数据集的 Y 轴错开:挂单态偏移 0,成交态向上 8 像素放类型标签、向下 8 像素放数据标签。

[CODE]if ((A == IT_PENDING)(A == IT_RESULT))

{ m_BtnInfoType.PositionAxleY(macroMountName(ticket, A, EV_TYPE), y + (A == IT_PENDING ? 0 : 8)); m_BtnInfo_DS.PositionAxleY(macroMountName(ticket, A, EV_DS), y - (A == IT_PENDING ? 0: 8)); }[/CODE] 逐行拆解:第 1 行判断当前块是挂单或成交才进排版;第 3 行给类型按钮设 Y,成交时 y+8 避免压住主线;第 4 行给数据按钮设 Y,成交时 y-8 对称分布。 横向定位用宏 macroSetAxleX 一次性推多个子控件。背景与交易线对齐 B,平仓键右移 3、编辑框右移 21,移动键在非成交态再右移 80 且挂单额外 +52。 [CODE]#define macroSetAxleX(A, B) \ { \ m_BackGround.PositionAxleX(macroMountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(macroMountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(macroMountName(ticket, A, EV_CLOSE), B + 3); \ m_EditInfo1.PositionAxleX(macroMountName(ticket, A, EV_EDIT), B + 21); \ if (A != IT_RESULT) m_BtnMove.PositionAxleX(macroMountName(ticket, A, EV_MOVE), B + 80 + (A == IT_PENDING ? 52 : 0)); \ }[/CODE] 挂单比成交多 52 像素的移动键空间,是因为挂单需要显示改价按钮。开 MT5 把这套宏贴进面板类,调 B 值就能看控件是否还叠在一起。外汇贵金属控件高频刷新,布局算错可能引发行对象闪烁,实盘前先在策略测试器跑一遍。

MQL5 / C++
if ((A == IT_PENDING) || (A == IT_RESULT))
  {
  m_BtnInfoType.PositionAxleY(macroMountName(ticket, A, EV_TYPE), y + (A == IT_PENDING ? class="num">0 : class="num">8));
  m_BtnInfo_DS.PositionAxleY(macroMountName(ticket, A, EV_DS), y - (A == IT_PENDING ? class="num">0: class="num">8));
  }

class="macro">#define macroSetAxleX(A, B) \
  { \
  m_BackGround.PositionAxleX(macroMountName(ticket, A, EV_GROUND), B); \
  m_TradeLine.PositionAxleX(macroMountName(ticket, A, EV_LINE), B); \
  m_BtnClose.PositionAxleX(macroMountName(ticket, A, EV_CLOSE), B + class="num">3); \
  m_EditInfo1.PositionAxleX(macroMountName(ticket, A, EV_EDIT), B + class="num">21); \
  if (A != IT_RESULT) m_BtnMove.PositionAxleX(macroMountName(ticket, A, EV_MOVE), B + class="num">80 + (A == IT_PENDING ? class="num">52 : class="num">0)); \
  }

挂单与成交条目的界面重绘分支

这段片段处理交易面板里不同条目的横向坐标排布,核心看 A 的状态:当 A 不是挂单时,利润文本控件 m_EditInfo2 的 X 轴锚点落在 B+21 像素处;一旦 A 等于 IT_PENDING,勾选按钮 m_BtnCheck 则移到 B+82。 若 A 处于 IT_PENDING 或 IT_RESULT,类型按钮与数据源按钮的坐标会按状态切换:挂单时 m_BtnInfoType 取 B+100、m_BtnInfo_DS 取 B+118,成交态则两者都回退到 B+82。这种偏移量差异直接决定 MT5 自定义面板上控件是否重叠。 ReDrawAllsIndicator 负责批量重绘,先用 ObjectsTotal 以 OBJ_EDIT 为过滤统计终端编辑框数量(入参 -1 表示当前图表),再把当前选择暂存到 Local 后清空 m_Selection.ticket 为 0,避免重绘过程中误触旧选中项。外汇与贵金属自定义面板开发属高风险环境,坐标算错只会造成显示异常,不会直接下单。

MQL5 / C++
else m_EditInfo2.PositionAxleX(macroMountName(ticket, A, EV_PROFIT), B + class="num">21);
if (A == IT_PENDING) m_BtnCheck.PositionAxleX(macroMountName(ticket, A, EV_CHECK), B + class="num">82);
if ((A == IT_PENDING) || (A == IT_RESULT)) {
m_BtnInfoType.PositionAxleX(macroMountName(ticket, A, EV_TYPE), B + (A == IT_PENDING ? class="num">100 : class="num">82));
m_BtnInfo_DS.PositionAxleX(macroMountName(ticket, A, EV_DS), B + (A == IT_PENDING ? class="num">118 : class="num">82));
}
class=class="str">"cmt">//---
class="type">void ReDrawAllsIndicator(class="type">void)
  {
  C_IndicatorTradeView::st00 Local;
  class="type">int   max = ObjectsTotal(Terminal.Get_ID(), -class="num">1, OBJ_EDIT);
  class="type">class="kw">ulong ticket;
  eIndicatorTrade it;
  eEventType ev;
  
  Local = m_Selection;
  m_Selection.ticket = class="num">0;

常见问题

宏在编译期做文本替换,把纵向坐标、宽度等参数集中到一个头文件,改宏就等于改所有引用处,避免逐个控件手调错位。
优先检查按状态切换控件的可见性与启用分支,常见问题是挂单未成交时把按钮 Disable 了;用打印状态值确认当前走的是哪个重绘分支。
小布可读取你的面板坐标配置并比对宏定义,标出哪条纵向坐标没走统一宏,直接给你改哪一行的高效提示。
用宏把持仓面板纵向坐标一次性对齐,所有条目 y 值引用同一基准宏,后续只调宏里的基准数即可整体平移。
是的,挂单与成交条目要走不同重绘分支;在状态变更事件里显式调用对应分支,否则成交后旧挂单条目会残留不刷。