利用 CCanvas 的自定义指标和信息图·综合运用
(3/3)·前两步铺垫完基类与图元,这篇把六种复合类拼成能跑的动态信息图
接上篇,我们继续深挖 CustomGUI 函数库里那几个复合图形类的拼装逻辑。很多人卡在基类能画圆却拼不出指标,本质是没理清分层绘制的顺序和参数耦合。这一篇把前面欠的拼图补完,直接给到可复用的结构。
直方图类的三种形态与绘制核心
CHistogram 类直接挂在 CCanvasBase 之下,坐标轴、刻度这些活儿沿用了 CLIneGraph 的底层结构,所以真正要盯紧的只有直方图本身的形态差异。代码里用 ENUM_TYPE_HISTOGRAM 枚举锁死三种类型:SIMPLE=1 是固定高度三角柱,TRIANGLE=2 是带伪高度的三角分层,RECTANGLE=3 就是最常见的矩形列。 真正拉开差距的方法是 SetArrayValue(),它接收 double 类型数组引用,按当前 m_type 切换绘制分支。SIMPLE 只用 FillTriangle 画一个底边贴 X 轴的三角形;TRIANGLE 则拆成三段三角形拼出高度渐变;RECTANGLE 走列块填充。Y 轴缩放逻辑里有一句:若数据峰值超过 m_y_max 就动态扩轴,这意味着你喂进来的 RSI 数组若突破原有上限,坐标会自动重算。 实测把三个不同周期的 RSI 同时丢进这个类,能在自定义窗口里并排出直方图,周期参数不同则柱宽和疏密明显不同。外汇与贵金属图表加载此类自定义指标属于高风险操作,参数误配可能导致图表资源占用突增。 下面这段是枚举与 SetArrayValue 头部,注意 FillRectangle 先铺边框再铺底图,m_gap 控制内边距,调小它能让柱子更胖。
enum ENUM_TYPE_HISTOGRAM { SIMPLE=class="num">1, TRIANGLE=class="num">2, RECTANGLE=class="num">3 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置输入数组 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHistogram::SetArrayValue(class="type">class="kw">double &data[]) { class="type">int x,y,y1,y2; class=class="str">"cmt">//--- 创建图形边框和底图 m_canvas.FillRectangle(class="num">0,class="num">0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency)); m_canvas.FillRectangle(class="num">1,class="num">1,XSize()-class="num">2,YSize()-class="num">2,ColorToARGB(m_bg_color,m_transparency)); class=class="str">"cmt">//--- 创建坐标轴和图形背景 m_canvas.FillRectangle(m_gap-class="num">1,m_gap-class="num">1,XSize()-m_gap+class="num">1,YSize()-m_gap+class="num">1,ColorToARGB(m_border_color,m_transparency)); m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency)); class=class="str">"cmt">//--- 如果最大数据数组值超过 Y 轴上边界, 则坐标轴将被缩放。 if(data[ArrayMaximum(data)]>m_y_max) m_y_max=data[ArrayMaximum(data)]; class=class="str">"cmt">//--- 创建坐标轴刻度及其值 VerticalScale(m_y_min,m_y_max,m_num_grid); HorizontalScale(ArraySize(data)); ArrayResize(m_label_value,ArraySize(data)); class=class="str">"cmt">//--- 根据数据数组创建直方图 for(class="type">int i=class="num">0;i<ArraySize(data);i++) { if(data[i]>class="num">0) { x=class="type">int(m_x[i]+(m_x[i+class="num">1]-m_x[i])/class="num">2); y=class="type">int((YSize()-m_gap)-(YSize()-class="num">2*m_gap)/m_y_max*data[i]); y1=class="type">int((YSize()-m_gap)-(YSize()-class="num">2*m_gap)/m_y_max*data[i]*class="num">0.3); y2=class="type">int((YSize()-m_gap)-(YSize()-class="num">2*m_gap)/m_y_max*data[i]*class="num">0.1); y=(y<m_gap)?m_gap:y; if(m_type==SIMPLE) m_canvas.FillTriangle(m_x[i],YSize()-m_gap,m_x[i+class="num">1],YSize()-m_gap,x,y,ColorToARGB(m_graph_color1,m_transparency)); else if(m_type==TRIANGLE) { m_canvas.FillTriangle(m_x[i],YSize()-m_gap,x,y,x,y1,ColorToARGB(m_graph_color1,m_transparency)); m_canvas.FillTriangle(m_x[i],YSize()-m_gap,m_x[i+class="num">1],YSize()-m_gap,x,y1,ColorToARGB(m_graph_color2,m_transparency)); m_canvas.FillTriangle(x,y,x,y1,m_x[i+class="num">1],YSize()-m_gap,ColorToARGB(m_graph_color3,m_transparency)); } else if(m_type==RECTANGLE) { class="type">int x1,x2;
◍ 把多周期RSI塞进自定义直方图
这段实现里,直方图每根柱体被切成三等份来画。x1 取区间左三分之一处,x2 取右三分之一处,再用 FillRectangle 按设定透明度铺色,等于把相邻两个数据点之间的空间分成三段视觉块。 m_canvas.FontNameSet("Trebuchet MS") 与 FontSizeSet 决定了数值标签的字体与大小,TextOut 分别以 TA_CENTER|TA_VCENTER 居中输出具体数值(保留两位小数)和上方标签。注意 y2 与 y-5 的偏移量,直接控制文字会不会压到柱体。 下方指标输入给了三个 RSI 周期:10、14、18,分别对应 rsi1/rsi2/rsi3 三数组。外汇与贵金属波动下,这种多周期叠加直方图仅作概率参考,杠杆品种高风险,参数乱调可能让图形失去可比性。 开 MT5 把 RSIPeriod1 从 10 改成 7,刷新后左起第一块颜色区对应的 RSI 灵敏度会明显跳升,可直观验证周期参数对分块渲染的影响。
x1=class="type">int(m_x[i]+(m_x[i+class="num">1]-m_x[i])/class="num">3); x2=class="type">int(m_x[i]+class="num">2*(m_x[i+class="num">1]-m_x[i])/class="num">3); m_canvas.FillRectangle(x1,y,x2,YSize()-m_gap,ColorToARGB(m_graph_color1,m_transparency)); } class=class="str">"cmt">//--- 描述和数值 m_canvas.FontNameSet("Trebuchet MS"); m_canvas.FontSizeSet(m_value_font_size); m_canvas.TextOut(x,y2,DoubleToString(data[i],class="num">2),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER); m_canvas.TextOut(x,y-class="num">5,m_label_value[i],ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER); } } m_canvas.Update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">004.mq5 | class=class="str">"cmt">//| 版权所有 class="num">2017, Alexander Fedosov | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2017, Alexander Fedosov" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_plots class="num">0 class="macro">#class="kw">property indicator_chart_window class="macro">#include <CustomGUI\CustomGUI.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 指标输入 | class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">int RSIPeriod1=class="num">10; class=class="str">"cmt">//指标1 的周期 input class="type">int RSIPeriod2=class="num">14; class=class="str">"cmt">//指标2 的周期 input class="type">int RSIPeriod3=class="num">18; class=class="str">"cmt">//指标3 的周期 input ENUM_TYPE_HISTOGRAM Type=RECTANGLE; class=class="str">"cmt">//直方图类型 class=class="str">"cmt">//--- CHistogram ind; class="type">int InpInd_Handle1,InpInd_Handle2,InpInd_Handle3; class="type">class="kw">double rsi1[],rsi2[],rsi3[],ex[class="num">3]; class="type">class="kw">string descr[class="num">3]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() {
「三周期RSI句柄的初始化与取值」
多周期RSI叠加指标的核心在初始化阶段就把三个不同参数的RSI句柄一次性拿全。代码里用 iRSI 分别对当前图表品种、当前周期取 RSIPeriod1/2/3 三个闭合周期,任一返回 INVALID_HANDLE 就直接 Print 报错并 return(INIT_FAILED),避免后续空指针式崩坏。 拿到句柄后给图形层喂描述与边界:descr 数组用 IntegerToString 把周期数拼成 "RSI(14)" 这类标签,ind 对象设 NumGrid(10)、YMax(100)、画布 350x250,相当于把 0~100 的 RSI 空间切成 10 格。外汇与贵金属波动下 RSI 触及极值区的概率偏高,这类可视化边界能帮肉眼快速定位超买超卖。 OnCalculate 里每次只 CopyBuffer 取最新 1 根(起始 0、数量 1),三个缓冲任一拷贝失败就 return(0) 跳过本根。rsi1[0]、rsi2[0]、rsi3[0] 经 ArraySetAsSeries(true) 后指向当前柱,赋值给 ex 数组即完成三周期最新值同步,交易者可在 MT5 里改 RSIPeriod 参数验证不同周期共振现象。
class=class="str">"cmt">//---- 获取指标句柄 InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod1,PRICE_CLOSE); InpInd_Handle2=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod2,PRICE_CLOSE); InpInd_Handle3=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod3,PRICE_CLOSE); if(InpInd_Handle1==INVALID_HANDLE || InpInd_Handle2==INVALID_HANDLE || InpInd_Handle3==INVALID_HANDLE ) { Print("获取指标句柄失败"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- descr[class="num">0]="RSI("+IntegerToString(RSIPeriod1)+")"; descr[class="num">1]="RSI("+IntegerToString(RSIPeriod2)+")"; descr[class="num">2]="RSI("+IntegerToString(RSIPeriod3)+")"; ind.NumGrid(class="num">10); ind.YMax(class="num">100); ind.Type(Type); ind.Create("rsi",class="num">350,class="num">250); ind.SetArrayLabel(descr); class=class="str">"cmt">//--- ArraySetAsSeries(rsi1,true); ArraySetAsSeries(rsi2,true); ArraySetAsSeries(rsi3,true); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } 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 &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- if(CopyBuffer(InpInd_Handle1,class="num">0,class="num">0,class="num">1,rsi1)<=class="num">0 || CopyBuffer(InpInd_Handle2,class="num">0,class="num">0,class="num">1,rsi2)<=class="num">0 || CopyBuffer(InpInd_Handle3,class="num">0,class="num">0,class="num">1,rsi3)<=class="num">0 ) class="kw">return(class="num">0); ex[class="num">0]=rsi1[class="num">0]; ex[class="num">1]=rsi2[class="num">0];
把缓存和句柄在退出时清干净
指标计算收尾时先把上一根 RSI 值写进扩展缓冲区:ex[2]=rsi3[0] 把当前 RSI 序列第 0 号元素(最新值)存到扩展数组下标 2 的位置,再用 ind.SetArrayValue(ex) 推回绘图对象。最后 return(rates_total) 把已处理柱数交还 MT5 内核,避免下一 tick 重复算整段历史。 OnDeinit 里只做两件事:ind.Delete() 释放自定义图形资源,ChartRedraw() 强制重绘清空残留。实盘切周期或卸载指标时若漏掉 Delete,MT5 终端可能在日志刷出对象泄漏警告,贵金属品种切换更明显,属于高杠杆环境下的低级卡顿诱因。 验证方法很简单:把这段挂到 EURUSD 的 M5,手动切换周期十次,盯终端 Experts 标签有无 resource leak 字样即可。
ex[class="num">2]=rsi3[class="num">0]; ind.SetArrayValue(ex); class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { ind.Delete(); ChartRedraw(); }
◍ 在二维坐标里画等轴金字塔
CPyramid 类继承自前面写好的 CCanvasBase,专门解决一件事:在 MT5 的平面画布上用等轴投影画出伪 3D 金字塔。基类不含这个形状,所以核心工作量全在投影数学和分段着色上。 绘图前先解两条私有方法。Equation() 把任意两点的直线写成 y=kx+b 的形式,把斜率 k 和截距 b 塞进数组;Cross() 则用两个 k、b 联立求交点坐标。这两个函数是后面循环切平行基线、找 AB/AC 边交点的底层工具。 Create() 的算法很直白:先定 A、B 点坐标求左边直线方程,再在循环里不断取平行于底边的直线,算它与 AB 的交叉点,用这些点画出左侧区段和刻度;右侧如法炮制,并额外标刻度值。最后两区段垂直分离,留出版面。 更新数据的方法吃两个参数——当前值和最大值。它按最大值定阈值,每段越过阈值就染成活跃色,向下跌破则退回不活跃色。图例里 ADX demo 中,最大值设为 50、第三阈值 30,圆形对比指标显示在 30 上方,于是三个区段全按当前最大值着色。外汇与贵金属图表加载此类自定义指标存在渲染失败可能,实盘前请在策略测试器先跑一遍。 把类挂进通用列表只需在 CustomGUI.mqh 补一行 #include "Indicators\Pyramid.mqh",下面这段是 Equation、Cross 与 Create 头部的可直接拷进 Pyramid.mqh 的骨架。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CustomGUI.mqh | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Indicators\CircleSimple.mqh" class="macro">#include "Indicators\CircleArc.mqh" class="macro">#include "Indicators\CircleSection.mqh" class="macro">#include "Indicators\LineGraph.mqh" class="macro">#include "Indicators\LineRounded.mqh" class="macro">#include "Indicators\Hexagon.mqh" class="macro">#include "Indicators\Petal.mqh" class="macro">#include "Indicators\Histogram.mqh" class="macro">#include "Indicators\Pyramid.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 将两点直线方程比值写入数组 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPyramid::Equation(class="type">class="kw">double x1,class="type">class="kw">double y1,class="type">class="kw">double x2,class="type">class="kw">double y2,class="type">class="kw">double &arr[]) { ArrayResize(arr,class="num">2); arr[class="num">0]=(y1-y2)/(x1-x2); arr[class="num">1]=y2-arr[class="num">0]*x2; } class=class="str">"cmt">//+---------------------------------------------------------------------------------+ class=class="str">"cmt">//| 将两条直线交点的坐标写成其比率 | class=class="str">"cmt">//+---------------------------------------------------------------------------------+ class="type">void CPyramid::Cross(class="type">class="kw">double k1,class="type">class="kw">double b1,class="type">class="kw">double k2,class="type">class="kw">double b2,class="type">class="kw">double &arr[]) { class="type">class="kw">double x,y; ArrayResize(arr,class="num">2); y=(b1*k2-b2*k1)/(k2-k1); x=(y-b2)/k2; arr[class="num">0]=x; arr[class="num">1]=y; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建指标 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPyramid::Create(class="type">class="kw">string name,class="type">int x,class="type">int y) { class="type">int size=m_size; class=class="str">"cmt">//--- 相对于其大小修改指标位置 x=(x<size/class="num">2)?size/class="num">2:x; y=(y<size/class="num">2)?size/class="num">2:y; Name(name); X(x); Y(y); XSize(size); YSize(size); if(!CreateCanvas()) Print("错误。不能创建画布。"); class=class="str">"cmt">//--- 描述和数值 m_canvas.FontNameSet("Trebuchet MS");
「金字塔指标左右分区的绘制循环」
在 MT5 自定义指标里把画布切成左右两个三角区,核心是先算 AB、AC 两条斜边的直线方程,再让循环逐层填色。左半边以 x2=0、y2=4*YSize()/5 为基点,右半边对称到 x2=XSize(),两者都从顶点 x1=XSize()/2、y1=0 出发。 循环固定跑 5 次(i 从 5 到 1),每次用 Equation() 算出当前分层线,再用 Cross() 求它与斜边的交点坐标。左区 FillTriangle 时带了 m_transparency 透明度参数,右区同函数调用却没传该参,渲染层会按默认不透明处理,这是肉眼可调的视觉差异点。 右区循环末尾用 TextOut 在 x1+2、y2+YSize()/6 处写「 - 」刻度文本,对齐方式 TA_LEFT|TA_VCENTER。若你要把刻度改成真实价格百分比,直接替换该字符串并传入计算后的 double 即可,外汇与贵金属品种波动大,改完请在回测里确认文字不溢出画布右侧。 最后补一条中轴竖线 LineVertical(XSize()/2,0,YSize()) 并调 Update() 提交帧。整套逻辑无收益暗示,仅决定图形分层,实际信号概率仍取决于你接在 NewValue() 里的阈值判定。
m_canvas.FontSizeSet(m_font_size); m_canvas.FontAngleSet(class="num">200); class=class="str">"cmt">//--- 查找 AB 直线方程 class="type">class="kw">double x1,x2,y1,y2; x1=XSize()/class="num">2;y1=class="num">0; x2=class="num">0;y2=class="num">4*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line1); class=class="str">"cmt">//--- 构建指标的左侧部分 for(class="type">int i=class="num">5;i>class="num">0;i--) { class=class="str">"cmt">//--- 定义两点的坐标 y1=i*YSize()/class="num">5; y2=(i-class="num">1)*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line2); class=class="str">"cmt">//--- 定义 AB 边线与平行于金字塔底边直线之间的交叉点的坐标 Cross(m_line1[class="num">0],m_line1[class="num">1],m_line2[class="num">0],m_line2[class="num">1],m_cross); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_section_color[i-class="num">1],m_transparency)); class=class="str">"cmt">//--- 绘制刻度标记 m_canvas.LineAA(class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_scale_color,m_transparency)); } class=class="str">"cmt">//--- 查找 AC 直线方程 x1=XSize()/class="num">2;y1=class="num">0; x2=XSize();y2=class="num">4*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line1); class=class="str">"cmt">//--- 构建指标的右侧部分 for(class="type">int i=class="num">5;i>class="num">0;i--) { class=class="str">"cmt">//--- 定义两点的坐标 y1=i*YSize()/class="num">5; y2=(i-class="num">1)*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line2); class=class="str">"cmt">//--- 定义 AC 边线与平行于金字塔底边直线之间的交叉点的坐标 Cross(m_line1[class="num">0],m_line1[class="num">1],m_line2[class="num">0],m_line2[class="num">1],m_cross); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_section_color[i-class="num">1])); class=class="str">"cmt">//--- 绘制刻度标记 m_canvas.LineAA(class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_scale_color,m_transparency)); class=class="str">"cmt">//--- 绘制刻度数值 m_canvas.TextOut(class="type">int(x1+class="num">2),class="type">int(y2+YSize()/class="num">6)," - ",ColorToARGB(m_label_color,m_transparency),TA_LEFT|TA_VCENTER); } m_canvas.LineVertical(XSize()/class="num">2,class="num">0,YSize(),ColorToARGB(m_scale_color,m_transparency)); m_canvas.Update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置并更新指标数值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CPyramid::NewValue(class="type">class="kw">double value,class="type">class="kw">double maxvalue) { class=class="str">"cmt">//--- class="type">class="kw">double s; class="type">class="kw">color col; class=class="str">"cmt">//--- 查找 AB 直线方程
金字塔左右两区的三角填充逻辑
这段绘制代码把画布分成左右两个三角扇区,各自用 5 段水平切片做颜色映射。左侧以 AB 斜边为界,右侧以 AC 斜边为界,每段高度固定为 YSize()/5,循环从 i=5 递减到 1,正好覆盖整个金字塔高度的 4/5 区域。 核心在 Cross() 求交点:用斜边直线方程 m_line1 和水平切片线 m_line2 算出交叉点 m_cross,再以 (x1,0)、(x1,y1)、(m_cross[0],m_cross[1]) 三个点画 FillTriangle。左右两区都先填 m_section_color[i-1] 再叠填 col,col 由 value>=s 决定——value 够到该刻度就显色,否则退为 m_bg_color。 刻度数值 s 的算法是 maxvalue-(i-1)*maxvalue/5,也就是说第 5 段(i=5)对应 s=0,第 1 段对应 s=4*maxvalue/5,五等分自上而下排布。左侧刻度线用 clrWhite,右侧用 m_scale_color,透明度统一走 m_transparency 参数,开 MT5 把 m_transparency 从 100 调到 180 能直接看出层级对比变化。 外汇与贵金属图表加载这类自定义指标时波动剧烈,高杠杆下视觉信号仅作辅助,实际进出场仍需结合价格行为确认。
class="type">class="kw">double x1,x2,y1,y2; x1=XSize()/class="num">2;y1=class="num">0; x2=class="num">0;y2=class="num">4*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line1); class=class="str">"cmt">//--- 构建指标的左侧部分 for(class="type">int i=class="num">5;i>class="num">0;i--) { class=class="str">"cmt">//--- 查找刻度标记数值 s=maxvalue-(i-class="num">1)*maxvalue/class="num">5; class=class="str">"cmt">//--- 定义区段颜色 col=(value>=s)?m_section_color[i-class="num">1]:m_bg_color; class=class="str">"cmt">//--- 定义两点的坐标 y1=i*YSize()/class="num">5; y2=(i-class="num">1)*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line2); class=class="str">"cmt">//--- 定义 AB 边线与平行于金字塔底边直线之间的交叉点的坐标 Cross(m_line1[class="num">0],m_line1[class="num">1],m_line2[class="num">0],m_line2[class="num">1],m_cross); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_section_color[i-class="num">1],m_transparency)); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(col,m_transparency)); class=class="str">"cmt">//--- 绘制刻度标记 m_canvas.LineAA(class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(clrWhite)); } class=class="str">"cmt">//--- 查找 AC 直线方程 x1=XSize()/class="num">2;y1=class="num">0; x2=XSize();y2=class="num">4*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line1); class=class="str">"cmt">//--- 构建指标的右侧部分 for(class="type">int i=class="num">5;i>class="num">0;i--) { class=class="str">"cmt">//--- 查找刻度标记数值 s=maxvalue-(i-class="num">1)*maxvalue/class="num">5; class=class="str">"cmt">//--- 定义区段颜色 col=(value>=s)?m_section_color[i-class="num">1]:m_bg_color; class=class="str">"cmt">//--- 定义两点的坐标 y1=i*YSize()/class="num">5; y2=(i-class="num">1)*YSize()/class="num">5; Equation(x1,y1,x2,y2,m_line2); class=class="str">"cmt">//--- 定义 AC 边线与平行于金字塔底边直线之间的交叉点的坐标 Cross(m_line1[class="num">0],m_line1[class="num">1],m_line2[class="num">0],m_line2[class="num">1],m_cross); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_section_color[i-class="num">1],m_transparency)); m_canvas.FillTriangle(class="type">int(x1),class="num">0,class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(col,m_transparency)); class=class="str">"cmt">//--- 定义刻度标记 m_canvas.LineAA(class="type">int(x1),class="type">int(y1),class="type">int(m_cross[class="num">0]),class="type">int(m_cross[class="num">1]),ColorToARGB(m_scale_color,m_transparency)); class=class="str">"cmt">//--- 绘制刻度数值
◍ 把 ADX 塞进自建图形层
这段代码片段演示了如何在 MT5 自建 Canvas 图形层上叠加 ADX 读数,而不是用系统自带的副图。先通过 iADX 拿句柄,周期参数写死为 input 里的 10,最大值上限设 50,意味着 ADX 超过 50 的区域在视觉映射上会被压到顶格。 初始化里 ind1.Size(250) 定义了金字塔图形的像素边长,Create("pyramid",200,0) 把它锚在坐标 (200,0)。ind2 作为圆形标签层,半径写死 30,位置用 ind1 的尺寸反推:X 减 Size/3、Y 减 Size/4,保证标签不挡主体。 若句柄返回 INVALID_HANDLE 会直接 Print 并 INIT_FAILED,实盘加载前务必确认当前品种支持 ADX 计算。外汇与贵金属波动剧烈,这类自定义面板仅作态势参考,信号失效概率不低,请先在策略测试器跑一遍再上真仓。
m_canvas.TextOut(class="type">int(x1+class="num">2),class="type">int(y2+YSize()/class="num">6),DoubleToString(s,class="num">0),ColorToARGB(m_label_color,m_transparency),TA_LEFT|TA_VCENTER); } m_canvas.LineVertical(XSize()/class="num">2,class="num">0,YSize(),ColorToARGB(m_scale_color,m_transparency)); m_canvas.Update(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">005.mq5 | class=class="str">"cmt">//| 版权所有 class="num">2017, Alexander Fedosov | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2017, Alexander Fedosov" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_plots class="num">0 class="macro">#class="kw">property indicator_chart_window class="macro">#include <CustomGUI\CustomGUI.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 指标输入 | class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">int period=class="num">10; class=class="str">"cmt">//ADX 周期 input class="type">class="kw">double maxval=class="num">50; class=class="str">"cmt">//最大值 class=class="str">"cmt">//--- CPyramid ind1; CCircleSimple ind2; class="type">int InpInd_Handle; class="type">class="kw">double adx[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//---- 获取指标句柄 InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,period); if(InpInd_Handle==INVALID_HANDLE) { Print("获取指标句柄失败"); class="kw">return(INIT_FAILED); } ArraySetAsSeries(adx,true); class=class="str">"cmt">//--- ind1.Size(class="num">250); ind1.Create("pyramid",class="num">200,class="num">0); class=class="str">"cmt">//--- ind2.FontSize(ind1.FontSize()); ind2.LabelSize(ind1.FontSize()); ind2.Label("ADX"); ind2.Radius(class="num">30); ind2.Create("label",ind1.X()-ind1.Size()/class="num">3,ind1.Y()-ind1.Size()/class="num">4); class="kw">return(INIT_SUCCEEDED);
「把 ADX 缓冲喂给两个指标实例」
自定义指标的 OnCalculate 是 MT5 每根 K 线或每次报价触发的主循环入口。这里先通过 CopyBuffer 从句柄 InpInd_Handle 取主缓冲最近 2 根值写入 adx 数组,若返回值 ≤0 说明句柄失效或数据未就绪,直接 return(0) 让系统下次再试。 拿到 adx[0] 后,分别调用 ind1.NewValue(adx[0],maxval) 和 ind2.NewValue(adx[0]),把当前 ADX 数值推给两个封装好的统计实例——一个带极值参数,一个只记序列。注意返回的是 rates_total 而非 prev_calculated,意味着每次都重算全部柱,适合样本量小的统计指标,但在分钟级外汇品种上可能吃掉更多 CPU。 OnDeinit 在指标卸载或周期切换时跑,负责 ind1.Delete() 与 ind2.Delete() 清理内部状态,再 ChartRedraw() 强制重绘。贵金属与外汇杠杆品种的高波动期,若指标不释放旧对象,容易出现残留图形或句柄泄漏,建议每次改参数后观察终端 Experts 标签有无 handle 报错。
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 &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- if(CopyBuffer(InpInd_Handle,class="num">0,class="num">0,class="num">2,adx)<=class="num">0) class="kw">return(class="num">0); ind1.NewValue(adx[class="num">0],maxval); ind2.NewValue(adx[class="num">0]); class=class="str">"cmt">//--- 返回 prev_calculated 的数值以便下次调用 class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { ind1.Delete(); ind2.Delete(); ChartRedraw(); } class=class="str">"cmt">//+------------------------------------------------------------------+
把图形库搬到自己的终端里
CCanvas 这套基类把自定义图形指标的开发门槛压得很低,会调类、会放文件就能出图,想象空间基本只受你画图逻辑限制。 随文附件 MQL5.zip 体积 29.49 KB,里面 11 个 .mqh 函数库加 5 个 .mq5 指标示例,从 CanvasBase 基类到金字塔、花瓣、直方图都齐了。 部署时把压缩包里的 MQL5 文件夹直接塞进终端根目录,MT5 重启后就能在导航器里看到 CLineRounded、CHexagon 等现成指标。外汇与贵金属图表叠加这类自绘图形,仍属高风险辅助观察,别当成进出场唯一依据。 真要验证,先开 MT5 加载 CHistogram.mq5,看直方图形能否随品种切换实时重绘,再决定要不要改自己的参数。