同步多个相同交易品种而时段不同的图表·进阶篇
(2/3)·手动套模板同步多图对象既慢又易漏,这篇讲清事件驱动指标怎么干掉重复劳动
不少交易者看同一品种的不同周期时,习惯在高周期画趋势线再切低周期找入场,对象一改就得重存模板逐个应用。图表一多、对象一杂,这种手工同步基本等于给自己加无效工时。
「用句柄在图表间克隆指标」
跨图表同步指标的核心,是先拿主图上的指标句柄,再在从图上用同一套参数重建。下面这段逻辑遍历图表,发现目标指标已存在就直接登记,不存在就尝试克隆,遍历靠 ChartNext 推进。 int handl=ChartIndicatorGet(check_chart,0,s_IndyName); if(handl!=INVALID_HANDLE) { AddChartToArray(check_chart,charts); check_chart=ChartNext(check_chart); continue; } if(!AddIndicator(chart,check_chart)) { check_chart=ChartNext(check_chart); continue; } AddChartToArray(check_chart, charts); check_chart=ChartNext(check_chart); } //--- return true; } bool CCloneIndy::AddIndicator(const long master_chart,const long slave_chart) {
| if(master_chart<0 | slave_chart<=0 | master_chart==slave_chart) |
|---|
return false; int master_handle=ChartIndicatorGet(master_chart,0,s_IndyName); if(master_handle==INVALID_HANDLE) return false; MqlParam params[]; ENUM_INDICATOR type; if(IndicatorParameters(master_handle,type,params)<0) return false; params[0].string_value=s_IndyPath; params[1].integer_value=slave_chart; ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart); int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params); if(slave_handle<0) return false; return ChartIndicatorAdd(slave_chart,0,slave_handle); } 逐行看:ChartIndicatorGet 取句柄,INVALID_HANDLE 说明从图上没这指标;AddIndicator 里先排除主从图相同(master_chart==slave_chart 直接 false),再用 IndicatorParameters 把主图指标的参数扒出来。 params[0] 塞路径、params[1] 塞从图 ID,IndicatorCreate 按从图周期重建,最后 ChartIndicatorAdd 挂上去。句柄小于 0 即创建失败,外汇和贵金属多图同显时这种失败概率不低,建议开 MT5 在 EURUSD 与 XAUUSD 各开一图实测。
class="type">int handl=ChartIndicatorGet(check_chart,class="num">0,s_IndyName); if(handl!=INVALID_HANDLE) { AddChartToArray(check_chart,charts); check_chart=ChartNext(check_chart); class="kw">continue; } if(!AddIndicator(chart,check_chart)) { check_chart=ChartNext(check_chart); class="kw">continue; } AddChartToArray(check_chart, charts); check_chart=ChartNext(check_chart); } class=class="str">"cmt">//--- class="kw">return true; } class="type">bool CCloneIndy::AddIndicator(class="kw">const class="type">long master_chart,class="kw">const class="type">long slave_chart) { if(master_chart<class="num">0 || slave_chart<=class="num">0 || master_chart==slave_chart) class="kw">return class="kw">false; class="type">int master_handle=ChartIndicatorGet(master_chart,class="num">0,s_IndyName); if(master_handle==INVALID_HANDLE) class="kw">return class="kw">false; class="type">MqlParam params[]; ENUM_INDICATOR type; if(IndicatorParameters(master_handle,type,params)<class="num">0) class="kw">return class="kw">false; params[class="num">0].string_value=s_IndyPath; params[class="num">1].integer_value=slave_chart; ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart); class="type">int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params); if(slave_handle<class="num">0) class="kw">return class="kw">false; class="kw">return ChartIndicatorAdd(slave_chart,class="num">0,slave_handle); }
用类把图形对象数据塞进自定义事件
跨图表同步趋势线、矩形这类图形对象,笨办法是在目标图表用 ObjectCreate 逐个复刻,图表一多就卡。更顺手的做法是把对象变动交给各图表上的指标,用自定义事件异步广播,工作负载自然分摊到所有实例。 OnChartEvent 只给四个槽位:事件 ID、长整型、双精度、字符串。要传一条趋势线的全部属性,只能把信息压进那个字符串参数。参考已有的云存储交换思路,把对象字段拼成键值串再发走。 注意 EventChartCustom 的 sparam 被限制在 63 个字符以内。一条趋势线价格保留 5 位小数、带两个时间点和价格点,轻轻松松超长,所以消息必须拆段发送,TrendToString 这类函数用 part 参数指定当前拼第几段。 下面这段类声明和趋势线序列化实例,直接能抄进 MT5 指标工程里改。外汇与贵金属图表多周期联动属高风险操作,跨图表对象同步失败可能导致误判信号,实盘前请在模拟环境验证。
class CCloneObjects { class="kw">private: class="type">class="kw">string HLineToString(class="type">long chart, class="type">class="kw">string name, class="type">int part); class="type">class="kw">string VLineToString(class="type">long chart, class="type">class="kw">string name, class="type">int part); class="type">class="kw">string TrendToString(class="type">long chart, class="type">class="kw">string name, class="type">int part); class="type">class="kw">string RectangleToString(class="type">long chart, class="type">class="kw">string name, class="type">int part); class="type">bool CopySettingsToObject(class="type">long chart,class="type">class="kw">string name,class="type">class="kw">string &settings[]); class="kw">public: CCloneObjects(); ~CCloneObjects(); class=class="str">"cmt">//--- class="type">class="kw">string CreateMessage(class="type">long chart, class="type">class="kw">string name, class="type">int part); class="type">bool DrawObjects(class="type">long chart, class="type">class="kw">string message); }; class="type">class="kw">string CCloneObjects::TrendToString(class="type">long chart,class="type">class="kw">string name, class="type">int part) { class="type">class="kw">string result = NULL; if(ObjectFind(chart,name)!=class="num">0) class="kw">return result; class="kw">switch(part) { case class="num">0: result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=class="num">0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,class="num">0),class="num">5)+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=class="num">0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,class="num">0))+"|"; result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=class="num">1="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,class="num">1),class="num">5)+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=class="num">1="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,class="num">1))+"|";
◍ 射线与通用属性的取值拼接
这段分支代码负责把图形对象的整数型和字符串型属性序列化成一段可读字符串,方便在日志或面板里直接排查对象状态。 对于趋势线类对象,它在 ENUM_SET_TYPE_INTEGER 分支下单独取了 OBJPROP_RAY_LEFT 和 OBJPROP_RAY_RIGHT 两个开关——这两个值非 0 即 1,决定射线是否向左或向右无限延伸。 default 分支覆盖其余对象类型:依次取颜色(OBJPROP_COLOR)、线宽(OBJPROP_WIDTH)、线型(OBJPROP_STYLE)、背景层标记(OBJPROP_BACK),再用 ObjectGetString 抓 OBJPROP_TEXT 与 OBJPROP_TOOLTIP 文本,每段以竖线分隔。 开 MT5 把这段塞进对象遍历函数,能在 1 秒内列出当前图表所有对象的延伸方向与样式参数,比手动点属性框快得多。外汇与贵金属图表对象状态受实时报价影响,参数可能随时跳变,属高风险环境下的辅助观测手段。
result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_LEFT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_LEFT))+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_RIGHT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_RIGHT))+"|"; class="kw">break; class="kw">default: result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR))+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH))+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE))+"|"; result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK))+"|"; result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"="+ObjectGetString(chart,name,OBJPROP_TEXT)+"|"; result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"="+ObjectGetString(chart,name,OBJPROP_TOOLTIP); class="kw">break; } class="kw">return result; }
「把克隆逻辑收进一个指标里」
指标只暴露一个参数 Chart ID,手动加载时它恒为 0。保留这个看似无用的参数,是因为从 EA 或其它图表用 iCustom 调用时,传进去的 Chart ID 才会生效,用来指定克隆目标图表。 MetaTrader 5 对同品种同时段指标的加载有去重机制:第一次调用后实例常驻,后续调用都指向同一份。若不在类里隔离工作图表 ID,CCloneIndy 返回的会是首跑图表的数据,克隆就串台了。 OnInit 里先 new 出 CCloneIndy 与 CCloneObjects,用 Chart>0 判断取参还是取 ChartID(),再 SearchCharts 把目标图表塞进 ar_Charts 数组,最后 EventSetTimer(10) 每 10 秒刷新一次图表列表。 OnCalculate 是空壳,全部动作在 OnChartEvent:对象创建/修改/拖拽时打两条消息广播给数组里所有图表;删除时直接拿 sparam 里的名字循环通知。收到外来消息则反向在本地图表画或删对象。 外汇与贵金属图表多周期联动本身不保本,跨图表对象同步只是降低手动重复绘制的出错概率,实盘前请在模拟盘验证广播延迟。
sinput class="type">long Chart = class="num">0; CCloneIndy *CloneIndy; CCloneObjects *CloneObjects; class="type">long l_Chart; class="type">long ar_Charts[]; class="type">int OnInit() { class=class="str">"cmt">//--- 创建指标类 CloneIndy = new CCloneIndy(); if(CheckPointer(CloneIndy)==POINTER_INVALID) class="kw">return INIT_FAILED; CloneObjects= new CCloneObjects(); if(CheckPointer(CloneObjects)==POINTER_INVALID) class="kw">return INIT_FAILED; l_Chart=(Chart>class="num">0 ? Chart : ChartID()); CloneIndy.SearchCharts(l_Chart,ar_Charts); EventSetTimer(class="num">10); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } 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) { class=class="str">"cmt">//--- class="type">class="kw">string message1=NULL; class="type">class="kw">string message2=NULL; class="type">int total=class="num">0; class="kw">switch(id) { case CHARTEVENT_OBJECT_CHANGE: case CHARTEVENT_OBJECT_CREATE: case CHARTEVENT_OBJECT_DRAG: message1=CloneObjects.CreateMessage(l_Chart,sparam,class="num">0); message2=CloneObjects.CreateMessage(l_Chart,sparam,class="num">1); total=ArraySize(ar_Charts); for(class="type">int i=class="num">0;i<total;i++) { EventChartCustom(ar_Charts[i],(class="type">class="kw">ushort)id,class="num">0,class="num">0,message1); EventChartCustom(ar_Charts[i],(class="type">class="kw">ushort)id,class="num">0,class="num">0,message2); } class="kw">break; case CHARTEVENT_OBJECT_DELETE: total=ArraySize(ar_Charts); for(class="type">int i=class="num">0;i<total;i++) EventChartCustom(ar_Charts[i],(class="type">class="kw">ushort)id,class="num">0,class="num">0,sparam);