期货量化交易软件:基于画布的指标为通道填充透明度
概述
赫兹量化在本文中,我将介绍一种创建自定义指标的方法,其绘图是利用标准库中的 CCanvas 类制作的。 我将着手处理特殊的指标,其需要用一种纯色填充两条线之间的区域。 在开始之前,我们将了解为什么要用画布,这也许是此类指标当前可用选项的最佳选择。 之后,我们将看到计算坐标所需的一些图表属性,以及涉及操控 CCanvas 的基本过程。
最终目标是结合到目前为止看到的所有内容来构建应用透明度的指标。 所有工作将只考虑在主图表窗口。 一旦我们的目标达成,我们就可以将其扩展到在子窗口里工作的指标。
本文的主题如下:

采用画布的原因
图表窗口属性
理解图表窗口属性
图表属性查看器指标
坐标转换
透明的 DRAW_FILLING
在子窗口指标中工作的扩展方法
采用画布的原因
有人会问,为什么要采用画布替代已在自定义指标中采用的 DRAW_FILLING? 这里至少有两个原因:
指标的颜色与其它指标、蜡烛和图表对象的颜色混杂
DRAW_FILLING 不支持透明度

图表窗口属性
为了开始绘制自定义图表,我们需要研究一些图表属性。 可以在文档中找到所有属性 若要获取这些属性值,我们需要调用相应的函数 ChartGetInteger 和 ChartGetDouble。 还有一个 ChartGetString,但我们在这里不会用到它。
我们打算把用到的属性按简述列出。 如果我们需要更多,我稍后会一并列出。
CHART_WIDTH_IN_PIXELS — 图表窗口的宽度,不包括价格标尺
CHART_HEIGTH_IN_PIXELS — 子窗口的高度,不包括日期标尺
CHART_PRICE_MAX — 对应于子窗口顶部的价格
CHART_PRICE_MIN — 对应于子窗口底部的价格
CHART_SCALE — 确定柱线之间的间距。 经过一些测试,我发现它是两个值的幂,由 pow(2, CHART_SCALE) 得到。
CHART_FISRT_VISIBLE_BAR — 图表上第一根可见的柱线,从左到右。
CHART_VISIBLE_BARS — 图表上可见柱线的数量。
理解图表窗口属性 这些属性可在下图中轻松看到。

属性 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGTH _IN_PIXELS,我们将用它们来确定我们需要创建的画布对象的大小,以便制作绘图。 当图表窗口发生变化时,如果这些属性也发生了变化,我们就需要调整画布大小。 为了更好地理解,我们将创建一个简单的指标,显示属性,以及它们如何根据价格变化和用户交互而变化。 我们已开始采用画布来了解指标绘制过程。 我已遵循这条路径创建了我的指标。 出于组织原则目的,我建议您也这样做。

一旦指标框架准备就绪,我们需要将 CCanvas 函数库添加到文件当中。 我们可以使用 #include 预编译指令来做到这一点。 然后我们创建 CCanvas 类的实例。 所有这些都紧跟在指标 #property 指令之后。 #property copyright "Copyright 2023, Samuel Manoel De Souza" #property link "https://www.mql5.com/en/users/samuelmnl" #property version "1.00" #property indicator_chart_window #include <Canvas/Canvas.mqh> CCanvas Canvas;
操控 CCanvas 时,我们需要做的第一件事是创建一个 OBJ_BITMAP_LABEL,并向其内附加资源。 如果您想将其添加到图表之中,通常在指标初始化里,调用 CreateBitampLabel(...) 方法,即可完成。 最后是删除 OBJ_BITMAP_LABEL 和附加到它的资源。 如果您想从图表中删除它,通常在指标逆初始化里,调用 Destory(void) 方法,即可完成。 与此同时,我们执行基本的绘图过程,其中包括擦除图形(清除或设置资源的默认像素值),制作图形,并更新资源。 画布流程的完整生存周期如下图所示。

为简单起见,我们将在一个名为 “Redraw” 的函数中暂留 "Erase","Draw","Update"。 在代码中编写所有内容,我们得到以下结构。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping Canvas.CreateBitmapLabel(0, 0, "Canvas", 0, 0, 200, 150, COLOR_FORMAT_ARGB_NORMALIZE); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Canvas.Destroy(); } //+------------------------------------------------------------------+ //| Custom indicator redraw function | //+------------------------------------------------------------------+ void Redraw(void) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw //--- add second draw //--- add ... draw //--- add last draw //--- canvas update Canvas.Update(); }
为了显示属性,我们将使用 TextOut 方法编写它们。 而这些属性值将以字符串形式存储在结构数组变量当中
struct StrProperty { string name; string value; };
结构可以如下。 然后我们可以在循环中输出它们的摘要。 由于我们还没有数组,我们将在 Redraw 函数中将数组作为参数传递。 然后,重绘函数将如下所示:
void Redraw(StrProperty &array[]) { uint default_color = ColorToARGB(clrBlack); uint text_color = ColorToARGB(clrWhite); //--- canvas erase Canvas.Erase(default_color); //--- add first draw int total = ArraySize(array); for(int i=0;i<total;i++) { int padding = 2; int left = padding, right = Canvas.Width() - padding, y = i * 20 + padding; Canvas.TextOut(left, y, array[i].name, text_color, TA_LEFT); Canvas.TextOut(right, y, array[i].value, text_color, TA_RIGHT); } //--- canvas update Canvas.Update(); }
最后我们就可以获取属性值,并输出它们。 如果您的代码还没有OnChartEvent函数处理程序,则您需要将其加入。 在那里,我们将检查 CHARTEVENT_CHART_CHANGE 事件 ID。 当有事件发生时,我们声明一些变量来获取属性值,并将它们传递给结构数组,然后调用 Redraw 函数。 那么我们启程吧。 我们可以编译指标,将其添加到图表中,并操作图表来查看画布更新。
//+------------------------------------------------------------------+ //| Custom indicator chart event handler function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE) return; int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); int chart_scale = (int)ChartGetInteger(0, CHART_SCALE); int chart_first_vis_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); int chart_vis_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); double chart_prcmin = ChartGetDouble(0, CHART_PRICE_MIN); double chart_prcmax = ChartGetDouble(0, CHART_PRICE_MAX); //--- StrProperty array[] { {"Width", (string)chart_width}, {"Height", (string)chart_height}, {"Scale", (string)chart_scale}, {"First Vis. Bar", (string)chart_first_vis_bar}, {"Visible Bars", (string)chart_vis_bars}, {"Price Min", (string)chart_prcmin}, {"Price Max", (string)chart_prcmax}, }; Redraw(array); }
坐标转换
此处,我们需要一些基本函数来从日期时间或柱线索引转换为像素坐标 x,以及把价格转换为像素坐标 y,或从 x 转换为柱线索引,以及从 y 转换为价格(其中一些我们现在不会用到,但我们可以一次将它们全部制作完成)。 有因于此,我们将图表属性变量移动到全局范围,而在 OnChartEvent 函数中,我们只会更新数值,并在需要时调用 Redraw 函数。 理想的解决方案是将变量和转换函数封装在类或结构中,但现在我们先保持简单。 不过,我建议您通过阅读文章面向对象编程的基础知识和文档中的相关主题(面向对象编程)来开始学习 OOP。 我们将在下一次机会中用到这些。