Chart Trade 指标重构基础:把条件指标变成真正可调用缓冲区的铺垫(基础篇)
📊

Chart Trade 指标重构基础:把条件指标变成真正可调用缓冲区的铺垫(基础篇)

(1/3)· 很多交易者照搬指标代码却不懂缓冲区初始化,导致 EA 调用时信号错位、回放系统卡死

偏理论 第 1/3 篇
把 Chart Trade 当条件指标硬接 EA,常在回放里出现信号延迟或丢失。多数人没意识到,问题出在缓冲区没按标准初始化。不补这一步,后面再复杂的交易逻辑都建在沙地上。

◍ 给 MT5 图表交易加一层回放骨架

要在 MT5 里做图表交易回放,核心是把历史报价流重新喂给自定义图形对象,而不是依赖实时 tick。2024 年 12 月 5 日发布的 Chart Trade 项目第六部分,定位为第 47 期回放系统开发的延续,重点在把下单面板与历史重播引擎解耦。 实际落地时,建议先在脚本里用 CopyRates 把指定品种的历史柱抓取进数组,再用定时器按原节奏回推。这样你开 MT5 加载脚本后,能看到价格按过去某段行情逐步走出来,方便验证手感。外汇与贵金属杠杆高,回放仅用于训练,不等于实盘概率。 最小验证路径:新建 EA 骨架,只做一件事——把 2023 年欧美对 1 小时图的 200 根 K 线回放出来,不挂任何指标。能跑通,回放系统就立住了。

「把 Chart Trade 改造成真指标」

上一篇文章里已经把数据直接打进可执行文件,省掉了单独传数据的麻烦。这一步是后面扩展的基础,但眼下我们要先补完更前置的东西。 本文的目标是把 Chart Trade 指标升级到能跟 EA 配合工作的程度,调用方式跟真连了 EA 一样。之前第 30 篇只是把它当条件指标用,这次要做成带数据缓冲区的正规指标。 缓冲区这套机制在回放系统第 37、38、39 三篇里铺过底,没读过的建议先补,否则很容易卡在底层逻辑上。 改动集中在已有的 C_ChartFloatingRAD 类里,加几处小修改就能顺畅取到所需数据,这也意味着 Chart Trade 指标正式进入收尾阶段。

缓冲区同步才是 Chart Trade 的命门

这一节里指标本体代码几乎没动,真正关键的是把按钮点击从 CHARTEVENT_OBJECT_CLICK 挪到了 CHARTEVENT_MOUSE_MOVE 里处理。类里第 353 行捕获鼠标移动事件,第 354 行记下被点对象,第 376–378 行才是三个交易按钮的逻辑,第 380 行把按钮代号写进缓冲区,第 381 行用当前分时报价数生成一个唯一值——EA 端靠这个唯一值分辨指令批次。 为什么非这么绕?因为 MT5 里指标缓冲区不算指标私有内存,图表删掉指标后内存释放但数据未必清空。重挂指标时 CopyBuffer 可能读到上次残留值,EA 就会误执行:你发卖单它却下买单。原系统用 Click 事件做不到写入/读取两端同步,延迟一来指令就串味。 第 203 行把关键值存成全局终端变量,第 274 行才真正写回,原因就在这:指标重载后内存归零,全局变量能保住状态。另外第 245 行析构函数里做了道闸门——类若被指标使用就删对象,被 EA 读缓冲区时用就拦住,避免误删。 附件里那个测试 EA 只打印不下单,你挂上 Mouse 指标 + Chart Trade 指标 + 该 EA 三件套,点按钮看打印值就能验证通道。少一个组件整个系统哑火,外汇/贵金属这种高杠杆环境里误指令可能直接吃掉本金,验证前务必用模拟盘。

◍ 图表浮窗交易面板的指标骨架

这段源码展示了一个挂在主图窗口、不画线的指标外壳,核心是把鼠标交互和浮窗下单逻辑委托给 C_ChartFloatingRAD 类。它声明了 0 个 plot 但保留了 1 个 buffer,说明图形输出走自定义对象而非常规指标线。 #property indicator_chart_window 让指标直接贴在主图;#property indicator_plots 0 与 indicator_buffers 1 的组合,意味着你能在不干扰K线显示的前提下挂载一个隐藏缓冲。input 里的 user01=1 是杠杆档位,user02=100.1 与 user03=75.4 分别是该面板默认的盈利和止损财务阈值,单位依品种合约而定。 OnInit 中 new C_ChartFloatingRAD 时把 user01~03 直接传入构造器,若 _LastError 不为 0 会打印文件级错误并返回 INIT_FAILED。OnCalculate 仅做一件事:(*chart).MountBuffer 把 m_Buff 交给面板对象托管,然后原样返回 rates_total,计算开销极低。 别把 user02/user03 当硬止损 这两个数值是面板层财务参数,不是经纪商强制平仓线;实盘外汇和贵金属波动剧烈,高杠杆下可能瞬间触发追加保证金,请在 MT5 策略测试器里改这几个 input 感受滑点差异。

