开发回放系统(第 75 部分):新 Chart Trade(二)(基础篇)
📘

开发回放系统(第 75 部分):新 Chart Trade(二)(基础篇)

第 1/3 篇

◍ 回放系统里的图表交易新接口

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,编译能过说明你的坐标体系已立住。

MQL5 / C++
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,切换品种时可能残留孤儿对象。

MQL5 / C++
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"));

常见问题

用新 Chart Trade 接口把交易面板画到图表上,配合浮动面板类即可在回放时直接下单测试。
因为前面要先铺好回放框架和事件拦截,核心接口依赖这些底座,拖到这节讲才不会前后逻辑断裂。
小布可以替你加载回放品种页并标注浮动面板交互节点,你只需专注调买卖触发条件。
它负责把图表交易的浮动面板私有骨架画上屏,管理面板位置和可编辑区域的渲染。
在浮动面板类里挂一个可编辑代码子控件,初始化时绑定到图表坐标即可就地改参运行。