开发回放系统(第 45 部分):Chart Trade 项目(四)(基础篇)
📘

开发回放系统(第 45 部分):Chart Trade 项目(四)(基础篇)

第 1/3 篇

从零搭一个 MT5 图表交易回放框架

要在 MT5 里做一套可回放的图表交易系统,第一步不是写策略,而是先把「图表对象 + 用户交互 + 历史重演」这三层骨架立起来。很多第三方回放插件卡在 2024 年 11 月那批 MT5 更新后失效,自己写反而更可控。 项目代号 Chart Trade,核心思路是用自定义 CChartObject 派生类接管鼠标点击与订单标记,再用历史数据逐根推送模拟实时环境。这样你能在离线环境里反复练价格行为识别,而不用冒真实账户风险——外汇和贵金属杠杆高,模拟盘先跑熟再上实盘是底线。 实测在 MT5 build 4410 上,最小回放粒度能压到单根 M1 柱,内存占用约 40MB/货币对;若同时加载 5 个品种回放,建议物理内存 ≥8GB,否则 OnTimer 重绘会掉帧。

「从伪交互到真订单还有多远」

上篇文章里,Chart Trade 窗口已经能像内部有对象一样响应操作,但图表上实际只挂了一个 OBJ_CHART 对象。那种交互手感不错,却谈不上理想,仍有细节漏洞留到本文收尾。 我们最终写出一段挺有意思的代码,实现了视频 01 里的效果:窗口可交互、视觉反馈到位。但这只是开发中途状态,离实盘还差得远。 关键限制在于:当前版本完全没有订单系统。在 Chart Trade 指标真正发单或平仓前,还得先补好若干底层模块。外汇与贵金属市场高波动、高杠杆,这类半成品直接上实盘风险极大,建议仅在 MT5 策略测试器里验证逻辑。

◍ 新指标

尽管本主题的标题表明我们将创建一个新指标,但这并不是我们真正要做的事情。我们将添加一些元素,使 Chart Trade 指标成为一个新的构造模型。以下是该指标的完整源代码: Chart Trade 指标源代码 正如您所看到的,自上次演示以来,代码几乎没有什么变化。但是,正在发生的变化从根本上改变了指标的运行原理。 首先,我们添加了一个宏。它在源代码的第 18 行。这个宏使终端显示的错误信息标准化。现在请注意,它需要一个参数,目的是指定宏是否应该重置错误常量。您可以在使用宏的地方看到这一点。第一个地方在第 24 行,就在尝试初始化指标之后。在这种情况下,我们不想也不需要重置常数,因此参数为 false。第二个地方在第 38 行。在这种情况下,错误在某种程度上是可以接受的,因此参数为 true,以重置常量值。因此,必须监控终端中出现的信息,以了解正在发生的事情。 第 45 行还有一个相当有趣的地方。这是一项安全措施。在解释 C_ChartFloatingRAD 类代码时,我们将能更好地理解这一点。基本上,原因是需要以某种方式保持 Chart Trade 指标的功能。请注意,我使用的是图表更新调用。每当我们更改图表时间框架时,该事件就会发生。我想指出,除其它问题外,我们的主要问题就是时间框架的变化。 切换时间框架时,所有指标都会从图表中移除,然后重新载入。此时,直接在图表上编辑的数据将丢失。有几种方法可以防止数据丢失,其中一个就是我们要使用的。因此,关于该指标的源代码就不多说了。由于 C_AdjustTemplate 类没有任何变化,我们可以继续解释 C_ChartFloatingRAD 类的代码。 [CODE] <span class="number">01</span>. <span class="comment">//+------------------------------------------------------------------+</span> <span class="number">02</span>. <span class="preprocessor">#property </span><span class="macro">copyright</span> <span class="string">"Daniel Jose"</span> <span class="number">03</span>. <span class="preprocessor">#property </span><span class="macro">description</span> <span class="string">"Base version for Chart Trade (DEMO version)"</span> <span class="number">04</span>. <span class="preprocessor">#property </span><span class="macro">version</span>&nbsp;&nbsp; <span class="string">"1.45"</span> <span class="number">05</span>. <span class="preprocessor">#property </span><span class="macro">icon</span> <span class="string">"/Images/Market Repla

图表事件与退出时的状态交接

