开发回放系统(第 75 部分):新 Chart Trade(二)(基础篇)
◍ 回放系统里的图表交易新接口
MetaTrader 5 的回放系统开发推进到第 75 部分,这一节聚焦新 Chart Trade 功能的第二批实现细节。作者 Daniel Jose 在 2025 年 9 月 30 日发布的示例帖,截至统计有 665 次查看、0 条评论,说明该接口仍属早期试水阶段,社区实测反馈尚未铺开。 外汇与贵金属品种在 MT5 历史回放中做图表级下单,天然带高杠杆与滑点风险,用回放验证不等于实盘结论。建议直接开 MT5 终端加载回放示例,观察新接口在 tick 级重放时能否稳定挂单与改价。
为什么核心代码拖到这一节才讲
上篇讲 Chart Trade 指标时,我刻意没把主体代码放出来——直接贴会砍掉一百多行,读者很难看清到底改了什么。这节换了个更轻量的演示思路,专门把主代码拆开看。 如果你打算把这段逻辑塞进 EA 而不是指标里,技术上完全可行,只是要改几处上下文适配。上篇提过的「为什么放指标里更稳」依然成立,但怎么用随你。 下面直接进主代码,MT5 里建个指标工程就能对照跑。
「C_ChartFloatingRAD 怎么把图表交易画上屏」
C_ChartFloatingRAD 类的封装思路很硬核:原先暴露在第 17 行的结构体和枚举被收进私有域,避免变量泄漏到类外。第 18 行起所有内部变量塞进结构体,且屏幕坐标这类数据一律用 16 位 SHORT 而非默认 INT——65535 的上限够 4K 甚至 8K 显示器绘图,还能顺手压缩数据体积。 真正的‘魔法’在第 41 行的 CreateWindowRAD,它全程只被第 230 行调一次,唯一动作是在图表上建一个 OBJ_CHART 对象。有了这个容器,就不用手动堆按钮、图标等图形资源。第 72–133 行的 AdjustTemplate 是核心:第 78 行判断是新建还是复用模板,第 81 行循环按第 17 行枚举的数量定位对象,第 105 行让 C_AdjustTemplate 改模板副本的值,界面元素全在这里被定义和摆位。 代码里你找不到 ButtonCreate 之类显式声明,它只定位并启用既有模板对象。想换布局、字体、结构,不用改一行 MQL5,开 MT5 加载 Chart Trade 模板直接拖就行。第 107 行若模板修改失败,会借第 8 行宏跳去删指标并置错误旗,防止继续执行。 鼠标交互靠第 135 行 CheckMousePosition,命中可点对象就第 145 行返回对应 ID。OBJ_EDIT 文本输入则由第 150、156 行两个过程兜底增删。第 175 行 RestoreState 配第 244 行 SaveState,用单个全局终端变量暂存 OBJ_CHART 刷新前坐标,寿命通常不到 1 秒,只为重启动时不丢买卖按钮状态。外汇/贵金属图表挂这类浮动面板,遇闪崩滑点风险高,验证前先开模拟盘跑通模板加载。
◍ 浮动面板类的私有骨架
要在 MT5 里做随图表浮动的交易面板,先把状态塞进一个私有结构最省心。下面这段类声明把坐标、杠杆、模板文件名和十个交互区枚举一次性收口,避免全局变量满天飞。 类 C_ChartFloatingRAD 私有继承 C_Terminal,内部 eObjectsIDE 枚举了 MSG_LEVERAGE_VALUE 到 MSG_NULL 共 10 个控件 ID,对应杠杆显示、止盈止损输入、买卖按钮等热区。 结构体 st00 用 short 存 x/y/minx/miny 和 Leverage,用 long 存 WinHandle,用 double 存 FinanceTake/FinanceStop——这些都是面板刷新时每帧要读写的字段。嵌套的 st01 数组 Regions[MSG_NULL] 直接按控件 ID 索引,存每个区域的 x/y/w/h、底色、字体名与字号。 把鼠标对象做成指针 C_Mouse *m_Mouse,面板就能在 private 段直接判按钮命中,不用对外暴露接口。开 MT5 新建 EA 把这段抄进 mqh,编译能过说明你的坐标体系已立住。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include "../Auxiliar/C_Mouse.mqh" class="macro">#include "C_AdjustTemplate.mqh" class="macro">#define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A) class="macro">#define macro_CloseIndicator(A) { \ OnDeinit(REASON_INITFAILED); \ SetUserError(A); \ class="kw">return; \ } class C_ChartFloatingRAD : class="kw">private C_Terminal { class="kw">private : enum eObjectsIDE {MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL}; class="kw">struct st00 { class="type">short x, y, minx, miny, Leverage; class="type">class="kw">string szObj_Chart, szObj_Editable, szFileNameTemplate; class="type">long WinHandle; class="type">class="kw">double FinanceTake, FinanceStop; class="type">bool IsMaximized, IsDayTrade, IsSaveState; class="kw">struct st01 { class="type">short x, y, w, h; class="type">color bgcolor; class="type">int FontSize; class="type">class="kw">string FontName; }Regions[MSG_NULL]; }m_Info; C_Mouse *m_Mouse;
在图表里嵌一个可编辑代码窗
想让 MT5 主图旁边浮出一个独立图表对象当「代码编辑器」用,核心就是 CreateWindowRAD 这个函数。它先给对象起名 Chart Trade IDE,再拼一个带「 > Edit」后缀的可编辑子对象名,随后用 OBJ_CHART 类型在坐标 (0,0) 创建,并通过 OBJPROP_XDISTANCE / YDISTANCE 把浮窗挪到 m_Info.x、m_Info.y 指定的偏移位置。 窗口尺寸由入参 w、h 直接写入 OBJPROP_XSIZE / YDISTANCE 对应的 OBJPROP_YSIZE,同时把 OBJPROP_DATE_SCALE 和 OBJPROP_PRICE_SCALE 都设成 false,意味着这个嵌入图表不显示时间轴和价格轴——纯当画布。最后用 OBJPROP_CHART_ID 拿回新图表句柄存进 m_Info.WinHandle,后续所有子控件都挂这个句柄下。 AdjustEditabled 负责把模板里的样式刷进区域结构。bArg 为 true 时只往模板登记 bgcolor / fontsz / fontnm 三个键并置 NULL;为 false 时才从模板读回字符串,经 StringToInteger 转成 color 和 int 写进 m_Info.Regions[c0]。循环上界 MSG_STOP_VALUE 决定要配置多少块界面区域,改这个枚举就能增减 IDE 里的分区数量。 开 MT5 把这段抄进 EA 的 OnInit 之前调用一次 CreateWindowRAD(400, 300),主图左上就会多一块 400×300 的无轴图表;外汇与贵金属杠杆高,这类自定义对象若忘了在 OnDeinit 里 ObjectDelete,切换品种时可能残留孤儿对象。
class="num">040. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">041. class="type">void CreateWindowRAD(class="type">int w, class="type">int h) class="num">042. { class="num">043. m_Info.szObj_Chart = "Chart Trade IDE"; class="num">044. m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit"; class="num">045. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, class="num">0, class="num">0, class="num">0); class="num">046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x); class="num">047. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y); class="num">048. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); class="num">049. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); class="num">050. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, class="kw">false); class="num">051. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, class="kw">false); class="num">052. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); class="num">053. }; class="num">054. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">055. class="type">void AdjustEditabled(C_AdjustTemplate &Template, class="type">bool bArg) class="num">056. { class="num">057. for (eObjectsIDE c0 = class="num">0; c0 <= MSG_STOP_VALUE; c0++) class="num">058. if (bArg) class="num">059. { class="num">060. Template.Add(EnumToString(c0), "bgcolor", NULL); class="num">061. Template.Add(EnumToString(c0), "fontsz", NULL); class="num">062. Template.Add(EnumToString(c0), "fontnm", NULL); class="num">063. } class="num">064. else class="num">065. { class="num">066. m_Info.Regions[c0].bgcolor = (class="type">color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor")); class="num">067. m_Info.Regions[c0].FontSize = (class="type">int) StringToInteger(Template.Get(EnumToString(c0), "fontsz"));