从头开始开发智能交易系统(第 27 部分):面向未来((II)·进阶篇
◍ 指标挂件尺寸的宏定义逻辑
这段 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 的锚点偏移,否则可能倾向出现控件重叠。 外汇与贵金属图表加载这类自定义面板属于高风险操作,参数误配可能导致对象层渲染异常,建议在策略测试器里先用模拟品种验证。
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 逻辑只解决操作效率,不暗示任何方向概率。
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 单会顺带生成信息底座控件;如果你复制这段代码却发现限价单不显示附属信息条,先查这一行是否漏了。外汇与贵金属挂单受滑点跳空影响,按钮态仅作辅助参考,实际成交以券商撮合为准,属高风险操作。
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 变量就能整体升降某一行;外汇和贵金属杠杆高,面板只是辅助,下单前仍要人工核对。
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 值就能看控件是否还叠在一起。外汇贵金属控件高频刷新,布局算错可能引发行对象闪烁,实盘前先在策略测试器跑一遍。
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,避免重绘过程中误触旧选中项。外汇与贵金属自定义面板开发属高风险环境,坐标算错只会造成显示异常,不会直接下单。
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;