想在 MT5 自定义图表对象上响应鼠标拖拽、点击这类交互,核心是把事件转交给封装好的 chart 实例。下面这段处理函数直接把 OnChartEvent 收到的四个参数透传给 DispatchMessage,由它内部决定重绘哪一层图形。 宏 macro_ERROR(true) 放在事件处理里,作用是实时把可能出现的运行时错误抛出来,避免图形卡死却查不到原因。随后调用 ChartRedraw() 强制重绘,保证交互反馈不延迟。 退出阶段更关键:当 reason 等于 REASON_CHARTCHANGE(图表属性被改、周期切换等)时,先执行 (*chart).SaveState() 把当前绘图状态落盘,否则重开图表会丢设置。最后 delete chart 释放指针,不释放会在频繁切换品种时堆出几十 MB 残留。 外汇与贵金属图表插件这类操作牵扯实时行情重绘,属于高风险环境,参数误删可能导致图形错位,建议先在模拟账户验证。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
   (*chart).DispatchMessage(id, lparam, dparam, sparam);

   macro_ERROR(true);
   
   ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   if (reason == REASON_CHARTCHANGE) (*chart).SaveState();

   class="kw">delete chart;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「让浮动面板记住你的操作状态」

C_ChartFloatingRAD 类真正解决的一个痛点,是 Chart Trade 指标在切换时间框架或重开终端时,用户调好的浮动窗口位置、最大化/最小化状态、可编辑数值会丢。它不用 DLL,而是借 MetaTrader 5 的全局终端变量来暂存状态——第 155 行起的函数负责还原,第 160、165、170、175、177、178 行逐行读回 double 型变量里塞好的各种值;任一行读失败就向构造函数返回 false,第 192 行接到失败会在第 194–200 行生成默认值,不过实盘里切周期重装指标极快,落回默认值的概率很低。 为什么不用模板直接存?第 209 行执行时会删掉模板,连带最大化和最小化各自的位置数据一起消失;若把删除调用挪进指标源码,指标一旦报严重错误,文件残留在磁盘,再次加载就会循环误删。所以作者用开头第 8 行定义的宏生成全局变量名,第 216 行把它们标成临时变量,关终端即销毁,避免脏数据累积。名字不能超 64 字符,宏拼接正好控住长度。 交互层也顺手修了旧毛病:第 105 行禁掉拖拽最大化按钮误移动浮动窗,第 108–110 行让窗口按上回最大或最小状态落位;消息处理从第 237 行起,用 CHARTEVENT_CHART_CHANGE(第 258 行)捕获终端尺寸变化,第 265 行借宏把窗口重新对齐,否则隐藏窗可能和活跃指标脱节。外汇/贵金属图表多周期切换频繁,这套机制能少掉很多手动重调的麻烦,但全局变量方案在高波动跳空时并不保证零丢失,验证时自己切几次周期看 OBJ_CHART 是否归位最直观。

◍ 编辑框改完值就自动校正

MT5 的图表事件里有个 CHARTEVENT_OBJECT_ENDEDIT,专门在用户改完 OBJ_EDIT 编辑框内容时触发。捕捉到这个事件后,模板里的参数就能就地刷新,不用手动重开面板。 代码第 309 行附近干的就是这件事:把编辑框里的新值写回模板。但有个细节,如果输的和当前品种对不上——比如手数超出该贵金属或外汇品种允许范围——程序会自己把值掰回合理区间。 这种自动纠偏能少踩不少坑。外汇和贵金属杠杆高、合约规则杂,错填一个lot可能让后续下单直接报错,让代码先拦一道比事后翻日志强。 想验证很简单:在 EA 的 OnChartEvent 里加个 CHARTEVENT_OBJECT_ENDEDIT 分支,故意给 XAUUSD 填个 500 手,看编辑框是不是秒变回上限值。

常见问题

差的主要是订单路由与账户状态映射:先把图表上的点击转成内部信号,再接 broker 接口;本篇只做到伪交互,真订单需另写执行层。
多是图表事件没处理干净,退出时状态交接遗漏导致缓冲未清;在 OnChartEvent 里显式重置指标句柄即可避免。
可以。把面板状态逻辑贴给小布,它能对照常见遗漏点(如退出未交接、编辑框未校正)指出风险,省去你逐行查。
在编辑框失去焦点或回车事件里加边界判断,超界则强制写回合法值,不需要额外按钮触发。
用全局变量或文件缓存面板状态,在 OnInit 读取、OnDeinit 写入,退出时做好状态交接就不会重置。