指标间的数据交换:易如反掌!·进阶篇
(2/3)· 当指标 B 想实时吃指标 A 的缓冲区,句柄等待十几秒的坑你还要踩多久?
◍ 用全局变量做跨指标指针表
系统内核写好之后,得让它能在独立 MQL 程序之间被随时调用。MT5 客户端自带全局变量机制,最顺手的办法就是把指标缓冲区的指针塞进全局变量,当作一张描述符表来用。 描述符名称按固定格式拼:string_identifier#buffer_number#symbol#period#buffer_length#indexing_direction#random_number,值就是内存里对应缓冲区的整数指针。buffer_length 绝不能漏,越界可能直接把客户端甚至 Windows 干到蓝屏;indexing_direction 用 0 表示正常排序、1 表示 AS_SERIES 逆向排序;random_number 用来区分同标识多副本,不优雅但管用。 注册和注销靠两个函数:RegisterBuffer 先调 UnregisterBuffer 清掉旧的,再拼好名字写全局变量;UnregisterBuffer 遍历全局变量,删掉值等于该指针的项。新柱来临缓冲区长度 +1,必须重调 RegisterBuffer,否则旧描述符残留表里。 取数前先用搜索函数扫全局变量,字段可缺省——name 传 NULL 就跳过标识检查,mode<0、symbol=NULL、period<=0 同理,还能用 StringFind 做子串匹配,比如搜 "MA" 捞出所有移动平均线副本。拿到描述符列表后,包装 DLL 的 GetValue/SetValue,注意数组界限和排序方向,失败返回 EMPTY_VALUE 或 false。 下面这段是 RegisterBuffer 与 UnregisterBuffer 的 MQL5 实现,逐行看逻辑: void RegisterBuffer(double &Buffer[], string name, int mode) export // 导出函数,接收缓冲区引用、标识名、缓冲区编号 UnregisterBuffer(Buffer); // 先注销同缓冲区旧描述符,防止重复 int direction=0; // 默认排序方向为正常 if(ArrayGetAsSeries(Buffer)) direction=1; // 若缓冲区是 AS_SERIES 则方向置 1 name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction; // 拼出描述符前半段:标识#编号#品种#周期#长度#方向 int ptr=GetPtr(Buffer); // 取缓冲区内存指针 if(ptr==0) return; // 指针为 0 直接退出 MathSrand(ptr); // 用指针值做随机种子,方便生成区分随机数 while(true) // 死循环找不冲突的名字 { int rnd=MathRand(); // 取一个随机数 if(!GlobalVariableCheck(name+"#"+rnd)) // 检查拼接随机后缀的名字是否存在 { name=name+"#"+rnd; // 不存在就定下最终名 GlobalVariableSet(name,ptr); // 把指针写进全局变量 break; // 跳出循环 } } } void UnregisterBuffer(double &Buffer[]) export // 导出注销函数 int ptr=GetPtr(Buffer); // 再取一次指针 if(ptr==0) return; // 指针无效则退出 int gt=GlobalVariablesTotal(); // 拿到全局变量总数 int i; for(i=gt-1;i>=0;i--) // 从尾到头遍历所有全局变量
class="type">void RegisterBuffer(class="type">class="kw">double &Buffer[], class="type">class="kw">string name, class="type">int mode) class="kw">export { UnregisterBuffer(Buffer); class="type">int direction=class="num">0; if(ArrayGetAsSeries(Buffer)) direction=class="num">1; name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction; class="type">int ptr=GetPtr(Buffer); if(ptr==class="num">0) class="kw">return; MathSrand(ptr); while(true) { class="type">int rnd=MathRand(); if(!GlobalVariableCheck(name+"#"+rnd)) { name=name+"#"+rnd; GlobalVariableSet(name,ptr); break; } } } class="type">void UnregisterBuffer(class="type">class="kw">double &Buffer[]) class="kw">export { class="type">int ptr=GetPtr(Buffer); if(ptr==class="num">0) class="kw">return; class="type">int gt=GlobalVariablesTotal(); class="type">int i; for(i=gt-class="num">1;i>=class="num">0;i--)
用全局变量反查指标缓冲区
在 MT5 里跨 EA / 指标抓缓冲区,常见做法是用全局变量名当索引。上面这段把全局变量名按 '#' 拆成 descriptor 数组,字段分别对应 name、mode、symbol、period,依次比对入参做过滤。 FindBuffers 先调 GlobalVariablesTotal 拿到总数,再从后往前遍历;StringTrimLeft / StringTrimRight 清掉 name 两端空格,避免匹配漏判。任一字段不符就置 found=false,全中才把 name_i 写进 buffers 并返回 count。 StringExplode 是自己写的分割函数:没找到分隔符就整串丢进 result[0];找到就循环 StringSubstr 截段,每次 ArrayResize 扩一位。外汇 / 贵金属品种多周期运行时,这种全局变量缓存易堆积,建议定时清理,否则 GlobalVariablesTotal 可能涨到数百拖慢遍历。
class=class="str">"cmt">//并且从所有地方删除缓存 class="type">class="kw">string name=GlobalVariableName(i); if(GlobalVariableGet(name)==ptr) GlobalVariableDel(name); } } class="type">int FindBuffers(class="type">class="kw">string name, class="type">int mode, class="type">class="kw">string symbol, class="type">int period, class="type">class="kw">string &buffers[]) class="kw">export { class="type">int count=class="num">0; class="type">int i; class="type">bool found; class="type">class="kw">string name_i; class="type">class="kw">string descriptor[]; class="type">int gt=GlobalVariablesTotal(); StringTrimLeft(name); StringTrimRight(name); ArrayResize(buffers,count); for(i=gt-class="num">1;i>=class="num">0;i--) { found=true; name_i=GlobalVariableName(i); StringExplode(name_i,"#",descriptor); if(StringFind(descriptor[class="num">0],name)<class="num">0&&name!=NULL) found=false; if(descriptor[class="num">1]!=mode&&mode>=class="num">0) found=false; if(descriptor[class="num">2]!=symbol&&symbol!=NULL) found=false; if(descriptor[class="num">3]!=period&&period>class="num">0) found=false; if(found) { count++; ArrayResize(buffers,count); buffers[count-class="num">1]=name_i; } } class="kw">return(count); } class="type">void StringExplode(class="type">class="kw">string s, class="type">class="kw">string separator, class="type">class="kw">string &result[]) { class="type">int i,pos; ArrayResize(result,class="num">1); pos=StringFind(s,separator); if(pos<class="num">0) {result[class="num">0]=s;class="kw">return;} for(i=class="num">0;;i++) { pos=StringFind(s,separator); if(pos>=class="num">0) { result[i]=StringSubstr(s,class="num">0,pos); s=StringSubstr(s,pos+StringLen(separator)); } else break; ArrayResize(result,ArraySize(result)+class="num">1); } }
「跨指标读写的指针桥接函数」
在 MT5 里想让两个独立 EA 或脚本共享自定义指标缓冲区,靠全局变量存指针是最轻量的办法。下面这两个导出函数就是干这个的:用 descriptor 字符串去查全局变量,拿到内存指针后直接定位数组下标取值或写值。 descriptor 按 '#' 拆字段,其中第 5 个(索引 4)是数组 size,第 6 个(索引 5)是 direction。direction==1 代表排序反向,这时 shift 会被重算成 size-1-shift,否则按正向索引。 两个函数都先做 GlobalVariableCheck 确认描述符存在,再取 ptr 判非零,最后用 shift>=0 && shift<size 拦住越界——这一步省了,客户端大概率直接崩。读失败返回 EMPTY_VALUE,写失败返回 false,调用方据此判断共享是否生效。 实盘里外汇和贵金属波动跳空频繁,这类跨程序内存共享不保证时序,高频切换品种时可能读到上一根残留值,验证前先在 EURUSD 的 M1 上跑通再上 XAUUSD。
class="type">class="kw">double GetIndicatorValue(class="type">class="kw">string descriptor, class="type">int shift) class="kw">export { class="type">int ptr; class="type">class="kw">string fields[]; class="type">int size,direction; if(GlobalVariableCheck(descriptor)>class="num">0) class=class="str">"cmt">//检查描述符的有效性 { ptr = GlobalVariableGet(descriptor); class=class="str">"cmt">//获取指针的值 if(ptr!=class="num">0) { StringExplode(descriptor,"#",fields); class=class="str">"cmt">//将名称分割为字段 size = fields[class="num">4]; class=class="str">"cmt">//我们需要当前数组大小 direction=fields[class="num">5]; if(direction==class="num">1) shift=size-class="num">1-shift; class=class="str">"cmt">//如果ordering_direction反向 if(shift>=class="num">0&&shift<size) class=class="str">"cmt">//检查其有效性,以防止崩溃 class="kw">return(GetValue(MathAbs(ptr),shift)); class=class="str">"cmt">//ok,返回值 } } class="kw">return(EMPTY_VALUE); class=class="str">"cmt">//否则返回空值 } class="type">bool SetIndicatorValue(class="type">class="kw">string descriptor, class="type">int shift, class="type">class="kw">double value) class="kw">export { class="type">int ptr; class="type">class="kw">string fields[]; class="type">int size,direction; if(GlobalVariableCheck(descriptor)>class="num">0) class=class="str">"cmt">//检查其有效性 { ptr = GlobalVariableGet(descriptor); class=class="str">"cmt">//获取描述符值 if(ptr!=class="num">0) { StringExplode(descriptor,"#",fields); class=class="str">"cmt">//将其分割为字段 size = fields[class="num">4]; class=class="str">"cmt">//我们要知道它的大小 direction=fields[class="num">5]; if(direction==class="num">1) shift=size-class="num">1-shift; class=class="str">"cmt">//订单转向的情况 if(shift>=class="num">0&&shift<size) class=class="str">"cmt">//检查索引防止客户端崩溃 { SetValue(MathAbs(ptr),shift,value); class="kw">return(true); } } } class="kw">return(false); }
◍ 把 ATR 塞进别的指标窗口
想验证跨指标缓冲区搬运是否靠谱,最直白的靶子是标准库里的真实波动幅度均值(ATR)。只要改几行 OnCalculate,就能把它的缓冲区值注册成全局描述符,再让另一个指标窗口读取并覆写。 关键动作落在 prev_calculated != rates_total 这个条件里:新柱出现或补历史数据时,才调用 RegisterBuffer 把 ExtATRBuffer 以 "ATR" 名义挂到表上。拖太久不注销会留垃圾,所以 OnDeinit 里必须补一句 UnregisterBuffer(ExtATRBuffer),注意它返回 void 且带 const int reason 参数。 另起一个叫 test 的指标,包含 exchng.mqh,用子字符串从描述符列表捞到目标,再读缓冲区写进自己的 ATRCopyBuffer。示例代码里直接把数组下标当值写进去——纯属验证通道通不通。 跑起来后,ATR 本体的线在下方子窗,test 指标的同窗口会画出一条由索引值组成的直线,证明跨窗读取成功。外汇与贵金属杠杆高,这类实验只在策略测试器或模拟盘做,实盘误写缓冲区可能让图表显示失真。 别把正态当圣经:描述符名写错一个字母,test 就读不到东西,全局变量列表里却照常多一条,排查时先看图2那种列表再回头查代码。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 真实波动幅度均值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &Time[], const class="type">class="kw">double &Open[], const class="type">class="kw">double &High[], const class="type">class="kw">double &Low[], const class="type">class="kw">double &Close[], const class="type">long &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) { if(prev_calculated!=rates_total) RegisterBuffer(ExtATRBuffer,"ATR",class="num">0); … class="type">void OnDeinit(const class="type">int reason) { UnregisterBuffer(ExtATRBuffer); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| test.mq5 | class=class="str">"cmt">//| Copyright class="num">2009, alsu | class=class="str">"cmt">//| alsufx@gmail.com | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "class="num">2009, alsu" class="macro">#class="kw">property link "alsufx@gmail.com" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class="macro">#include <exchng.mqh> class=class="str">"cmt">//----绘制ATRCopy class="macro">#class="kw">property indicator_label1 "ATRCopy" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 Red class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- 指标缓存 class="type">class="kw">double ATRCopyBuffer[]; class="type">class="kw">string atr_buffer; class="type">class="kw">string buffers[]; class=class="str">"cmt">//+------------------------------------------------------------------+
把别人的 ATR 缓存直接搬进自定义指标
想在自己写的 MT5 指标里复用系统 ATR,不必重算,只要从全局缓冲区把现成数据抓出来。OnInit 里用 SetIndexBuffer(0,ATRCopyBuffer,INDICATOR_DATA) 把本地数组绑到第 0 号画线缓冲,指标才能把值画到副图。 OnCalculate 的核心是先 FindBuffers("ATR",0,NULL,0,buffers) 去全局变量里搜 ATR 描述符。返回 found>0 就说明目标指标已加载,把 buffers[0] 存进 atr_buffer;否则置 NULL,后面直接 break 不画,避免空指针报错。 抓到句柄后,从 prev_calculated 开始循环到 rates_total,每行调用 GetIndicatorValue(atr_buffer,i) 把 ATR 值抄进 ATRCopyBuffer[i],顺手 SetIndicatorValue(atr_buffer,i,i) 写回原缓冲。最后 return(rates_total) 而非 prev_calculated,能保证下一根 K 线重算时不会漏掉末尾数据。 这套写法在 EURUSD 15 分钟图上验证过:若图表未挂 ATR 指标,found 返回 0,副图全空;挂上默认 14 周期 ATR 后,重绘即同步。外汇与贵金属波动剧烈,指标复用仅降低算力占用,信号本身不代表方向,实盘仍属高风险。
class=class="str">"cmt">//| 自定义指标初始化函数 |//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 SetIndexBuffer(class="num">0,ATRCopyBuffer,INDICATOR_DATA); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 |//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime& time[], const class="type">class="kw">double& open[], const class="type">class="kw">double& high[], const class="type">class="kw">double& low[], const class="type">class="kw">double& close[], const class="type">long& tick_volume[], const class="type">long& volume[], const class="type">int& spread[]) { class=class="str">"cmt">//--- class="type">int found=FindBuffers("ATR",class="num">0,NULL,class="num">0,buffers); class=class="str">"cmt">//在全局变量中搜索描述符 if(found>class="num">0) atr_buffer=buffers[class="num">0]; class=class="str">"cmt">//如果我们发现它,将它保存到atr_buffer else atr_buffer=NULL; class="type">int i; for(i=prev_calculated;i<rates_total;i++) { if(atr_buffer==NULL) break; ATRCopyBuffer[i]=GetIndicatorValue(atr_buffer,i); class=class="str">"cmt">//现在获取数据就简单了 SetIndicatorValue(atr_buffer,i,i); class=class="str">"cmt">//并且记录他们也方便 } class=class="str">"cmt">//--- 返回prev_calculated的值用于下一次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+