MQL5 / C++
class="macro">#class="kw">property description "See the articles for more details."
class="macro">#class="kw">property version   "class="num">1.47"
class="macro">#class="kw">property icon "/Images/Market Replay/Icons/Indicators.ico"
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_plots class="num">0
class="macro">#class="kw">property indicator_buffers class="num">1
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
C_ChartFloatingRAD *chart = NULL;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int      user01 = class="num">1;        class=class="str">"cmt">//Leverage
class="kw">input class="type">class="kw">double   user02 = class="num">100.1;    class=class="str">"cmt">//Finance Take
class="kw">input class="type">class="kw">double   user03 = class="num">75.4;     class=class="str">"cmt">//Finance Stop
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double m_Buff[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
   class="type">bool bErr;

   chart = new C_ChartFloatingRAD("Indicator Chart Trade", new C_Mouse("Indicator Mouse Study"), user01, user02, user03);

   if (bErr = (_LastError != ERR_SUCCESS)) Print(__FILE__, " - [Error]: ", _LastError);

   SetIndexBuffer(class="num">0, m_Buff, INDICATOR_DATA);
   ArrayInitialize(m_Buff, EMPTY_VALUE);

   class="kw">return (bErr ? INIT_FAILED : INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[])
{
   (*chart).MountBuffer(m_Buff, rates_total);

   class="kw">return rates_total;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(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)
{

「图表生命周期与浮动面板的数据骨架」

这段截取的代码展示了指标/EA在MT5里的两个关键钩子:OnChartEvent把鼠标与缓冲区消息派发给chart对象后立刻ChartRedraw(),意味着界面刷新紧跟交互,不依赖下一根K线 tick;OnDeinit里仅在REASON_CHARTCHANGE时调SaveState()再delete chart,避免切换周期或品种时把运行状态写脏。 浮层面板类C_ChartFloatingRAD私有继承C_Terminal,用宏macro_NameGlobalVariable拼出带终端ID的全局变量名(ChartTrade_%u+后缀),多图表不会串号。结构体stData把杠杆、止盈止损点数、是否日内交易和一条union(TickCount与dValue共用8字节)打包,配合eObjectsIDE枚举区分九类UI消息,是从前端交互回写后端交易的紧凑通道。 直接把这段类声明和OnDeinit丢进MT5的MQH里编译,能在 Experts 日志看到REASON_CHARTCHANGE触发SaveState的概率随手动换周期显著上升;外汇与贵金属杠杆可调,这类面板若误写状态,重加载后可能偏移原止损预设,属高风险操作。

MQL5 / C++
(*chart).DispatchMessage(id, lparam, dparam, sparam);
(*chart).MountBuffer(m_Buff);

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

 class="kw">delete chart;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "../Auxiliar/C_Mouse.mqh"
class="macro">#include "../Auxiliar/Interprocess.mqh"
class="macro">#include "C_AdjustTemplate.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A)
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_ChartFloatingRAD : class="kw">private C_Terminal
{
 class="kw">public      :
  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 stData
  {
   class="type">int         Leverage;
   class="type">class="kw">double      PointsTake,
               PointsStop;
   class="type">bool        IsDayTrade;
   union u01
   {
    class="type">class="kw">ulong  TickCount;
    class="type">class="kw">double dValue;
   }uCount;
   eObjectsIDE Msg;
  };
 class="kw">private :
  class="kw">struct st00

把图表交易面板塞进自定义窗体

这段结构体把鼠标交互面板所需的全部状态收进一个 m_Info 里:坐标、窗口句柄、止盈止损数值、是否最大化,以及一个带 MSG_NULL 下标的 Regions 子结构数组,用来记录多个可绘制区域的位置与配色。 C_Mouse *m_Mouse 与 m_szShortName 挂在类成员上,前者接管点击事件,后者作短名标识。CreateWindowRAD(int w, int h) 负责真正建窗:先把图表对象名写成 "Chart Trade IDE",再拼出 "Chart Trade IDE > Edit" 作为可编辑层名。 ObjectCreate 用终端 ID 在坐标 (0,0) 处落一个 OBJ_CHART,随后 ObjectSetInteger 把 XDISTANCE 绑到 m_Info.x。开 MT5 把这段贴进 EA 类,改 m_Info.x/y 就能把面板挪到屏幕任意角;外汇与贵金属杠杆高,面板只是辅助,下单前仍要自判风险。

MQL5 / C++
{
   class="type">int      x, y, minx, miny;
   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;
   stData   ConfigChartTrade;
   class="kw">struct st01
   {
      class="type">int    x, y, w, h;
      class="type">class="kw">color  bgcolor;
      class="type">int    FontSize;
      class="type">class="kw">string FontName;
   }Regions[MSG_NULL];
}m_Info;
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Mouse   *m_Mouse;
class="type">class="kw">string     m_szShortName;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CreateWindowRAD(class="type">int w, class="type">int h)
   {
      m_Info.szObj_Chart = "Chart Trade IDE";
      m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit";
      ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, class="num">0, class="num">0, class="num">0);
      ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x);
交给小布盯盘看指标链路
这些指标与 EA 的调用关系,小布盯盘的 AIGC 已内置品种页诊断,打开对应品种即可看到链路是否通畅,你专注策略本身。

常见问题

早期版本没有标准数据缓冲区,EA 无法像读取普通指标那样稳定取值,只能做触发判断。本篇开始补齐缓冲区机制。
避免历史垃圾值污染 EA 决策,保证每根 K 线重放时信号从干净状态计算,概率上更贴近实盘重演。
难度会明显上升,那三篇铺了类结构与数据传递底层,建议先补,否则容易卡在 C_ChartFloatingRAD 的修改点。
可以,小布盯盘对应品种页有 AIGC 诊断,能提示缓冲区连通与调用异常,省去手动逐行排查。
杠杆与滑点放大重放偏差,回放结论仅作概率参考,实盘前仍需小仓验证,这类品种高风险不可逆。