指标喷发整体特征的计算·进阶篇
(2/3)· 喷发点铺满图表却读不出数字?本篇把可视混乱收敛成支撑阻力与趋势概率
把 iMA 与 iEnvelopes 的喷发点往图表上一挂,洋红浅绿蓝点混作一团,多数人以为看到了结构,其实只是看到了噪声。没有整体特征提取,这些点既给不出价位也称不出强度,自动化交易更无从谈起。外汇贵金属杠杆高,拿肉眼混沌当信号容易吃反向鞭子。
◍ 两条回归线交点的类实现
在 MT5 自定义指标里,若要用两条不同窗口的回归直线推算未来可能的交汇点,可以封装一个 CEmission 类来处理。构造函数只接收左侧和右侧的偏移限制(limitLeft、limitRight),并把当前图表周期的秒数存进 sec,后续换算时间坐标时直接复用。 CalcPoint 是核心:它吃进两条线在 bar[1] 与 bar[0] 上的 Y 值,以及当前 bar 的时间 t0。先算 y0-y1(线1斜率投影)、y1-yy1(两线在[1]处的纵向差)、yy0-yy1(线2斜率投影),再用 del0 = yy0yy1 - y0y1 做分母。若 MathAbs(del0) 不大于 0 说明两线平行,直接返回空点。 point.x = y1yy1/del0 得到的是相对 bar[0] 的索引偏移;一旦超出 lim_Left~lim_Right 区间或算出的 y 为负,就放弃该点。合法时 point.t = t0 + (int)(point.x*sec),把偏移转成具体时间,real 置 true。 下一段的 CreatePoint 负责把算出的 PointEmission 结构画到图上,入参是点的 name 与引用。复制下面代码到 MT5 的 .mqh 里,挂上两个不同周期的回归指标,就能在 EURUSD 或 XAUUSD 上验证交汇点是否合理——外汇与贵金属杠杆高,信号仅作概率参考。
CEmission::CEmission(class="type">int limitLeft,class="type">int limitRight) { sec=PeriodSeconds(); lim_Left=limitLeft; lim_Right=limitRight; } CEmission::~CEmission() { } PointEmission CEmission::CalcPoint(class="type">class="kw">double y1, class=class="str">"cmt">// Y-coordinate of straight line class="num">1 on bar [class="num">1] class="type">class="kw">double y0, class=class="str">"cmt">// Y-coordinate of straight line class="num">1 on bar [class="num">0] class="type">class="kw">double yy1,class=class="str">"cmt">// Y-coordinate of straight line class="num">2 on bar [class="num">1] class="type">class="kw">double yy0,class=class="str">"cmt">// Y-coordinate of straight line class="num">2 on bar [class="num">0] class="type">class="kw">datetime t0 class=class="str">"cmt">// t-coordinate of the current bar Time[class="num">0] ) { PointEmission point={NULL,NULL,NULL,class="kw">false}; class="type">class="kw">double y0y1=y0-y1; class="type">class="kw">double y1yy1=y1-yy1; class="type">class="kw">double yy0yy1=yy0-yy1; class="type">class="kw">double del0=yy0yy1-y0y1; if(MathAbs(del0)>class="num">0) { point.x=y1yy1/del0; if(point.x<lim_Left || point.x>lim_Right) class="kw">return(point); point.y=y1+y0y1*y1yy1/del0; if(point.y<class="num">0) class="kw">return(point); point.t=t0+(class="type">int)(point.x*sec); point.real=true; class="kw">return(point); } class="kw">return(point); } class="type">bool CEmission::CreatePoint(class="type">class="kw">string name, class=class="str">"cmt">// point name PointEmission &point class=class="str">"cmt">// coordinates of the point
「在MT5图表上画Wingdings标记点」
想在K线图上用特殊符号标注关键价位,可以借助OBJ_TEXT对象配合Wingdings字体实现。下面这段函数接收名称、坐标点和样式结构,尝试在图表零号子窗口创建文本对象。 创建成功后,代码把字体设为Wingdings,锚点置中,字号取自style.Width,文本内容由style.Code转成字符,再把价格和时间分别绑定到point.y与point.t,最后上色返回true;若ObjectCreate失败则直接返回false。 实际验证时,把style.Code填成159会显示实心圆点,填成108则是箭头,外汇与贵金属波动剧烈,这类标记仅作视觉辅助,信号失效概率不低,需结合量价确认。
if(ObjectCreate(class="num">0,name,OBJ_TEXT,class="num">0,class="num">0,class="num">0)) { ObjectSetString(class="num">0,name,OBJPROP_FONT,"Wingdings"); ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,ANCHOR_CENTER); ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,style.Width); ObjectSetString(class="num">0,name,OBJPROP_TEXT,CharToString((class="type">uchar)style.Code)); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,point.y); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,point.t); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,style.Color); class="kw">return(true); } class="kw">return(class="kw">false); }
从彩色噪点里榨出可量化的喷发特征
把 EA 挂上图表,屏幕会跳出一堆分色点:浅绿是 iMA 与 iEnvelopes 上轨交叉,蓝色是下轨交叉,洋红是 iMA 与自身不同周期的交叉。肉眼看是图像,机器要的是数字——这种混乱没法直接喂给自动交易。 先从小处落地:给每种点类型算平均价格,画出对应水平线。再算平均时间值,标在均价线上,得到第一批量化特征——喷发点从不静止,始终在价格空间里漂移。图 4 只显示瞬时位置,要研究历史就得想办法把它钉死,这一步暂且留白。 为方便横向比较,改用百分比。iMA 与 iEnvelopes 交叉产生的点视作 100% 基准,实测洋红自交叉点占比 34.4%,三类相加会超 100%,那个 34.4% 是指标自交叉的参考值,用法后面再谈。点数百分比随行情波动,怎么在历史里固定它们,仍是待解问题。 下方代码块就是累加与显示这些整体特征的核心:先按颜色归类点数与价格和,再画虚点线标出均价水平。复制进 MT5 的 OnTick 段,你能直接看到分色水平线随 tick 更新。外汇与贵金属市场杠杆高、波动剧烈,此类统计特征仅作概率参考,不构成方向判定。
class=class="str">"cmt">//--- arrays for calculation and display of integral characteristics of emissions class="macro">#define NUMBER_TYPES_POINT class="num">3 class="type">class="kw">double sum[NUMBER_TYPES_POINT],sumprev[NUMBER_TYPES_POINT]; class="type">class="kw">datetime sum_time[NUMBER_TYPES_POINT]; class="type">int n[NUMBER_TYPES_POINT],W[NUMBER_TYPES_POINT]; class="type">color colorLine[]={clrAqua,clrBlue,clrMagenta}; class=class="str">"cmt">//--- calculation of integral characteristics of emissions ArrayInitialize(n,class="num">0); ArrayInitialize(sum,class="num">0.0); ArrayInitialize(sum_time,class="num">0.0); for(class="type">int obj=total-class="num">1;obj>=class="num">0;obj--) { class="type">class="kw">string obj_name=ObjectName(class="num">0,obj,class="num">0,OBJ_TEXT); class="type">class="kw">datetime obj_time=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,obj_name,OBJPROP_TIME); if(obj_time>T[class="num">0]) { class="type">color obj_color=(class="type">color)ObjectGetInteger(class="num">0,obj_name,OBJPROP_COLOR); class="type">class="kw">double obj_price=ObjectGetDouble(class="num">0,obj_name,OBJPROP_PRICE); for(class="type">int i=ArraySize(n)-class="num">1; i>=class="num">0; i--) if(obj_color==colorPoint[i]) { n[i]++; sum[i]+=obj_price; sum_time[i]+=obj_time; } } } class=class="str">"cmt">//--- displaying integral characteristics of emissions for(class="type">int i=ArraySize(n)-class="num">1; i>=class="num">0; i--) { if(n[i]>class="num">0) { name="H.line."+(class="type">class="kw">string)i; ObjectCreate(class="num">0,name,OBJ_HLINE,class="num">0,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]); ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_DASHDOT); ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1);
◍ 用 ObjectSetDouble 把均值写回画线
在 MT5 里给指标对象动态赋值,核心就是 ObjectSetDouble 这一句。它把计算好的移动平均结果,按索引写进对应价格坐标,让水平线或曲线实时跟着数据走。
ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,sum[i]/n[i]); } }
ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,sum[i]/n[i]); } }
「用厚度与颜色把喷发整体通道画出来」
主喷发点按百分比比例给曲线不同厚度,是当前段沿当前与前一根柱平均价格线绘制的——注意这些坐标实际取自未来,相当于指标喷发的主导整体通道(图表中洋红色线即 iMA & iMA 喷发用途)。这给了我们一个新东西:整合移动平均线。 光有平均价格线还不够,时间坐标也得管。浅绿是通道上限、蓝色是下限,要找时间上更贴近零柱的标记。把 PRICE_MEDIAN 线挂到图上,让它根据更接近末根柱的标记颜色在浅绿/蓝之间切换,就能看出时间维度的主导。 如果按初始喷发整体特征再去画一层喷发(类似二阶喷发),那些线也会交叉产生新喷发点。图 8 能看到新点但暂时无意义,必须对图 9 的新点继续算整体特征并叠喷发,循环到不可行为止。外汇与贵金属市场杠杆高,这类图形实验仅用于可视化验证,不代表任何方向确定性。 下方代码块负责把喷发的整体特征画到图表:水平线用平均价、文本标记用 Wingdings 字符 163、趋势线用整合曲线。W 数组里最大喷发点权重 20、最小 3,线宽统一 1,样式 DASHDOT。开 MT5 把这段塞进 OnTick 就能看到通道自绘。
class=class="str">"cmt">//--- displaying integral characteristics of emissions ArrayInitialize(W,class="num">10); W[ArrayMaximum(n)]=class="num">20; W[ArrayMinimum(n)]=class="num">3; for(class="type">int i=ArraySize(n)-class="num">1; i>=class="num">0; i--) { if(n[i]>class="num">0) { class=class="str">"cmt">//--- horizontal lines of mean prices name="H.line."+(class="type">class="kw">string)i; ObjectCreate(class="num">0,name,OBJ_HLINE,class="num">0,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]); ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_DASHDOT); ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,sum[i]/n[i]); class=class="str">"cmt">//--- markers name="P."+(class="type">class="kw">string)i; ObjectCreate(class="num">0,name,OBJ_TEXT,class="num">0,class="num">0,class="num">0); ObjectSetString(class="num">0,name,OBJPROP_FONT,"Wingdings"); ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,ANCHOR_CENTER); ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,class="num">17); ObjectSetString(class="num">0,name,OBJPROP_TEXT,CharToString(class="num">163)); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,sum[i]/n[i]); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,sum_time[i]/n[i]); class=class="str">"cmt">//--- integral curves name="T"+(class="type">class="kw">string)i+".line"+(class="type">class="kw">string)T[class="num">1]; ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]);
把分布重心画成可读的线
这段收尾逻辑干两件事:给每段分布画趋势线,再在重心位置贴百分比文字。线宽由 W[i] 控制,若前段累计量 sumprev[i] 大于 0,就把趋势线两端锚在 T[1] 与 T[0] 的均价 (sum[i]/n[i]) 上, visually 把两段重心连起来。 文字对象用 OBJ_TEXT 建在重心价 (sum[i]/n[i]) 与时间均值 sum_time[i]/n[i] 处,字号 30、左下单锚,内容是 n[i] 占 n[0]+n[1] 的比重,保留 1 位小数——比如某段成交笔数占比 63.4% 会直接标在图上。 最后挑时间均值更早的那段画一条宽度 8 的粗趋势线,端点价用 HL(H,L) 算出的区间中值。外汇与贵金属波动剧烈,这类重心线仅描述历史分布,后市方向仍可能反转,请务必在 MT5 策略测试器里用小周期数据验证渲染效果。
ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,W[i]); if(sumprev[i]>class="num">0) { ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,sumprev[i]); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">0,T[class="num">1]); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">1,(sum[i]/n[i])); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,T[class="num">0]); } class=class="str">"cmt">//--- numerical values of integral characteristics name="Text"+(class="type">class="kw">string)i+".control"; ObjectCreate(class="num">0,name,OBJ_TEXT,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,class="num">30); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]); class="type">class="kw">string str=DoubleToString((class="type">class="kw">double)n[i]/(class="type">class="kw">double)(n[class="num">0]+n[class="num">1])*class="num">100,class="num">1); ObjectSetString(class="num">0,name,OBJPROP_TEXT,str); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,(sum[i]/n[i])); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">0,sum_time[i]/n[i]); } } class=class="str">"cmt">//--- if(n[ArrayMinimum(n)]>class="num">0) { class="type">class="kw">datetime d[class="num">2]; for(class="type">int j=class="num">0;j<class="num">2;j++) { d[j]=sum_time[j]/n[j]; } class="type">int i=ArrayMinimum(d); name="Price.line"+(class="type">class="kw">string)T[class="num">1]; ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,class="num">0,class="num">0); ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">8); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,HL(H[class="num">1],L[class="num">1])); ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">0,T[class="num">1]); ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">1,HL(H[class="num">0],L[class="num">0]));