Chart Trade 指标重构基础:把条件指标变成真正可调用缓冲区的铺垫(基础篇)
(1/3)· 很多交易者照搬指标代码却不懂缓冲区初始化,导致 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 感受滑点差异。
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的概率随手动换周期显著上升;外汇与贵金属杠杆可调,这类面板若误写状态,重加载后可能偏移原止损预设,属高风险操作。
(*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 就能把面板挪到屏幕任意角;外汇与贵金属杠杆高,面板只是辅助,下单前仍要自判风险。
{
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);