开发回放系统(第 44 部分):Chart Trader 项目(三)·综合运用
(3/3)· 前两步搭好的浮窗模板在高频调用下暴露效率短板,本篇用新类一次建模彻底改写
浮动风险面板对象的构造与析构
在 MT5 里做一个跟随图表的悬浮风险面板,核心是先绑定鼠标指针和杠杆参数。下面这段构造函数把外部传入的短名、鼠标对象、杠杆、止盈止损金额一次性灌进成员结构,并强制把日线交易标记和窗口最大化标记置为真。 杠杆参数做了防呆:若传入小于 0 则回退为 1 倍,避免负数杠杆让后续点值换算崩掉。止盈止损先取绝对值,再经 FinanceToPoints 与 PointsToFinance 两轮转换,保证内部存储的是规范化后的金融量。 窗口尺寸在 CreateWindowRAD(115, 64, 170, 210) 写死:左上角偏移 (115,64),宽 170 高 210 像素。若指标名校验不通过,直接 SetUserError 抛错,面板不会继续初始化。 析构时反向清理:删掉图表上的子对象、删掉临时模板文件,最后 delete 鼠标指针。外汇与贵金属杠杆品种波动剧烈,这种悬浮面板只是辅助展示,实际下单仍属高风险行为,参数错了可能误导仓位。
class="kw">public: C_ChartFloatingRAD(class="type">class="kw">string szShortName, C_Mouse *MousePtr, class="kw">const class="type">int Leverage, class="kw">const class="type">class="kw">double FinanceTake, class="kw">const class="type">class="kw">double FinanceStop) :C_Terminal() { m_Mouse = MousePtr; m_Info.Leverage = (Leverage < class="num">0 ? class="num">1 : Leverage); m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage)); m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage)); m_Info.IsDayTrade = true; m_Info.IsMaximized = true; if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); CreateWindowRAD(class="num">115, class="num">64, class="num">170, class="num">210); AdjustTemplate(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ ~C_ChartFloatingRAD() { ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart); FileDelete(m_Info.szFileNameTemplate); class="kw">delete m_Mouse; }
「鼠标拖拽时先锁死图表滚动」
在自定义指标或 EA 里接管 CHARTEVENT_MOUSE_MOVE 后,如果不加干预,左键拖拽对象的同时图表会跟着滚轮逻辑乱跑,坐标采样直接失真。上面这段分发函数给出了一个实用做法:左键按下且命中标题区(MSG_TITLE_IDE)时,先判断静态变量 sx 是否仍为 -1,若是则立即关闭 CHART_MOUSE_SCROLL。 sx、sy 初始化为 -1,本身就是「还没记录过抓取起点」的哨兵值;一旦进入拖拽分支且 sx<0,说明这是第一次落点,此时 ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false) 会把当前图表窗口的鼠标滚动禁掉。外汇与贵金属杠杆高、图表缩放误触代价大,这个开关能降低手动标注时的视图跳动概率。 验证方式很直接:把下面代码塞进你的鼠标事件分支,在 MT5 上用左键拖一下面板标题,观察图表是否还能用滚轮平移——锁成功的话,滚动会被冻住直到你自行恢复。
class="type">void DispatchMessage(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="kw">static class="type">int sx = -class="num">1, sy = -class="num">1; class="type">int x, y, mx, my; class="kw">static eObjectsIDE it = MSG_NULL; class="kw">switch (id) { case CHARTEVENT_MOUSE_MOVE: if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) { class="kw">switch (it = CheckMousePosition(x = (class="type">int)lparam, y = (class="type">int)dparam)) { case MSG_TITLE_IDE: if (sx < class="num">0) { ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
◍ 拖拽后如何回写图表坐标
当用户在 MT5 图表上拖动自定义对象,系统捕获到鼠标位移后,需要把相对偏移换算回对象的绝对距离属性,否则下一次重绘会跳回原位。 sx 与 sy 记录的是拖动起始时鼠标坐标与对象原锚点的差(x - m_Info.x、y - m_Info.y),这段在 149–151 行完成。真正落盘发生在 152–153 行:用当前鼠标坐标减掉 sx/sy 得到新距离,且仅当结果大于 0 才写回 OBJPROP_XDISTANCE / OBJPROP_YDISTANCE,同时更新 m_Info.x、m_Info.y 供后续逻辑复用。 负值不写回是个隐蔽但关键的设计——它能避免对象被拖到图表可视区左/上边界外后属性变成负数导致渲染异常。实盘里若你改这段逻辑,建议先在 EURUSD 的 M5 上拖几次确认边界行为,外汇品种点差跳变时坐标刷新可能有 1–2 帧延迟,属正常现象,贵金属杠杆更高波动更剧,测试时更需注意高风险。
sx = x - m_Info.x; sy = y - m_Info.y; } if ((mx = x - sx) > class="num">0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx); if ((my = y - sy) > class="num">0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my); class="kw">break; } }else { if (it != MSG_NULL) { if (it != MSG_NULL) { class="kw">switch (it) {
面板按钮如何切换图表尺寸与交易日模式
自定义面板收到 MSG_MAX_MIN 消息时,会翻转 m_Info.IsMaximized 布尔值,借此在折叠与展开之间切换。 紧接着用 ObjectSetInteger 改写图表对象 OBJPROP_YSIZE:最大化时高度锁为 210 像素,否则回退到标题区高度 m_Info.Regions[MSG_TITLE_IDE].h 再加 6 像素的边距。 MSG_DAY_TRADE 分支只做一件事——翻转 m_Info.IsDayTrade,控制面板是否按交易日过滤显示,不改动尺寸。 处理完消息后把 it 置为 MSG_NULL 并调用 AdjustTemplate(),确保界面状态与内部标志同步;外汇与贵金属波动剧烈,这类界面切换仅影响观察效率,不预示任何方向。 [CODE]case MSG_MAX_MIN: m_Info.IsMaximized = (m_Info.IsMaximized ? false : true); ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); break; case MSG_DAY_TRADE: m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true); break; } it = MSG_NULL; AdjustTemplate(); [/CODE] 逐行拆解:第1行进入最大化/最小化消息分支;第2行用三元运算符切换 IsMaximized 状态;第3行按状态设置图表对象纵向像素高度,210 与标题高+6 是两个实测锚点;第4行跳出分支。第5行进入日交易开关分支;第6行翻转 IsDayTrade;第7行直接跳出。右花括号结束 switch;随后 it 清为空消息,AdjustTemplate 重排模板。 开 MT5 把 210 改成 160 试一下,面板最大高度会明显压缩,适合多图表平铺盯贵金属。
「鼠标滚轮与坐标复位的处理分支」
这段代码片段处在图表事件回调的某条分支里,核心动作只有两件:放开图表鼠标滚轮限制,并把上一次记录的光标坐标清零。 第175行调用 ChartSetInteger,对 GetInfoTerminal().ID 指定的图表句柄写入 CHART_MOUSE_SCROLL 为 true,意味着允许用户用滚轮平移或缩放该图表,默认禁用的滚动在这之后恢复可用。 第176行紧跟着把 sx 与 sy 同时赋值为 -1,这是常见的哨兵值写法,代表「当前没有有效的鼠标坐标缓存」。后续绘制或拾取逻辑读到 -1 时,应当跳过基于坐标的计算,而不是沿用旧位置。 外汇与贵金属品种在 MT5 上波动频繁,这类交互状态复位若漏写,可能在切换周期或重绘时让指标误判鼠标锚点,概率上会引发显示错位而非交易信号错误。
{
ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
sx = sy = -class="num">1;
}
}
class="kw">break;
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
};
class=class="str">"cmt">//+------------------------------------------------------------------+◍ 画得少,看得清
用 OBJ_CHART 套模板这套路子,核心优势就一条:直接借 MetaTrader 5 原生控件拼界面,省掉大段 MQL5 绘图与交互代码。实测下来,虚拟图表对象的行为已和真实图表高度接近,你开 MT5 拖一个 OBJ_CHART 进去加载模板就能复现。 初看乱,是因为这套对象嵌套逻辑对你而言是新的;练过几遍后,它能塞进不少自定义面板场景。当前版本还有个小缺陷——用户没法直接在 Chart Trade 里敲数值。 下一篇会补上这个输入口,实现起来不枯燥。外汇与贵金属波动剧烈、杠杆高风险大,这类界面方案只解决交互效率,不替你过滤风险。