从头开始开发智能交易系统(第 9 部分):概念上的飞跃 (II)·综合运用
◍ 解析对象节点时顺手锚定浮动窗坐标
在读取图表对象配置文本时,循环会以 </object> 作为结束标记逐行扫描。遇到 name 字段就把值转大写,再去和预设的 IDE 消息枚举(从 eRESULT 到 eEDIT_STOP)比对,找不到且当前处于浮动模式就直接返回 true,相当于跳过不匹配的对象。
这段逻辑里有个容易忽略的落点:当匹配索引正好等于 m_IDEStruct.Index 时,代码会把窗口坐标加 3 像素偏移写回结构体,Y 方向还额外叠了 def_SizeBarCaption + 3。这说明浮动面板的锚点不是窗口左上角,而是避开标题栏后的内缩位置。
struct IDE_Struct 只存 X、Y、Index 和 IsMaximized 四个字段,非常轻量。你在 MT5 里接这套解析时,若发现浮动窗总是偏出预期几像素,优先查 def_SizeBarCaption 的实际值,外汇与贵金属图表多屏布局下这种偏移会被放大,属高风险调试项。
class="kw">while (m_szLine != "</object>") if (!FileReadLine()) class="kw">return class="kw">false; else { if (m_szLine == "name") { b0 = class="kw">false; StringToUpper(m_szValue); for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++); if (!b0 && m_IsFloating) class="kw">return true; else c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); class=class="str">"cmt">//... The rest of the function... } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct IDE_Struct { class="type">int X, Y, Index; class="type">bool IsMaximized; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class C_ChartFloating { class=class="str">"cmt">// ... Class code ... class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void SetPosition(class="kw">const class="type">int X, class="kw">const class="type">int Y, class="kw">const class="type">int c0) { class=class="str">"cmt">// ... Function code ... if (c0 == m_IDEStruct.Index) { m_IDEStruct.X = m_Win[c0].PosX + class="num">3; m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + class="num">3; m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// ... Class code ... class=class="str">"cmt">//+------------------------------------------------------------------+
浮动面板的锚定与重绘逻辑
在 MT5 自建 IDE 式面板时,先通过 GetIDE_Struct() 把当前窗口的几何状态(坐标、是否最大化)缓存进 m_IDEStruct,后续所有子对象定位都读这份快照,避免每次重绘都去查系统。 Add_RAD_IDE() 负责开一个子窗口:宽高任一 ≤0 直接返回 false,StageLocal01 建图失败也返回 false,只有都过了才套用 \Files\Chart Trade\IDE.tpl 模板,并把 m_IDEStruct.Index 记为 m_MaxCounter。 Resize(int x) 有两种写法。第一版里,浮动对象用 GetIDE_Struct().X/Y 加偏移量设 OBJPROP_XDISTANCE / YDISTANCE;非浮动则只用传入的 x。第二版多了一句判断:IsMaximized 为 false 时,X/Y 偏移还要叠上 Terminal.GetWidth() / GetHeight(),否则面板会黏在屏幕左上角之外。 开 MT5 把第二段 Resize 抄进 EA,故意让 IsMaximized=false 且不给 Terminal.GetWidth() 补偿,能直观看到对象飞出可视区——这就是锚点算漏的典型坑。外汇与贵金属图表上跑这类界面属高风险操作,参数误写可能让对象丢失且难回溯。
class="kw">inline IDE_Struct GetIDE_Struct(class="type">void) class="kw">const { class="kw">return m_IDEStruct; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Add_RAD_IDE(class="type">class="kw">string sz0, class="type">int x, class="type">int y, class="type">int w, class="type">int h) { if ((w <= class="num">0) || (h <= class="num">0)) class="kw">return class="kw">false; if (!StageLocal01(sz0, PERIOD_CURRENT, -class="num">1)) class="kw">return class="kw">false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); m_IDEStruct.Index = m_MaxCounter; StageLocal02(x, y, w, h); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//... The rest of the class code } class="type">void Resize(class="type">int x) { for (class="type">int c0 = class="num">0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); }; class="type">void Resize(class="type">int x) { for (class="type">int c0 = class="num">0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? class="num">0 : Terminal.GetWidth())); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? class="num">0 : Terminal.GetHeight())); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); }; class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam) {
「用鼠标位移触发浮动面板重绘」
在 MT5 自定义图表类里,鼠标移动事件是最廉价的「脏检查」入口。下面这段把上一次光标坐标存进 static 变量,只在像素真正变化时调 Resize(-1),避免每帧无谓重排。 [CODE] static double AccumulatedRoof = 0.0; bool b0; double d0; static int px = -1, py = -1; C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam); if (m_CountObject < eEDIT_STOP) return; switch (id) { case CHARTEVENT_MOUSE_MOVE:
| if ((GetIDE_Struct().X != px) | (GetIDE_Struct().Y != py)) |
|---|
{ px = GetIDE_Struct().X; py = GetIDE_Struct().Y; Resize(-1); } break; //... The rest of the function ... [/CODE] 逐行拆解:第1行 static double AccumulatedRoof 是跨调用保留的累加器,初值 0.0;b0、d0 为局部占位变量。static int px=-1, py=-1 记录「上一帧」鼠标 x/y,初始化为 -1 保证首帧必触发。 DispatchMessage 先透传事件给基类;若编辑对象数不足 eEDIT_STOP 直接 return,不往下走。switch 只抓 CHARTEVENT_MOUSE_MOVE:当新坐标与 px/py 任一不同,就刷新坐标并 Resize(-1) 重算面板尺寸,否则跳过。 实盘注意:外汇、贵金属杠杆高,这类浮动 UI 只影响交互,不改变订单逻辑;在 EURUSD 的 1 分钟图上快速拖鼠标,若每帧都 Resize 可能吃掉 3~5% 的 OnChartEvent 耗时,用此差分法可压到接近 0。开 MT5 把这段塞进你的 C_ChartFloating 派生类,跑一下看像素级位移会不会触发重绘。
class="kw">static class="type">class="kw">double AccumulatedRoof = class="num">0.0; class="type">bool b0; class="type">class="kw">double d0; class="kw">static class="type">int px = -class="num">1, py = -class="num">1; C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam); if (m_CountObject < eEDIT_STOP) class="kw">return; class="kw">switch (id) { case CHARTEVENT_MOUSE_MOVE: if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py)) { px = GetIDE_Struct().X; py = GetIDE_Struct().Y; Resize(-class="num">1); } class="kw">break; class=class="str">"cmt">//... The rest of the function ...
◍ 别急着下结论
这套从问题出发、靠小步迭代收敛到精简可用代码的思路,在 MT5 里确实能少走弯路,但落到外汇和贵金属实盘,价格行为受宏观事件扰动,历史回测顺滑不代表未来概率一致,属于高风险博弈。 另有一个坑得记牢:MT5 更新 3228 之后,用到 clrNONE 颜色的对象会莫名不透明,图表上出现怪异遮罩时,优先用附带的 IDE.tpl(约 10.13 KB)覆盖本地模板修复,而非改 EA 逻辑。 代码能跑通只是起点,真要用于黄金或欧美盘,建议先在策略测试器用 2023 年至今的 tick 数据跑一遍,观察滑点和点差放大时的最小成交单位是否还稳。