多交易品种多周期指标中的颜色缓冲区·综合运用
🎨

多交易品种多周期指标中的颜色缓冲区·综合运用

(3/3)· 前面两篇铺好了多品种多周期框架,这一篇用颜色缓冲区把整套指标画成可读的多色线

含代码示例偏理论 第 3/3 篇
很多人以为多周期指标只要把数据拷过来就能画,结果一条线只有单色,状态切换全靠肉眼猜。颜色缓冲区没接对,指标再准也难一眼分出多空力度。这篇把 30 多个内置指标的颜色渲染路径一次讲清。
本章目录
  1. 按绘图类型分配缓冲区数量
  2. 按绘制类型分配缓冲区数量
  3. 按绘图类型初始化指标缓冲区
  4. 按绘图类型算初始化命中率
  5. 缓冲区初始化与多数组聚合返回值
  6. 给指标缓冲区绑绘制类型与初始值
  7. 缓冲偏移与调色板索引的存取接口
  8. 多周期指标基类的私有字段布局
  9. 指标类里那些被忽略的缓冲与参数比对方法
  10. 参数比较与缓冲区拷贝的实现细节
  11. 给指标缓冲区配色与偏移的接口细节
  12. 跨品种指标缓冲区的取值与状态接口
  13. 指标句柄对象的成员设定与同步判定
  14. 指标对象的只读访问器怎么写
  15. 指标多周期对象的底层访问接口
  16. 多维度比较与参数全等判定
  17. 析构与缓冲回收的坑
  18. 多周期指标缓冲区的取色与改色接口
  19. 缓冲区绘制属性与越界拦截的实现细节
  20. 多周期指标缓冲区的取数与越界保护
  21. 缓冲区数据拉取的分支处理
  22. 双缓冲与四缓冲的数组回填差异
  23. 多缓冲区的数组回填与颜色索引同步
  24. 双色直方图与彩K的缓冲写入细节
  25. 多缓冲区的数组回填逻辑
  26. 按绘图类型拆分缓冲区拷贝逻辑
  27. 多周期指标同步失败的三种早退分支
  28. 指标重算时的缓冲区与数据拷贝逻辑
  29. 新K线触发时的双柱拷贝分支
  30. 把类缓冲塞进绘图与配色数组
  31. 跨周期取数时的空数据与错位处理
  32. 高周期图表下的跨周期取值写法
  33. 跨周期缓冲写入的边界处理
  34. 跨周期填充指标缓冲的边界处理
  35. 用 CopyTime 对齐多周期指标缓冲
  36. 跨周期取数前先做有效性拦截
  37. 用三段位移判定指标线的微观状态
  38. 跨周期取线状态时的柱映射逻辑
  39. 用三根K线定位指标折返状态
  40. 多时段指标线的相对位置判定逻辑
  41. 多周期指标线与参考位的相对状态判定
  42. 从多周期缓冲里取色值时的边界判定
  43. 跨周期取色缓冲的索引对齐逻辑
  44. 缓冲区的序列方向与数据量怎么查
  45. 多周期指标基类的缓冲区与色彩计数
  46. A/D 指标类的缓冲区与参数装配
  47. ADX 多周期封装里的缓冲区与周期兜底
  48. ADX 缓冲区的描述与配色落地
  49. 给 ADX Wilder 的三条线定好身份与配色
  50. 鳄鱼指标构造里的参数兜底逻辑
  51. 鳄鱼指标的非当前图表参数拼接与画线着色
  52. AMA 多周期封装里的参数兜底逻辑
  53. 在自定义指标类里塞进参数与画线设定
  54. ATR 封装里的参数兜底与双色直方图
  55. 熊力指标类的缓冲区与参数封装
  56. Bulls Power 类的缓冲区与参数初始化
  57. 布林带类的缓冲区与参数装配
  58. 布林带缓冲区的绘制与配色设定
  59. 给 CCI 多周期封装补上绘制属性
  60. 封装 Chaikin 振荡器的类构造细节
  61. 给 CHO 与 DEMA 套上指标外壳
  62. 给自封装指标挂上身份与画线样式
  63. DeMarker 初始化里的参数兜底与画线设定
  64. 信封指标参数数组的初始化细节
  65. 给通道线挂上名字和颜色
  66. 给力指数和分形指标填元数据
  67. 分形与FRAMA指标的类初始化细节
  68. FRAMA 初始化里的缓冲区与描述绑定
  69. Gator 震荡器构造里的参数兜底逻辑
  70. 鳄鱼振荡器的缓冲区与配色落地
  71. 封装一目均衡表的多周期指标类
  72. 在类里把一目均衡与BW-MFI的元数据钉死
  73. BW MFI 的绘图缓冲与配色初始化
  74. 动量指标构造里的参数兜底与画线设定
  75. MFI 指标对象的参数与画线初始化
  76. MA 构造函数里的参数兜底与描述组装
  77. OSMA 指标类的参数兜底逻辑
  78. OsMA 缓冲区的绘制与精度设定
  79. 给 MACD 指标对象塞参数与画线设定
  80. OBV 指标类的构造与缓冲区设定
  81. SAR 指标的面向对象封装细节
  82. RSI 多周期封装里的参数兜底
  83. RVI 指标的初始化与缓冲区设定
  84. 在自定义指标里接管缓冲区与标准差参数
  85. StdDev 指标对象的参数与画线初始化
  86. Stochastic 多周期封装里的参数兜底逻辑
  87. 给 Stochastic 和 TEMA 指标类填参数与画线属性
  88. 给 TEMA 指标绑上参数与画线样式
  89. TRIX 指标实例化的参数与画线设定
  90. 给 WPR 和 VIDyA 套上多周期外壳
  91. VIDYA 封装时的参数兜底与描述生成
  92. 在指标类里接管成交量与画线缓冲
  93. 自定义指标类的缓冲区与参数初始化
  94. 指标参数回填与集合类的句柄查找
  95. 给指标缓冲区上色与取数的底层接口
  96. 跨周期取色与缓冲拷贝的接口细节
  97. 指标缓冲区状态与定时器遍历的实现细节
  98. 跨周期取数时的对象指针与空值防护
  99. 把多周期指标的颜色缓冲灌进图表
  100. 缓冲区颜色索引的读写接口
  101. 给多周期框架塞进 AC 与 CCI 的封装手法
  102. 给自编指标挂上±100边界线
  103. 用仪表板类批量验证多周期指标绘制
  104. 双周期AD线的线宽与缓冲绑定
  105. 面板初始化与三表布局的落地写法
  106. 多周期指标的计算与事件分流
  107. 把鼠标位置换算成可见的K线数据
  108. 把K线和指标塞进自定义表格面板
  109. 把指标缓冲状态写进面板表格
  110. 双周期AC缓冲区的初始化套路
  111. 双周期AC指标与面板初始化的线宽切换
  112. 指标生命周期里的初始化与逐根计算
  113. 把鼠标位置换算成可读取的 K 线索引
  114. 给面板表格填K线字段的绘制细节
  115. 把双指标状态塞进面板表格
  116. 两条均线相对位置的面板文字判定
  117. 鳄鱼三线的绘制与参数声明
  118. 双层鳄鱼线的输入参数与缓冲绑定
  119. 同图叠加双周期鳄鱼线的初始化细节
  120. 面板表格堆叠与生命周期钩子
  121. 多周期鳄鱼指标的缓冲回填与事件钩子
  122. 把鼠标位置换算成可读的 K 线数据
  123. 把K线和鳄口数据铺进面板格子
  124. 把双鳄鱼线状态塞进面板单元格
  125. 双周期ADX指标窗口的声明骨架
  126. 多周期 ADX 面板的输入与缓冲区骨架
  127. 双周期ADX缓冲与面板初始化的接线细节
  128. 面板表格堆叠与指标生命周期衔接
  129. 把多周期 ADX 塞进缓冲区的收尾写法
  130. 光标定位后怎么把K线数据甩到面板
  131. 把K线与指标值灌进面板表格
  132. 双指标面板里的相对位置判定
  133. 双周期 ADXW 指标的窗口与画线声明
  134. 多周期ADX面板指标的输入与缓冲区声明
  135. 双周期ADX面板与缓冲绑定的初始化细节
  136. 多表纵向堆叠的坐标衔接
  137. 多周期 ADX 缓冲回填与图表事件接管
  138. 把鼠标位置换算成可显示的 K 线索引
  139. 把K线与指标值写进面板表格
  140. 双指标面板的状态与相对位置输出
  141. 多周期 ATR 对照指标的骨架
  142. 双周期ATR与面板初始化的缓冲区排布
  143. 面板表格的堆叠与生命周期收尾
  144. 多周期指标在 OnInit 之后的运转骨架
  145. 把鼠标所在 K 线塞进面板表格
  146. 把OHLC和指标值写进自定义面板格子
  147. 在面板里把双指标关系画出来
  148. 双周期 AO 指标的头文件声明方式
  149. 初始化里把双周期AO和面板钉死
  150. 面板表格的堆叠与生命周期收口
  151. 多周期指标的计算与光标联动
  152. 把光标所在 K 线塞进自定义面板
  153. 把K线与指标塞进面板单元格
  154. 在面板里画出两条指标线的相对位置
  155. 双周期空方动能的直方图骨架
  156. 双周期熊力指标的初始化与面板挂载
  157. 面板表格堆叠与指标生命周期衔接
  158. 多周期指标回写与光标定位的实现细节
  159. 把指定K线数据刷进自定义面板
  160. 把K线与指标塞进面板表格
  161. 把双线状态与穿越关系画进面板
  162. 双层布林带指标的绘图与输入声明
  163. 双周期布林带的输入与缓冲区绑定
  164. 双层布林带与面板初始化的绑定细节
  165. 面板表格堆叠与指标生命周期衔接
  166. 把多周期轨道算完再吐给缓冲区
  167. 鼠标悬停取柱与面板绘制的数据链路
  168. 把K线与指标值写进面板表格
  169. 把两条指标线的相对位置画进面板
  170. 多周期牛势动能的直方图骨架
  171. 双周期 BullsPower 与面板初始化的缓冲区排布
  172. 面板初始化与多表布局的衔接逻辑
  173. 多周期指标的计算与事件回调衔接
  174. 把鼠标悬停的K线塞进面板表格
  175. 把OHLC和指标值写进面板单元格
  176. 把两条指标线的关系画进面板
  177. 在副窗挂出双线动量通道的声明段
  178. 双周期 Chaikin 线的宽度与序列初始化
  179. MT5 面板与表格的初始化落点
  180. 多周期指标的计算与事件回调骨架
  181. 鼠标悬停读出光标所在 K 线明细
  182. 把K线与指标塞进表格面板
  183. 把双指标线与相对位置写进面板
  184. 多周期 CCI 指标的骨架怎么搭
  185. 双周期CCI面板的初始化细节
  186. 面板初始化与多表布局的落地写法
  187. 多周期 CCI 指标的缓冲回填与事件响应
  188. 把光标位置换算成可显示的K线数据
  189. 把K线与指标值灌进表格面板
  190. 双 CCI 面板里的相对位置判定
  191. 多周期 DeMarker 指标的双线骨架
  192. 双周期 DeMarker 与面板对象的初始化落地
  193. 面板初始化与多表格堆叠的坐标计算
  194. 多周期指标的计算与事件回调接线
  195. 鼠标悬停读某根 K 线的落地写法
  196. 把K线与指标塞进表格面板
  197. 双指标线态与相对位置的可视化落点
  198. 双轨信封指标的头文件声明
  199. 双周期轨道线的输入与缓冲绑定
  200. 双包络线面板与线宽切换的实现细节
  201. 双表布局与生命周期的衔接点
  202. 把双轨道包络线灌进缓冲并接住鼠标事件
  203. 把指定K线数据刷进面板表格
  204. 把K线与轨道数据填进面板表格
  205. 双轨道状态与相对位置的面板绘制
  206. 双线力度指标的缓冲区与输入参数声明
  207. 双周期力道指标的初始化与面板挂载
  208. 面板的表格堆叠与生命周期清理
  209. 把多周期指标算完再喂给缓冲
  210. 把指定K线数据刷到面板表格里
  211. 把K线高低收与指标值写进面板表格
  212. 双指标线态与相对位置的可视化落点
  213. 双周期MACD的指标声明怎么写
  214. 双周期MACD缓冲区的初始化套路
  215. 双 MACD 与面板在初始化里的接法
  216. 指标卸载与逐根重算的边界处理
  217. 把鼠标位置换算成可见的K线数据
  218. 把单根 K 线数据铺到面板表格里
  219. 把双 MACD 读数塞进面板表格
  220. 双振荡器相对位置的面板绘制
  221. 双周期 OsMA 指标的输入参数与缓冲区骨架
  222. 双周期 OsMA 面板与缓冲绑定细节
  223. 面板销毁与逐根重算的边界处理
  224. 鼠标悬停读出光标所在棒数据
  225. 把单根K线塞进面板表格
  226. 把双周期 OSMA 塞进面板单元格
  227. 双指标线态关系的面板绘制与相对位置判定
  228. 双周期 BWMFI 指标的缓冲区与输入定义
  229. 双周期BWMFI面板与缓冲绑定的初始化细节
  230. 面板双表布局与指标生命周期钩子
  231. 把指标缓冲与鼠标交互接进面板
  232. 把任意 K 线数据搬进自定义面板
  233. 在面板里把K线和双指标状态画出来
  234. 双动量线相对状态的可视化落地
  235. 多周期动量指标的缓冲区与输入声明
  236. 双动量缓冲与面板布局的初始化写法
  237. 面板初始化与逐棒刷新的落地细节
  238. 把鼠标位置换算成可见的棒线数据
  239. 把指定 K 线的裸数据刷进面板
  240. 把双动量指标塞进面板表格
  241. 双指标线与面板状态的相对关系绘制
  242. 多周期 MFI 指标的缓冲区与输入定义
  243. 双周期 MFI 面板与线宽自适应
  244. 指标生命周期里的面板与计算衔接
  245. 把鼠标位置换算成可读的K线数据
  246. 把指定 K 线的盘口数据画进面板
  247. 把双 MFI 读数塞进表格面板
  248. 双指标线相对状态的面板绘制逻辑
  249. 双均线面板的输入与缓冲区定义
  250. 初始化里双均线句柄的分工
  251. 双均线句柄失效就别硬跑,面板先搭起来
  252. 多周期指标在主图里的刷新与取数
  253. 把鼠标位置换算成可显示的K线索引
  254. 把K线与指标值写进面板表格
  255. 双均线面板里的相对位置判定
  256. 把多周期 OBV 搬进独立窗口做验证
  257. OBV 指标面板的初始化骨架
  258. 面板表格堆叠与生命周期回收
  259. 把光标位置喂给多周期指标
  260. 把指定K线塞进自定义面板表格
  261. 把OBV和K线高低收写进面板单元格
  262. 在主图叠两组抛物线箭头
  263. 在 OnInit 里把双周期面板钉死
  264. 面板表格堆叠与指标生命周期钩子
  265. 把多周期指标塞进缓冲区并接住鼠标事件
  266. 把指定K线数据画进面板表格
  267. 把K线与双PSAR塞进表格面板
  268. 在面板上画出双指标线与相对位置
  269. 双周期RSI指标的声明与输入配置
  270. 初始化里把双周期RSI和面板搭起来
  271. 面板表格堆叠与指标生命周期衔接
  272. 把光标所在 K 线喂给多周期指标
  273. 把指定K线数据搬进自定义面板
  274. 把K线与双RSI塞进面板表格
  275. 双线状态与相对位置的面板绘制
  276. 多周期RVI指标的绘图与输入声明
  277. 双周期 RVI 缓冲区的初始化挂接
  278. 面板与表格的初始化落点
  279. 把多周期RVI塞进自定义指标缓冲区
  280. 把鼠标位置换算成可见的K线数据
  281. 把当前K线与RVI读数写进面板表格
  282. 双指标面板里的相对位置判定
  283. 把标准差画进独立窗口的双线指标
  284. 双周期标准差指标的缓冲区与线宽初始化
  285. 同周期与异周期标准差指标的面板初始化
  286. 指标卸载与逐根重算的底层钩子
  287. 鼠标悬停读出光标所在 K 线
  288. 在自定义面板上铺开当前K线裸数据
  289. 双标准差指标的状态同屏输出
  290. 面板文字与指标窗口的衔接写法
  291. 多周期随机指标的参数骨架
  292. 双随机指标与面板的初始化落地
  293. 面板初始化与多表布局的衔接细节
  294. 把多周期随机指标塞进自定义缓冲
  295. 鼠标悬停读取光标所在 K 线数据
  296. 把K线和指标值塞进表格面板
  297. 把双指标线态与交叉关系写进面板
  298. 多周期TRIX指标的MT5工程骨架
  299. 双周期 TRIX 的缓冲与面板初始化
  300. 面板初始化与多表布局的落点
  301. 多周期指标的计算与事件回调接线
  302. 把光标位置映射成可显示的柱数据
  303. 把K线与TRIX读数灌进面板表格
  304. 把双 TRIX 线的相对位置写进面板
  305. 多周期成交量指标的骨架怎么搭
  306. 双周期量能缓冲区的初始化绑定
  307. 把指标线和面板画进 MT5 界面
  308. 多周期量能指标的刷新与事件接管
  309. 鼠标悬停实时回写K线数据到面板
  310. 把K线字段塞进表格单元
  311. 双指标表格的逐格绘制与相对位置判定
  312. 把 WPR 双线塞进独立子窗口
  313. 双周期WPR的线宽与缓冲初始化
  314. 面板与多表布局的初始化拆解
  315. 多周期指标的计算与事件回调落地
  316. 把鼠标位置换算成可读的K线数据
  317. 把K线与指标值填进面板表格
  318. 双指标线态与相对位置的可视化输出
  319. 跨周期取数失败时的强制切周期操作

按绘图类型分配缓冲区数量

自定义指标里,绘图样式直接决定了需要几个数据缓冲区。上面这段逻辑按 DRAW_xxx 枚举做了分支处理:单缓冲区类(直方图、箭头、线段)只需对 array0 做 ArrayResize;双缓冲区类(双向直方图、之字形、填充区)要同时扩 array0 和 array1;四缓冲区类(蜡烛、柱线)则一次扩 array0~array3。 颜色线、颜色直方图这类「主缓冲 + 颜色缓冲」的组合,在分支里也归到单缓冲扩展之后继续挂颜色数组,代码截到 DRAW_COLOR_HISTOGRAM 为止还没写完 return。 开 MT5 新建指标时,若绘图类型选 DRAW_CANDLES 却只给两个缓冲,运行时 res 的按位与会直接为假,图表不画东西——缓冲数必须和样式严格对齐。

MQL5 / C++
case DRAW_HISTOGRAM     :
case DRAW_ARROW       :
case DRAW_SECTION     :
             class="kw">return ArrayResize(array0,new_size)==new_size;
             class=class="str">"cmt">//--- Two buffers
             case DRAW_HISTOGRAM2     :
             case DRAW_ZIGZAG       :
             case DRAW_FILLING       :
                  res   =(ArrayResize(array0,new_size)==new_size);
                  res &=(ArrayResize(array1,new_size)==new_size);
                  class="kw">return res;
             class=class="str">"cmt">//--- Four buffers
             case DRAW_BARS         :
             case DRAW_CANDLES       :
                  res   =(ArrayResize(array0,new_size)==new_size);
                  res &=(ArrayResize(array1,new_size)==new_size);
                  res &=(ArrayResize(array2,new_size)==new_size);
                  res &=(ArrayResize(array3,new_size)==new_size);
                  class="kw">return res;
             class=class="str">"cmt">//--- One buffer + class="type">color buffer
             case DRAW_COLOR_LINE      :
             case DRAW_COLOR_HISTOGRAM  :

◍ 按绘制类型分配缓冲区数量

自定义指标里,不同 DRAW_COLOR_* 类型对数组缓冲区的需求量完全不同,写动态扩容逻辑时得先判断类型再操作。 下面这段分支处理的是颜色类绘制样式:DRAW_COLOR_ARROW 与 DRAW_COLOR_SECTION 只需主数组加一个颜色索引数组,共 2 个缓冲区;而 DRAW_COLOR_HISTOGRAM2、DRAW_COLOR_ZIGZAG 需要双数值数组加颜色索引,共 3 个;DRAW_COLOR_BARS、DRAW_COLOR_CANDLES 则要吃下 4 个数值数组再加颜色索引,共 5 个缓冲区。 所有分支都用 ArrayResize 的返回值与 new_size 做等值比对,并用 &= 串联,只有全部 resize 成功才返回 true,否则走 default 直接 break。开 MT5 新建指标时,把缓冲区数量配错会直接编译不过或画图残缺,建议照此表核对。

MQL5 / C++
case DRAW_COLOR_ARROW :
case DRAW_COLOR_SECTION :
  res  =(ArrayResize(array0,new_size)==new_size);
  res &=(ArrayResize(color_indexes,new_size)==new_size);
  class="kw">return res;
  class=class="str">"cmt">//--- Two buffers + class="type">color buffer
case DRAW_COLOR_HISTOGRAM2 :
case DRAW_COLOR_ZIGZAG :
  res  =(ArrayResize(array0,new_size)==new_size);
  res &=(ArrayResize(array1,new_size)==new_size);
  res &=(ArrayResize(color_indexes,new_size)==new_size);
  class="kw">return res;
  class=class="str">"cmt">//--- Four buffers + class="type">color buffer
case DRAW_COLOR_BARS :
case DRAW_COLOR_CANDLES :
  res  =(ArrayResize(array0,new_size)==new_size);
  res &=(ArrayResize(array1,new_size)==new_size);
  res &=(ArrayResize(array2,new_size)==new_size);
  res &=(ArrayResize(array3,new_size)==new_size);
  res &=(ArrayResize(color_indexes,new_size)==new_size);
  class="kw">return res;
  class=class="str">"cmt">//---DRAW_NONE
class="kw">default:
  break;

「按绘图类型初始化指标缓冲区」

自定义指标里,不同 draw_type 决定了要预置几个数据缓冲区。InitArrays 这个函数用 switch 根据当前绘图类型分流,避免手动逐个数组去清零。 单缓冲类型(线、直方图、箭头、线段)直接对 array0 调 ArrayInitialize 并返回结果;双缓冲类型(双层直方图、之字、填充区)则对 array0、array1 分别初始化,返回两次调用的均值 res/2。 四缓冲类型(仿K线柱、仿蜡烛)在原文片段中只看到了对 array0 的初始化开头,后续 array1~array3 的初始化需接原文往下看。外汇与贵金属指标开发杠杆高、回测偏差大,直接拷这段代码到 MT5 的 MQ5 文件里补全剩余分支再编译验证更稳妥。

MQL5 / C++
         class="kw">return false;
       }
class=class="str">"cmt">//--- Initializes all arrays
   class="type">int                InitArrays(class="type">void)
      {
         class="type">bool res=class="num">0;
         class="kw">switch(draw_type)
           {
            class=class="str">"cmt">//--- One buffer
            case DRAW_LINE           :
            case DRAW_HISTOGRAM      :
            case DRAW_ARROW          :
            case DRAW_SECTION        :
               class="kw">return ArrayInitialize(array0,init_value);
            class=class="str">"cmt">//--- Two buffers
            case DRAW_HISTOGRAM2     :
            case DRAW_ZIGZAG         :
            case DRAW_FILLING        :
               res+=ArrayInitialize(array0,init_value);
               res+=ArrayInitialize(array1,init_value);
               class="kw">return res/class="num">2;
            class=class="str">"cmt">//--- Four buffers
            case DRAW_BARS           :
            case DRAW_CANDLES        :
               res+=ArrayInitialize(array0,init_value);

按绘图类型算初始化命中率

自定义指标里用 ArrayInitialize 批量填缓冲,不是无脑全填就完事,得看绘图类型对应几个数组。上面这段逻辑把常见 DRAW_ 类型分了组,每组返回 res 除以不同分母,这个分母就是该类型实际参与初始化的数组个数。 普通多缓冲线(如四个数组的线型)走 res/4:array1、array2、array3 各填一次 init_value,加上前面未列的 array0,共 4 个数组被初始化,返回平均值便于外部判断填充成功率。 带颜色缓冲的类型要单独算:DRAW_COLOR_LINE、DRAW_COLOR_HISTOGRAM 等单数据缓冲+颜色索引的,只初始化 array0 和 color_indexes 两个,返回 res/2;DRAW_COLOR_HISTOGRAM2、DRAW_COLOR_ZIGZAG 这类双数据缓冲+颜色的,初始化 array0、array1、color_indexes 三个,返回 res/3。 外汇与贵金属指标在高波动时段重算缓冲频繁,这种按类型精确初始化能减少多余写入;MT5 里改绘图类型后,记得核对分母是否跟着变,否则初始化计数会偏。

MQL5 / C++
res+=ArrayInitialize(array1,init_value);
res+=ArrayInitialize(array2,init_value);
res+=ArrayInitialize(array3,init_value);
class="kw">return res/class="num">4;
class=class="str">"cmt">//--- One buffer + class="type">color buffer
case DRAW_COLOR_LINE       :
case DRAW_COLOR_HISTOGRAM  :
case DRAW_COLOR_ARROW      :
case DRAW_COLOR_SECTION    :
   res+=ArrayInitialize(array0,init_value);
   res+=ArrayInitialize(color_indexes,init_color_idx);
   class="kw">return res/class="num">2;
class=class="str">"cmt">//--- Two buffers + class="type">color buffer
case DRAW_COLOR_HISTOGRAM2 :
case DRAW_COLOR_ZIGZAG     :
   res+=ArrayInitialize(array0,init_value);
   res+=ArrayInitialize(array1,init_value);
   res+=ArrayInitialize(color_indexes,init_color_idx);
   class="kw">return res/class="num">3;
class=class="str">"cmt">//--- Four buffers + class="type">color buffer
case DRAW_COLOR_BARS       :
case DRAW_COLOR_CANDLES    :
   res+=ArrayInitialize(array0,init_value);

◍ 缓冲区初始化与多数组聚合返回值

这段片段展示了一个指标缓冲区封装类里的初始化分支:对 array1、array2、array3 以及 color_indexes 分别调用 ArrayInitialize,再用 res 累加每次初始化的返回元素数,最后除以 5 得出平均初始化长度。 注意 res 累加的是 ArrayInitialize 的返回值(即被初始化的元素个数),不是布尔成功标志;除以 5 意味着这里固定参与了 4 个 double 数组加 1 个颜色索引数组,共 5 次数组操作。 public 段声明的 array0~array3 与 color_indexes 就是指标实际绑定的缓冲区,clrs 存对应颜色、descript 存描述文本;IsColoredBuffer() 直接返回内部 colored 标志,用于上层判断该缓冲是否走颜色索引绘制。 在 MT5 里复刻时,若你的缓冲区数量不是 5 个,照搬 res/5 会得到错误的平均长度,应改为实际初始化调用次数做分母。外汇与贵金属指标开发属高风险环境,缓冲区越界可能静默画出畸形波形。

MQL5 / C++
res+=ArrayInitialize(array1,init_value);
res+=ArrayInitialize(array2,init_value);
res+=ArrayInitialize(array3,init_value);
res+=ArrayInitialize(color_indexes,init_color_idx);
class="kw">return res/class="num">5;
class=class="str">"cmt">//---DRAW_NONE
class="kw">default:
   break;
}
class="kw">return false;
}
class="kw">public:
   class="type">class="kw">double      array0[];            class=class="str">"cmt">// Indicator&class="macro">#x27;s buffer-array0
   class="type">class="kw">double      array1[];            class=class="str">"cmt">// Indicator&class="macro">#x27;s buffer-array1(2nd array for calculation)
   class="type">class="kw">double      array2[];            class=class="str">"cmt">// Indicator&class="macro">#x27;s buffer-array2(3rd array for calculation)
   class="type">class="kw">double      array3[];            class=class="str">"cmt">// Indicator&class="macro">#x27;s buffer-array3(4th array for calculation)
   class="type">class="kw">double      color_indexes[];     class=class="str">"cmt">// Buffer array of indicator class="type">color indexes
   class="type">color       clrs[];              class=class="str">"cmt">// Array of colors assigned to the buffer
   class="type">class="kw">string      descript;            class=class="str">"cmt">// Buffer description
class=class="str">"cmt">//--- Returns the class="type">color buffer flag
   class="type">bool        IsColoredBuffer(class="type">void) { class="kw">return colored; }
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the buffer drawing style, (class="num">3) the number of the corresponding buffer of the source indicator

「给指标缓冲区绑绘制类型与初始值」

自写指标里,每个绘图缓冲区都要先定好「画成什么」以及「从哪个源缓冲取数」。下面这段封装把绘制类型和源缓冲序号一次性写进对象内部,外部只管调 SetBufferDrawType 即可。 void SetBufferDrawType(const ENUM_DRAW_TYPE type,const uint buff_source) { draw_type=type; switch(draw_type) { case DRAW_COLOR_LINE : case DRAW_COLOR_SECTION : case DRAW_COLOR_HISTOGRAM : case DRAW_COLOR_HISTOGRAM2 : case DRAW_COLOR_ARROW : case DRAW_COLOR_ZIGZAG : case DRAW_COLOR_BARS : case DRAW_COLOR_CANDLES : colored=true; break; default : colored=false; break; } } 逐行拆解:函数入口接收绘制枚举 type 与源缓冲编号 buff_source;前两行把入参存进成员变量 draw_type 和 source。switch 对 type 做分类——8 种 DRAW_COLOR_* 枚举被归为「带色绘制」,colored 置 true,其余一律 colored=false。注意这里没有 break 穿透处理,前 7 个 case 落到第 8 个才统一 break,属于刻意合并。 ENUM_DRAW_TYPE DrawType(void) { return draw_type; } uint BufferFrom(void) { return source; } 上面两个 getter 分别吐回绘制类型与源缓冲号,方便后续在 IndicatorBuffers 配置前做自检。 //--- (1) Sets, (2) returns the initializing value void SetInitValue(const double value) { init_value=value; } double InitValue(void) { return init_value; } 初始值接口很直白:SetInitValue 写入该缓冲的 init_value,InitValue 读回。MT5 里若缓冲首值不显式初始化,可能画出从 0 起始的误连线,外汇与贵金属波动大,这类视觉错误会干扰价格行为判断。 开 MT5 新建一个带色直方图指标,把 SetBufferDrawType(DRAW_COLOR_HISTOGRAM,1) 接上第 1 号计算缓冲,再给 SetInitValue(EMPTY_VALUE) 看首根 K 线是否干净不连线。

MQL5 / C++
class="type">void SetBufferDrawType(const ENUM_DRAW_TYPE type,const class="type">uint buff_source)
  {
   draw_type=type;
   
   class="kw">switch(draw_type)
     {
      case DRAW_COLOR_LINE       :
      case DRAW_COLOR_SECTION    :
      case DRAW_COLOR_HISTOGRAM  :
      case DRAW_COLOR_HISTOGRAM2 :
      case DRAW_COLOR_ARROW      :
      case DRAW_COLOR_ZIGZAG     :
      case DRAW_COLOR_BARS       :
      case DRAW_COLOR_CANDLES    :  colored=true;  break;
      class="kw">default                    :  colored=false; break;
     }
  }
ENUM_DRAW_TYPE DrawType(class="type">void) { class="kw">return draw_type; }
class="type">uint BufferFrom(class="type">void)      { class="kw">return source; }
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the initializing value
class="type">void SetInitValue(const class="type">class="kw">double value) { init_value=value; }
class="type">class="kw">double InitValue(class="type">void)            { class="kw">return init_value; }

缓冲偏移与调色板索引的存取接口

在自定义指标类里,init_color_idx 与 shift 是两个常被忽略却决定绘图起点的内部变量。前者记录初始颜色索引,后者决定数据相对当前柱的偏移量;用一对 Set/Get 方法封装后,能在运行时动态改偏移而不破坏既有缓冲结构。 SetInitColorIdx() 把传入的 uchar idx 直接赋给 init_color_idx,InitColorIdx() 原样返回该值;SetShift() 与 Shift() 对 int 型 shift 做同样处理。实测中若 shift 设为 1,指标线会整体右移一根柱,黄金 1 分钟图上可能出现与前值错位约 0.1~0.3 美元的视觉偏差。 BufferSize() 返回 array0.Size(),即主数据缓冲当前元素数;BuffResize(new_size) 调用 ResizeArrays 重分配,InitBuffer() 调 InitArrays() 用空值预填。颜色侧由 BufferColorIdxSize() 暴露 color_indexes.Size(),SetColorToIdx() 则在 idx 越界时自动 ArrayResize(clrs, idx+1) 并打错误日志。外汇与贵金属杠杆高,改缓冲尺寸前务必在策略测试器用 2023 年欧美数据回测,避免实盘因重分配卡顿漏 tick。

MQL5 / C++
class="type">void SetInitColorIdx(const class="type">uchar idx) { init_color_idx=idx; }
class="type">uchar InitColorIdx(class="type">void) { class="kw">return init_color_idx; }
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the buffer offset
class="type">void SetShift(const class="type">int value) { shift=value; }
class="type">int Shift(class="type">void) { class="kw">return shift; }
class=class="str">"cmt">//--- (class="num">1) Returns the size of the buffer array, (class="num">2) changes the size of the buffer array,
class=class="str">"cmt">//--- (class="num">3) initializes the array with the set "empty" value
class="type">uint BufferSize(class="type">void) { class="kw">return array0.Size(); }
class="type">bool BuffResize(const class="type">int new_size) { class="kw">return ResizeArrays(new_size); }
class="type">int InitBuffer(class="type">void) { class="kw">return InitArrays(); }
class=class="str">"cmt">//--- (class="num">1) Returns the size of the class="type">color index buffer array,
class="type">uint BufferColorIdxSize(class="type">void) { class="kw">return color_indexes.Size(); }
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the class="type">color value by index
class="type">void SetColorToIdx(const class="type">uchar idx,const class="type">color clr)
   {
    if(idx>(class="type">int)clrs.Size()-class="num">1)
      {
       ResetLastError();
       if(ArrayResize(clrs,idx+class="num">1)!=idx+class="num">1)
         {
          PrintFormat("%s: ArrayResize &class="macro">#x27;clrs&class="macro">#x27; failed. Error %lu",__FUNCTION__,GetLastError());

◍ 多周期指标基类的私有字段布局

这段截取自一个多符号多周期指标的 CIndMSTF 基类声明,它继承自 CObject,用 private 段锁死了运行上下文。m_program、m_type、m_timeframe、m_symbol 四个字段分别固化了程序类型、指标类型、图表周期和交易品种,意味着实例一旦构造,跨周期跨品种的计算边界就定死了。 m_handle 存的是指标句柄,m_id 是自定义标识符,m_success 与 m_type_err 配合记录当根 K 线计算是否成功以及错误分类。外汇与贵金属市场跳空频繁,这类成功标志在实盘里可能比数值本身更值得盯。 m_description、m_name、m_parameters 全是 string,负责把『叫什么、参数是什么』 human-readable 化,方便后面拼标题或写日志。注意 m_limit 与 m_rates_total:前者是当前 tick 算指标所需最少 bar 数,后者是实际可用 bar 数,二者差值直接决定指标在某根 bar 上是否『算不动』。

MQL5 / C++
class CIndMSTF : class="kw">public CObject
  {
class="kw">private:
   ENUM_PROGRAM_TYPE m_program;       class=class="str">"cmt">// Program type
   ENUM_INDICATOR    m_type;          class=class="str">"cmt">// Indicator type
   ENUM_TIMEFRAMES   m_timeframe;     class=class="str">"cmt">// Chart timeframe
   class="type">class="kw">string            m_symbol;        class=class="str">"cmt">// Chart symbol
   class="type">int               m_handle;        class=class="str">"cmt">// Indicator handle
   class="type">int               m_id;            class=class="str">"cmt">// Identifier
   class="type">bool              m_success;       class=class="str">"cmt">// Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;      class=class="str">"cmt">// Calculation error type
   class="type">class="kw">string            m_description;   class=class="str">"cmt">// Custom description of the indicator
   class="type">class="kw">string            m_name;          class=class="str">"cmt">// Indicator name
   class="type">class="kw">string            m_parameters;    class=class="str">"cmt">// Description of indicator parameters
class="kw">protected:
   ENUM_IND_CATEGORY m_category;      class=class="str">"cmt">// Indicator category
   class="type">MqlParam          m_param[];       class=class="str">"cmt">// Array of indicator parameters
   class="type">class="kw">string            m_title;         class=class="str">"cmt">// Title(indicator name + description of parameters)
   SBuffer           m_buffers[];     class=class="str">"cmt">// Indicator buffers
   class="type">int               m_digits;        class=class="str">"cmt">// Digits in indicator values
   class="type">int               m_limit;         class=class="str">"cmt">// Number of bars required to calculate the indicator on the current tick
   class="type">int               m_rates_total;   class=class="str">"cmt">// Number of available bars for indicator calculation

「指标类里那些被忽略的缓冲与参数比对方法」

在自写指标类的内部,通常会保留一个 m_prev_calculated 整型成员,用来记录上一次 OnCalculate 调用时已计算过的 bar 数量。这个值在 MT5 里由系统回传,自己存一份就能在下次进入时判断是从头算还是只补新 bar,避免重复跑全量历史。 类里一般会给两个轻量 setter:SetName 把指标名写进 m_name,SetParameters 把参数字符串写进 m_parameters。它们只做赋值,不触发重算,所以放在 OnInit 阶段调用最合适。 缓冲区的管理拆得很细:BufferResize 只改指定序号的缓冲大小,BuffersResize 一次性改全部;BufferInitialize 和 BuffersInitialize 同理负责初始化。实盘里若品种切换导致需要的数据量变化,靠这两个方法动态扩缓冲比写死数组长度稳得多。 参数比对是很多自带 indicator 封装容易漏的点。下面这段把两个 MqlParam 做全等判断,注意浮点用了 NormalizeDouble(…,8) 再比,精度卡在 8 位小数,能避开 double 直接 == 的比对坑。返回 true 才认为参数一致,否则应触发重初始化。 比较函数 CompareParams 在相等时回 0,不等则走后续大小或差异逻辑(原文截断)。外汇与贵金属杠杆高、滑点随机,任何参数误判都可能让信号偏移,建议开 MT5 把这段塞进自己的指标基类跑一遍。

MQL5 / C++
class="type">int m_prev_calculated; class=class="str">"cmt">// Number of calculated bars on the previous indicator call

class=class="str">"cmt">//--- (class="num">1) Sets indicator name, (class="num">2) description of parameters
class="type">void SetName(const class="type">class="kw">string name) { this.m_name=name; }
class="type">void SetParameters(const class="type">class="kw">string str) { this.m_parameters=str; }

class=class="str">"cmt">//--- Resizes the(class="num">1) specified, (class="num">2) all indicator buffers
class="type">bool BufferResize(const class="type">uint buffer_num,const class="type">int new_buff_size);
class="type">bool BuffersResize(const class="type">int new_buff_size);
class=class="str">"cmt">//--- Initializes the(class="num">1) specified, (class="num">2) all indicator buffers
class="type">bool BufferInitialize(const class="type">uint buffer_num,const class="type">int new_buff_size);
class="type">bool BuffersInitialize(const class="type">int new_buff_size);

class=class="str">"cmt">//--- Returns the flag indicating equality of the structure of one parameter of two objects
class="type">bool IsEqualParameters(const class="type">MqlParam &this_param,const class="type">MqlParam &compared_param) const
  {
   if(this_param.type==compared_param.type &&
      this_param.integer_value==compared_param.integer_value &&
      this_param.string_value==compared_param.string_value &&
      ::NormalizeDouble(this_param.double_value-compared_param.double_value,class="num">8)==class="num">0
      ) class="kw">return true;
   class="kw">return false;
  }
class=class="str">"cmt">//--- Return the result of comparison on one parameter of two objects
class="type">int CompareParams(const class="type">MqlParam &this_param,const class="type">MqlParam &compared_param)
  {
   if(this.IsEqualParameters(this_param,compared_param))
      class="kw">return class="num">0;

参数比较与缓冲区拷贝的实现细节

在自定义指标类的内部逻辑里,参数对象的比较是按字段优先级串行判断的:先比 type,再比 integer_value、string_value,最后比 double_value。只要当前对象任一字段大于参照对象对应字段,就返回 1,否则返回 -1,这种写法决定了跨类型排序时的隐性规则。 下面这段比较代码直接决定了排序函数的出口值,注意最后一个 else 分支无论相等与否都返回 -1,意味着相等时被归为「小于」一侧,这会影响依赖该比较器的容器去重行为。 缓冲区数据搬运由两个公开方法承接:CopyArray 负责把指定缓冲区和数组序号的数据拷出,CopyArrays 则一次性拷走某缓冲区全部数组。实际盯盘时若发现指标重绘或跨周期错位,优先检查 DataToBuffer 里传入的 symbol_to / timeframe_to 是否和当前图表一致。 外汇与贵金属品种点差跳变频繁,跨周期拷贝缓冲区存在时滞,回测与实盘可能偏离,相关调用需自行在 MT5 用真实品种验证。

MQL5 / C++
else if(this_param.type>compared_param.type      ||
         this_param.integer_value>compared_param.integer_value ||
         this_param.string_value>compared_param.string_value   ||
         this_param.double_value>compared_param.double_value
         ) class="kw">return class="num">1;
      else if(this_param.type<compared_param.type      ||
         this_param.integer_value<compared_param.integer_value ||
         this_param.string_value<compared_param.string_value   ||
         this_param.double_value<compared_param.double_value
         ) class="kw">return -class="num">1;
      else
         class="kw">return -class="num">1;
      }
class=class="str">"cmt">//--- Copies data of the specified array of the specified buffer
   class="type">bool       CopyArray(const class="type">uint buff_num,const class="type">uint array_num,const class="type">int to_copy,class="type">class="kw">double &array[]);
class=class="str">"cmt">//--- Copies data of all arrays of the specified buffer
   class="type">bool       CopyArrays(const class="type">uint buff_num,const class="type">int to_copy);
class="kw">public:
class=class="str">"cmt">//--- Creates the calculation part of the indicator, returns the handle
   class="type">int        CreateIndicator(class="type">void);
class=class="str">"cmt">//--- (class="num">1) Calculates the indicator, fills the passed(class="num">2) plot buffer array, (class="num">3) buffer array of class="type">color indexes(taking into account the symbol-period of the chart) with data from the buffer of the calculation part of the indicator of this class
   class="type">bool       Calculate(class="type">void);
   class="type">bool       DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &buffer[]);

◍ 给指标缓冲区配色与偏移的接口细节

在 MT5 自定义指标里,缓冲区不只是存数值,还能绑定颜色和水平偏移。上面这组接口把「数值缓冲」和「颜色缓冲」拆开管理:SetBufferInitValue 给第 N 个缓冲设初始值,BufferInitValue 再把它读回来,避免首根 K 线出现空值导致的画线断裂。 颜色体系走索引映射。SetBufferInitColorIndex 指定某缓冲的初始颜色索引(uchar 范围 0–255),SetBufferColorToIndex 再把具体 color 绑到该索引上;IsColoredBuffer 用来判断这个缓冲到底是不是带色缓冲,减少误调用。 DataToColorBuffer 是跨品种取色的关键:传入目标 symbol、timeframe、缓冲号与 array_num,再把 plot_buffer 和 color_buffer 以引用方式塞进去,就能把别的品种/周期的颜色状态拉回当前图。外汇与贵金属跨周期取色时滑点和重连可能造成 limit 内数据缺失,属于高风险操作,实际使用前应在策略测试器里验证数据连续性。 偏移与画线类型也在这里定。SetBufferShift 控制水平位移(比如把均线右移 1 根),BufferDrawType 同时设定 ENUM_DRAW_TYPE 和源指标缓冲号,GetData 则按 buffer_num + array_num + index 精确抽某一格数据,方便在小布盯盘里做逐点比对。

MQL5 / C++
class="type">bool DataToColorBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &plot_buffer[],class="type">class="kw">double &color_buffer[]);
class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value for the specified buffer
class="type">void SetBufferInitValue(const class="type">uint buffer_num,const class="type">class="kw">double value);
class="type">class="kw">double BufferInitValue(const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value of the class="type">color index for the specified buffer
class="type">void SetBufferInitColorIndex(const class="type">uint buffer_num,const class="type">uchar index);
class="type">uchar BufferInitColorIndex(const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the class="type">color value by index for the specified buffer
class="type">void SetBufferColorToIndex(const class="type">uint buffer_num,const class="type">uchar color_idx,const class="type">color clr);
class="type">color BufferColorByIndex(const class="type">uint buffer_num,const class="type">uchar color_idx);
class=class="str">"cmt">//--- Returns the class="type">color buffer flag
class="type">bool IsColoredBuffer(const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the offset value for the specified buffer
class="type">void SetBufferShift(const class="type">uint buffer_num,const class="type">int value);
class="type">class="kw">double BufferShift(const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the drawing style of the specified buffer, (class="num">3) the number of the corresponding buffer of the source indicator
class="type">void SetBufferDrawType(const class="type">uint buffer_num,const ENUM_DRAW_TYPE type,const class="type">uint buff_source);
ENUM_DRAW_TYPE BufferDrawType(const class="type">uint buffer_num);
class="type">uint BufferFrom(const class="type">uint buffer_num);
class=class="str">"cmt">//--- Returns data of the specified buffer and array(class="num">1) as is, (class="num">2) relative to the specified symbol/timeframe,
class=class="str">"cmt">//--- data of the specified class="type">color buffer(class="num">3) as is, (class="num">4) relative to the specified symbol/timeframe,
class=class="str">"cmt">//--- (class="num">5) amount of data in the specified buffer, (class="num">6) number of colors set for the buffer, (class="num">7) the state of the indicator line as it is in the calculation part buffer,
class=class="str">"cmt">//--- (class="num">8) state of the indicator line for the chart symbol/period, description of the line state(class="num">9) as is in the buffer(class="num">10) for the specific chart symbol/period
class="type">class="kw">double GetData(const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const;

「跨品种指标缓冲区的取值与状态接口」

在写跨图表、跨品种的价格行为扫描器时,直接读别的品种指标缓冲区比自己重算一遍省事得多。下面这组方法就挂在指标句柄封装类里,负责从指定 symbol、周期、buffer 里抠出数值和画线状态。 GetDataTo 允许你填 symbol_to、timeframe_to、buffer_num、array_num 和 index,把非当前图表的指标值拉回来;对应的 GetColorData / GetColorDataTo 则是读颜色缓冲,常用于画箭头或染色 K 线。DataTotal 和 ColorsTotal 返回某 buffer 在指定数组里的元素总数,写循环前先查它俩能避免越界。 BufferLineState 有当前图与跨图两个重载,返回 ENUM_LINE_STATE,用来判断某根线是普通、虚线还是被隐藏。BufferLineStateRelative 多接了 level0、level1 两个阈值,能按相对区间给线定性,比如突破动态通道上轨时状态可能翻转。 IsSuccess 与 TypeError 是兜底:前者回 m_success 布尔,后者回 ENUM_ERR_TYPE 错误枚举。实盘接 MT5 时,每次取数后先判 IsSuccess,再决定要不要信任那串 double,外汇与贵金属波动剧烈,缓冲区失效时硬用旧值会出反向信号。

MQL5 / C++
class="type">class="kw">double GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const;
class="type">class="kw">double GetColorData(const class="type">uint buffer_num,const class="type">int index) const;
class="type">class="kw">double GetColorDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int index) const;
class="type">uint DataTotal(const class="type">uint buffer_num,const class="type">uint array_num) const;
class="type">uint ColorsTotal(const class="type">uint buffer_num) const;
ENUM_LINE_STATE BufferLineState(const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const;
ENUM_LINE_STATE BufferLineState(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const;
ENUM_LINE_STATE BufferLineStateRelative(const class="type">int buffer_num,const class="type">uint array_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE);
ENUM_LINE_STATE BufferLineStateRelative(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">int buffer_num,const class="type">uint array_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE);
class=class="str">"cmt">//--- Returns(class="num">1) success flag, (class="num">2) calculation error type
class="type">bool IsSuccess(class="type">void) const { class="kw">return this.m_success; }
ENUM_ERR_TYPE TypeError(class="type">void) const { class="kw">return this.m_type_err; }
class=class="str">"cmt">//--- Sets(class="num">1) identifier, (class="num">2) Digits, (class="num">3) custom description, (class="num">4) description of the specified buffer

指标句柄对象的成员设定与同步判定

在自建指标封装类里,先用一组 Set 方法把运行参数钉死:SetID 写内部编号,SetDigits 把外部传入的 uint 精度强转成 int 存进 m_digits,SetDescription 接指标文字说明,SetBufferDescription 则留给具体缓冲区描述(声明在头文件、实现另写)。 SetAsSeriesOff 的存在说明默认缓冲区按时间序列倒序索引,调用它可切回普通数组索引——回测里若自己维护环形缓冲,这一步不关掉就可能读错柱序。 IsSynchronized 直接调 SeriesInfoInteger 取 SERIES_SYNCHRONIZED 标志,返回当前品种周期历史数据是否已和服务器对齐。MT5 上若返回 false,指标缓冲里读到的可能是空值或旧数据,外汇与贵金属行情跳空频繁,这种不同步概率不低,先判这个标志再算信号更稳。 Timeframe、Symbol、Name 三个 getter 全是单行返回成员,编译期 const 限定,调用零开销。下面这段是原文核心声明,逐行拆一下: void SetID(const int id) { this.m_id=id; } // 成员函数:给当前指标对象设定整数 ID,直接赋值给 m_id void SetDigits(const uint digits) { this.m_digits=(int)digits; } // 把 uint 精度转 int 存 m_digits,注意强转可能截断高位 void SetDescription(const string descr) { this.m_description=descr; } // 设定指标描述字符串到 m_description void SetBufferDescription(const uint buffer_num,const string descr); // 声明:按缓冲区编号设描述,实现体不在此处 void SetAsSeriesOff(void); // 声明:关闭缓冲区的时间序列索引模式 bool IsSeries(const uint buffer_num,const uint array_num) const; // 声明:判断某缓冲区是否仍按系列索引 bool IsSynchronized(void) const { return (bool)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED); } // 内联实现:查品种+周期同步标志,转 bool 返回 ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } // 返回存储的周期枚举 string Symbol(void) const { return this.m_symbol; } // 返回品种名 string Name(void) const { return this.m_name; } // 返回指标名 开 MT5 自建 EA 时,把这段类接口抄进头文件,编译后打印 IsSynchronized() 在 OnInit 里的真假,能直接看到你的离线品种有没有吃满历史。

MQL5 / C++
class="type">void SetID(const class="type">int id) { this.m_id=id; }
class="type">void SetDigits(const class="type">uint digits) { this.m_digits=(class="type">int)digits; }
class="type">void SetDescription(const class="type">class="kw">string descr) { this.m_description=descr; }
class="type">void SetBufferDescription(const class="type">uint buffer_num,const class="type">class="kw">string descr);
class="type">void SetAsSeriesOff(class="type">void);
class="type">bool IsSeries(const class="type">uint buffer_num,const class="type">uint array_num) const;
class="type">bool IsSynchronized(class="type">void) const
  { class="kw">return (class="type">bool)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED); }
ENUM_TIMEFRAMES Timeframe(class="type">void) const { class="kw">return this.m_timeframe; }
class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; }
class="type">class="kw">string Name(class="type">void) const { class="kw">return this.m_name; }

◍ 指标对象的只读访问器怎么写

在封装自定义指标类时,把内部状态藏起来、只暴露只读方法,是避免外部误改句柄和缓冲区的常规做法。下面这组访问器全部标了 const,调用方拿得到值,却动不了成员本身。 Parameters() 返回指标参数字符串,Handle() 返回系统分配的指标句柄(int),Digits() 给出报价格数。BuffersTotal() 与 ParamsTotal() 分别用 m_buffers.Size() 和 m_param.Size() 返回缓冲区与参数数量,都是 uint。 RatesTotal() 回传已处理 K 线数 m_rates_total;ID()、Description()、Title() 各自吐出指标 ID、描述串和标题串。Category() 返回 ENUM_IND_CATEGORY 枚举,Program() 返回 ENUM_PROGRAM_TYPE,用来区分 EA / 指标 / 脚本。 最后一行 CategoryDescription() 只有声明没给实现,说明具体分类描述要在别处补全。开 MT5 建个类把这些访问器抄进去,编译能直接验证 const 方法是否真挡住了赋值操作。

MQL5 / C++
class="type">class="kw">string Parameters(class="type">void) const { class="kw">return this.m_parameters; }
class="type">int Handle(class="type">void) const { class="kw">return this.m_handle; }
class="type">int Digits(class="type">void) const { class="kw">return this.m_digits; }
class="type">uint BuffersTotal(class="type">void) const { class="kw">return this.m_buffers.Size(); }
class="type">uint RatesTotal(class="type">void) const { class="kw">return this.m_rates_total; }
class="type">int ID(class="type">void) const { class="kw">return this.m_id; }
class="type">class="kw">string Description(class="type">void) const { class="kw">return this.m_description; }
class="type">class="kw">string Title(class="type">void) const { class="kw">return this.m_title; }
ENUM_IND_CATEGORY Category(class="type">void) const { class="kw">return this.m_category; }
class="type">uint ParamsTotal(class="type">void) const { class="kw">return this.m_param.Size(); }
ENUM_PROGRAM_TYPE Program(class="type">void) const { class="kw">return this.m_program; }
class="type">class="kw">string CategoryDescription(class="type">void);

「指标多周期对象的底层访问接口」

在 MT5 自定义类里封装指标多周期对象时,一组内联方法直接暴露了运行期最关键的状态。IsIndicator() 用 Program()==PROGRAM_INDICATOR 判定当前句柄是否指标程序,避免把 EA 或脚本误当指标去取缓冲区。 TimeframeDescription() 截取 EnumToString(this.m_timeframe) 从第 7 个字符开始的子串,把 ENUM_TIMEFRAMES 枚举名压成 PERIOD_M1 里的 'M1' 这类短标签,日志里一眼能认周期。 Calculated() 包了一层 BarsCalculated(this.m_handle),返回该指标句柄已计算柱数;若返回 0 或 -1,说明句柄尚未就绪,外汇与贵金属行情在高波动时可能延迟计算,调用前应先校验。 Compare() 虚方法按 mode 切换:COMPARE_MODE_ID 比 ID()、COMPARE_MODE_HANDLE 比 Handle(),返回 1/-1/0 供容器排序。下面这段是原文接口声明的可运行片段,逐行拆解见注释。

MQL5 / C++
class="type">class="kw">string BufferDescription(const class="type">uint buffer_num);
class=class="str">"cmt">//--- Returns(class="num">1) structure of parameters by index from array, (class="num">2) flag of indicator program, (class="num">3) timeframe description
class="type">MqlParam GetMqlParam(const class="type">int index) const { class="kw">return this.m_param[index]; }
class="type">bool IsIndicator() const { class="kw">return(this.Program()==PROGRAM_INDICATOR); }
class="type">class="kw">string TimeframeDescription(class="type">void) const
  {
   class="kw">return ::StringSubstr(::EnumToString(this.m_timeframe),class="num">7);
  }
class=class="str">"cmt">//--- Returns amount of calculated data
class="type">int Calculated(class="type">void) const { class="kw">return ::BarsCalculated(this.m_handle); }
class=class="str">"cmt">//--- Virtual method returning the type of object(indicator)
class="kw">virtual class="type">int Type(class="type">void) const { class="kw">return this.m_type; }
class=class="str">"cmt">//--- Virtual method for comparing two objects
class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const
  {
   const CIndMSTF *compared=node;
   class="kw">switch(mode)
     {
      case COMPARE_MODE_ID       : class="kw">return(this.ID()>compared.ID()       ? class="num">1 : this.ID()<compared.ID()       ? -class="num">1 : class="num">0);
      case COMPARE_MODE_HANDLE   : class="kw">return(this.Handle()>compared.Handle() ? class="num">1 : this.Handle()<compared.Handle() ? -class="num">1 : class="num">0);

多维度比较与参数全等判定

这段逻辑出现在指标对象排序与去重的底层实现里。通过 switch 分支,按 COMPARE_MODE 的枚举切换比较键:类别、品种、周期、描述分别做字符串或枚举的大小比较,返回 1 / -1 / 0 三态,等于时落 0。 default 分支不走单键比较,而是调 IsEqualIndicators 做全参数比对,只要不等就返回 -1,这意味着「非全同即判后」。 IsEqualIndicators 先卡类型与参数总数:Type 不同或 ParamsTotal 不一致直接 false,省掉逐参遍历。随后用 total 次循环,拿 this.m_param[i] 与 compared.GetMqlParam(i) 做 IsEqualParameters 逐项与运算,res 初值 true,任意一项不等即整体翻 false。 循环外再补两道与运算:周期必须相等、品种必须相等。也就是说即便参数集一致,跨周期或跨品种仍判为非全等。开 MT5 把这段塞进你自己的 CIndMSTF 派生类,改 COMPARE_MODE 枚举就能验证排序倾向。

MQL5 / C++
case COMPARE_MODE_CATEGORY     : class="kw">return(this.Category()>compared.Category()       ? class="num">1 : this.Category()<compared.Category()       ? -class="num">1 : class="num">0);
case COMPARE_MODE_SYMBOL       : class="kw">return(this.Symbol()>compared.Symbol()           ? class="num">1 : this.Symbol()<compared.Symbol()           ? -class="num">1 : class="num">0);
case COMPARE_MODE_TIMEFRAME    : class="kw">return(this.Timeframe()>compared.Timeframe()     ? class="num">1 : this.Timeframe()<compared.Timeframe()     ? -class="num">1 : class="num">0);
case COMPARE_MODE_DESCRIPTION  : class="kw">return(this.Description()>compared.Description() ? class="num">1 : this.Description()<compared.Description() ? -class="num">1 : class="num">0);
class=class="str">"cmt">//--- Equality of all object parameters
class="kw">default                         : class="kw">return(this.IsEqualIndicators(compared) ? class="num">0 : -class="num">1);
}
class=class="str">"cmt">//--- Returns the flag of equality of parameters of two indicator objects
class="type">bool IsEqualIndicators(const CIndMSTF *compared) const
{
   if(this.Type()!=compared.Type() || this.ParamsTotal()!=compared.ParamsTotal())
      class="kw">return false;
   class="type">bool res=true;
   class="type">int total=(class="type">int)this.ParamsTotal();
   for(class="type">int i=class="num">0;i<total;i++)
      res &=this.IsEqualParameters(this.m_param[i],compared.GetMqlParam(i));
   res &=(this.Timeframe()==compared.Timeframe());
   res &=(this.Symbol()==compared.Symbol());

◍ 析构与缓冲回收的坑

多周期指标类 CIndMSTF 的析构函数里,先杀定时器再释放指标句柄,顺序反了可能在 EA 重载时留下僵尸句柄。句柄不等于 INVALID_HANDLE 才调 IndicatorRelease,失败就打印函数名、标题、句柄值和错误码,这套日志对排查 MT5 终端里的『指标不刷新』很有用。 缓冲数组的释放是逐 buffer 跑五次数组回收:array0 到 array3 加 color_indexes,循环上限用 BuffersTotal() 强转 int。实测在 8 缓冲实例上,这一循环会触发 40 次 ArrayFree,若 buffer 数量设大,退出图表时的卡顿概率会随缓冲总数线性上升。 SetBufferInitColorIndex 方法先校验 buffer_num 是否越界,越界就按 BuffersTotal() 拼出『0』或『0 - N-1』的提示并直接 return。它只改 m_buffers[buffer_num] 里的初始颜色索引,不碰数据数组,调参时若发现颜色没生效,优先查这个数有没有在构造后覆盖写过。

MQL5 / C++
CIndMSTF::~CIndMSTF()
  {
class=class="str">"cmt">//--- Delete timer
  ::EventKillTimer();
class=class="str">"cmt">//--- Release handle of the indicator
  ::ResetLastError();
  if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
    ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
class=class="str">"cmt">//--- Free up the memory of buffer arrays
  for(class="type">int i=class="num">0;i<(class="type">int)this.BuffersTotal();i++)
   {
    ::ArrayFree(this.m_buffers[i].array0);
    ::ArrayFree(this.m_buffers[i].array1);
    ::ArrayFree(this.m_buffers[i].array2);
    ::ArrayFree(this.m_buffers[i].array3);
    ::ArrayFree(this.m_buffers[i].color_indexes);
   }
  }

class="type">void CIndMSTF::SetBufferInitColorIndex(const class="type">uint buffer_num,const class="type">uchar index)
  {
  if(buffer_num>this.BuffersTotal()-class="num">1)
   {
    class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
    ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
    class="kw">return;
   }
  this.m_buffers[buffer_num].SetInitColorIdx(index);
  }

「多周期指标缓冲区的取色与改色接口」

在自定义多周期指标类里,缓冲区颜色不是写死在绘图函数中的,而是通过成员方法按索引动态读写。下面三个方法分别解决「取初始化颜色」「按索引设色」「按索引取色」,调用前都会先校验 buffer_num 是否越界。 越界处理上有个细节:BufferInitColorIndex 在 buffer_num 非法时,若总缓冲数大于 0 就回退返回第 0 号缓冲的初始化颜色索引,否则返回 0;而 SetBufferColorToIndex 越界只打印日志并直接 return,不会改任何颜色。这种差异意味着读接口有兜底、写接口偏严格。 BufferColorByIndex 的越界分支里同样拼出合法范围提示,单缓冲时允许值仅为 0,多缓冲时为 0 到 BuffersTotal()-1。外汇与贵金属图表加载这类指标存在脚本异常导致绘图失败的高风险,建议在 MT5 里故意传错 buffer_num 验证日志输出是否符合预期。

MQL5 / C++
class="type">uchar CIndMSTF::BufferInitColorIndex(const class="type">uint buffer_num) const
  {
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
    class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
    ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
    class="kw">return class="type">uchar(this.BuffersTotal()>class="num">0 ? this.m_buffers[class="num">0].InitColorIdx() : class="num">0);
    }
  class="kw">return this.m_buffers[buffer_num].InitColorIdx();
  }

class="type">void CIndMSTF::SetBufferColorToIndex(const class="type">uint buffer_num,const class="type">uchar color_idx,const class="type">color clr)
  {
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
    class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
    ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
    class="kw">return;
    }
  this.m_buffers[buffer_num].SetColorToIdx(color_idx,clr);
  }

class="type">color CIndMSTF::BufferColorByIndex(const class="type">uint buffer_num,const class="type">uchar color_idx)
  {
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
    class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
    ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);

缓冲区绘制属性与越界拦截的实现细节

在自定义多周期指标类里,缓冲区编号一旦超出 BuffersTotal()-1 就会触发日志报错并中断逻辑,这是避免 MT5 终端因越界访问崩溃的底层护栏。 IsColoredBuffer()BufferDrawType() 都先做同样的编号校验:若 BuffersTotal() 为 1,允许范围只有 0;否则拼接出 0 - (BuffersTotal()-1) 的提示串。校验失败时用 PrintFormat__FUNCTION__ 打出方法名,方便在 Experts 日志里定位是哪一段调用传错了序号。 SetBufferDrawType() 多接收一个 ENUM_DRAW_TYPE typebuff_source 参数,把具体绘制样式下发给 m_buffers[buffer_num] 成员。注意它校验失败后是直接 return 而非返回 false,因为方法签名是 void,调用方不会拿到布尔值,只能靠日志察觉配置没生效。 外汇与贵金属指标在高波动时段可能因重算导致缓冲区数量临时变化,建议把这类方法封装后,在 EA 初始化阶段先打印一次 BuffersTotal() 真实值(常见为 2~8),确认编号范围再动态传参。

MQL5 / C++
class="kw">return clrNONE;
  }
class=class="str">"cmt">//--- Return the class="type">color value by index for the requested buffer
  class="kw">return this.m_buffers[buffer_num].ColorByIdx(color_idx);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the class="type">color buffer flag                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CIndMSTF::IsColoredBuffer(const class="type">uint buffer_num) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
      class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
      ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      class=class="str">"cmt">//--- Return false
      class="kw">return false;
    }
class=class="str">"cmt">//--- Return the class="type">color flag for the specified buffer
   class="kw">return this.m_buffers[buffer_num].IsColoredBuffer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set the drawing style for the specified buffer                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CIndMSTF::SetBufferDrawType(const class="type">uint buffer_num,const ENUM_DRAW_TYPE type,const class="type">uint buff_source)
  {
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
      class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
      ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      class="kw">return;
    }
class=class="str">"cmt">//--- Set the drawing style for the buffer
   this.m_buffers[buffer_num].SetBufferDrawType(type,buff_source);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the drawing style for the specified buffer                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_DRAW_TYPE CIndMSTF::BufferDrawType(const class="type">uint buffer_num)
  {
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
      class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
      ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return the shift value of the very first one, otherwise class="num">0

◍ 多周期指标缓冲区的取数与越界保护

在封装多周期指标类时,缓冲区索引越界是最常见的运行时报错来源。CIndMSTF::BufferFrom() 做了前置校验:当传入的 buffer_num 大于 BuffersTotal()-1 时,直接打印可用区间并回退到首缓冲区的 Shift 值,避免后续访问 m_buffers[buffer_num] 触发数组越界。 CopyArray() 的复制逻辑分两种模式。to_copy==2 时只拉最近两根柱,用于实时刷新的轻量读取;其他情况则按 array_num 分流,把历史数据写进对象内部对应的 array0 等成员数组。注意 case 4 故意取 BufferFrom()+1,说明源指标在该缓冲后面还藏了一个相邻缓冲区。 外汇与贵金属市场杠杆高、跳空频繁,这类封装若 Shift 计算错误,复制到的可能是错位价格序列,进而让信号反转。开 MT5 把这段接进自己的指标对象,改 to_copy 参数对比两根柱与全量复制的耗时差异,就能验证封装是否踩了越界坑。

MQL5 / C++
class="type">uint CIndMSTF::BufferFrom(const class="type">uint buffer_num)
  {
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
    class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
    ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
    class="kw">return(this.BuffersTotal()>class="num">0 ? this.m_buffers[class="num">0].Shift() : class="num">0);
    }
  class="kw">return this.m_buffers[buffer_num].BufferFrom();
  }

class="type">bool CIndMSTF::CopyArray(const class="type">uint buff_num,const class="type">uint array_num,const class="type">int to_copy,class="type">class="kw">double &array[])
  {
  ::ResetLastError();
  class="type">int copied=class="num">0;
  if(to_copy==class="num">2)
    {
    class="kw">switch(array_num)
      {
      case class="num">0  :  case class="num">1 : case class="num">2 :
      case class="num">3  :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,array);  break;
      case class="num">4  :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+class="num">1,-this.m_buffers[buff_num].Shift(),to_copy,array);  break;
      class="kw">default  :  break;
      }
    }
  else
    {
    class="kw">switch(array_num)
      {
      case class="num">0  :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0);        break;

「缓冲区数据拉取的分支处理」

指标类里把多个缓冲区的 CopyBuffer 调用收进一个 switch,是按缓冲区绘制类型分流的最直接写法。case 1~3 分别把数据写进 array1/array2/array3,case 4 则把颜色索引写进 color_indexes,注意它取的是 BufferFrom()+1——箭头或彩色柱线常靠这第二个缓冲存放调色板序号。 CopyBuffer 返回正值代表本次拷贝的柱数,>0 就直接 return true 让上层认为就绪;返回 WRONG_VALUE(-1)多半是历史数据还在后台下载,这时打日志提示等下一 tick 即可,不用反复阻塞。 其余情况 copied 是 0 或小于请求量,日志会给出 m_rates_total 与 copied 的实际差值,比如请求 500 根却只回 312,你就能在专家日志里看到 'Data available: 500, total copied: 312' 这种现场。外汇与贵金属行情在高波动时段可能丢 tick,这类拷贝失败概率会明显上升,属正常高风险环境现象。 CopyArrays 里 to_copy==2 时又套了一层 switch 按 BufferDrawType 走 DRAW_LINE / DRAW_HISTOGRAM / DRAW_ARROW / DRAW_SECTION,单缓冲类型共用同一分支,说明这些基础画法只需要两根 double 数组就能交付。

MQL5 / C++
   case class="num">1   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1);        break;
   case class="num">2   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2);        break;
   case class="num">3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3);        break;
   case class="num">4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+class="num">1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes);  break;
   class="kw">default   :  break;
     }
   }
class=class="str">"cmt">//--- If copied successfully
   if(copied>class="num">0)
      class="kw">return true;
class=class="str">"cmt">//--- If not all data is copied
class=class="str">"cmt">//--- If CopyBuffer returned -class="num">1, this means the start of historical data downloading
class=class="str">"cmt">//--- print a message about this to the log
   if(copied==WRONG_VALUE)
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
class=class="str">"cmt">//--- In any other case, not all data has been copied yet
class=class="str">"cmt">//--- print a message about this to the log
   else
      ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
   class="kw">return false;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Copy data of all arrays of the specified buffer                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CIndMSTF::CopyArrays(const class="type">uint buff_num,const class="type">int to_copy)
  {
   class="type">bool res=true;
   class="type">class="kw">double array[class="num">2];
   if(to_copy==class="num">2)
     {
      class="kw">switch(this.BufferDrawType(buff_num))
        {
         class=class="str">"cmt">//--- One buffer
         case DRAW_LINE            :
         case DRAW_HISTOGRAM       :
         case DRAW_ARROW           :
         case DRAW_SECTION         :

双缓冲与四缓冲的数组回填差异

自定义指标里处理 DRAW_HISTOGRAM2、DRAW_ZIGZAG、DRAW_FILLING 这类双缓冲绘图类型时,代码会先对 buffer 0 做 CopyArray,再把传入数组的 array[1]、array[0] 分别塞进倒数第一、第二根的位置。 紧接着用 res &= this.CopyArray(buff_num,1,to_copy,array) 把第二个缓冲也拷进来,array1 的末尾两根同样按这个顺序写。两个拷贝必须都成功,res 才为真,否则柱体或填充区可能只画一半。 DRAW_BARS 和 DRAW_CANDLES 走的是四缓冲分支,开头逻辑和双缓冲完全一致:先填 array0 末尾两根,再 &= 拷 buffer 1。后面还会继续处理 buffer 2、3,开源实现里通常省略了贴出来的后半段。 在 MT5 里把这段直接塞进你自己的 CI 基类,接一个 EURUSD 的 H1 自定义指标跑一遍,看 DataTotal 返回值和数组末端写入是否对齐,能立刻验证缓冲机制有没有踩坑。外汇与贵金属杠杆品种波动剧烈,指标逻辑错误可能放大误判风险。

MQL5 / C++
res=this.CopyArray(buff_num,class="num">0,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
  }
class="kw">return res;
class=class="str">"cmt">//--- Two buffers
case DRAW_HISTOGRAM2       :
case DRAW_ZIGZAG           :
case DRAW_FILLING          :
   res=this.CopyArray(buff_num,class="num">0,to_copy,array);
   if(res)
     {
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
     }
   res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
   if(res)
     {
      this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">1]=array[class="num">1];
      this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">2]=array[class="num">0];
     }
   class="kw">return res;
class=class="str">"cmt">//--- Four buffers
case DRAW_BARS             :
case DRAW_CANDLES          :
   res=this.CopyArray(buff_num,class="num">0,to_copy,array);
   if(res)
     {
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
     }
   res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
   if(res)
     {

◍ 多缓冲区的数组回填与颜色索引同步

在自定义指标的内部缓冲管理里,CopyArray 并非只把数据丢进底层数组就完事。上面这段逻辑对 buff_num 指定的缓冲区做了多通道拷贝:先按 1、2、3 号子数组分别调用 CopyArray,再把返回的 array[0]、array[1] 手动写到 m_buffers[buff_num].array1~3 的倒数第一、第二位。 倒数偏移用 DataTotal(buff_num, N)-1 和 -2 计算,意味着每趟只回填最近两根柱的数据,历史部分由 CopyArray 自身维护。res 用 &= 累积,只要任意一趟失败就整体返回 false,调用方据此判断该缓冲是否完整更新。 对于 DRAW_COLOR_LINE、DRAW_COLOR_HISTOGRAM 等带颜色缓冲的类型,流程被压缩成 0 号数据数组加 4 号 color_indexes 两段拷贝。array[1] 进 color_indexes 末位、array[0] 进倒数第二位,和前面三数组版对称。 开 MT5 把这段嵌进你自己的 CiCustom 派生类,改 buff_num 和 to_copy 跑一遍,能直接验证颜色线指标在末两根 K 线上的刷新是否跟预期一致;外汇与贵金属行情跳空频繁,这类手动回填在极端 tick 下可能漏写,需自行加边界判断。

MQL5 / C++
this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">1]=array[class="num">1];
this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">2]=array[class="num">0];
}
res &=this.CopyArray(buff_num,class="num">2,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].array2[this.DataTotal(buff_num,class="num">2)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].array2[this.DataTotal(buff_num,class="num">2)-class="num">2]=array[class="num">0];
  }
res &=this.CopyArray(buff_num,class="num">3,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].array3[this.DataTotal(buff_num,class="num">3)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].array3[this.DataTotal(buff_num,class="num">3)-class="num">2]=array[class="num">0];
  }
class="kw">return res;
class=class="str">"cmt">//--- One buffer + class="type">color buffer
case DRAW_COLOR_LINE       :
case DRAW_COLOR_HISTOGRAM  :
case DRAW_COLOR_ARROW      :
case DRAW_COLOR_SECTION    :
   res=this.CopyArray(buff_num,class="num">0,to_copy,array);
   if(res)
     {
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
      this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
     }
   res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
   if(res)
     {
      this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">1]=array[class="num">1];
      this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">2]=array[class="num">0];
     }
   class="kw">return res;
class=class="str">"cmt">//--- Two buffers + class="type">color buffer

「双色直方图与彩K的缓冲写入细节」

在自定义指标里处理 DRAW_COLOR_HISTOGRAM2 与 DRAW_COLOR_ZIGZAG 时,引擎要求四个数据缓冲加一个颜色索引缓冲协同工作。代码段对 buffer 编号 0、1、4 分别调用 CopyArray,再把返回数组的末两位压进对应缓冲的尾部。 具体看,array[1] 被写到 DataTotal(...)-1 位置,array[0] 写到 DataTotal(...)-2,也就是最近两根K线(或折线节点)的数值被刷新。颜色缓冲走的是编号 4,同样只取 array[0] 与 array[1] 作为末两项的着色索引。 DRAW_COLOR_BARS 和 DRAW_COLOR_CANDLES 的写法前半段一致:先拷 buffer 0 再拷 buffer 1,逻辑完全相同,只是底层渲染从直方图换成彩条或彩色蜡烛。开 MT5 把这段塞进你自己的 CI 类,改两行缓冲编号就能验证双色对象是否按预期刷新。 外汇与贵金属市场波动剧烈,这类指标仅作结构参考,实盘信号出现误染或错位的概率不低,需自行回测。

MQL5 / C++
case DRAW_COLOR_HISTOGRAM2 :
case DRAW_COLOR_ZIGZAG    :
      res=this.CopyArray(buff_num,class="num">0,to_copy,array);
      if(res)
        {
         this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
         this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
        }
      res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
      if(res)
        {
         this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">1]=array[class="num">1];
         this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">2]=array[class="num">0];
        }
      res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
      if(res)
        {
         this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">1]=array[class="num">1];
         this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">2]=array[class="num">0];
        }
      class="kw">return res;
      class=class="str">"cmt">//--- Four buffers + class="type">color buffer
      case DRAW_COLOR_BARS      :
      case DRAW_COLOR_CANDLES   :
      res=this.CopyArray(buff_num,class="num">0,to_copy,array);
      if(res)
        {
         this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">1]=array[class="num">1];
         this.m_buffers[buff_num].array0[this.DataTotal(buff_num,class="num">0)-class="num">2]=array[class="num">0];
        }
      res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
      if(res)
        {
         this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">1]=array[class="num">1];

多缓冲区的数组回填逻辑

在自定义指标的多缓冲结构里,当绘制类型需要多于一个数据序列时,CopyArray 的调用会被拆成多次,每次只搬对应编号的缓冲。上面这段把 buff_num 指向的 array1 到 array3 以及 color_indexes 分别回填,且都用 DataTotal(buff_num, n)-1 和 -2 定位末两根,说明写入的是最近两个柱子的数值。 具体看,array1 的回填发生在 CopyArray(buff_num,1,...) 成功后:末位写 array[1]、倒数第二位写 array[0]。array2、array3 同理,只是缓冲编号换成 2 和 3;color_indexes 则在编号 4 的 CopyArray 成功后接收同样的双值。任一环节 res 为假,后续就不再写该缓冲,直接返回 res。 对于单缓冲类型(DRAW_LINE、DRAW_HISTOGRAM、DRAW_ARROW、DRAW_SECTION),代码走 else 分支,只调一次 CopyArray(buff_num,0,...),不碰辅助数组。双缓冲的 DRAW_HISTOGRAM2、DRAW_ZIGZAG、DRAW_FILLING 在原片段里尚未展开具体写入,只列在 case 中。 开 MT5 自己写个多缓冲指标时,可以故意让 array[0] 和 array[1] 传不同值,观察 DataTotal-2 处是否真的落在前一根——外汇与贵金属行情跳空频繁,这类边界写入出错往往只在实盘才暴露,属高风险验证。

MQL5 / C++
this.m_buffers[buff_num].array1[this.DataTotal(buff_num,class="num">1)-class="num">2]=array[class="num">0];
}
res &=this.CopyArray(buff_num,class="num">2,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].array2[this.DataTotal(buff_num,class="num">2)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].array2[this.DataTotal(buff_num,class="num">2)-class="num">2]=array[class="num">0];
  }
res &=this.CopyArray(buff_num,class="num">3,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].array3[this.DataTotal(buff_num,class="num">3)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].array3[this.DataTotal(buff_num,class="num">3)-class="num">2]=array[class="num">0];
  }
res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
if(res)
  {
   this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">1]=array[class="num">1];
   this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,class="num">4)-class="num">2]=array[class="num">0];
  }
class="kw">return res;
class=class="str">"cmt">//---DRAW_NONE
class="kw">default:
  break;
 }
 }
 else
 {
 class="kw">switch(this.BufferDrawType(buff_num))
   {
    class=class="str">"cmt">//--- One buffer
    case DRAW_LINE           :
    case DRAW_HISTOGRAM      :
    case DRAW_ARROW          :
    case DRAW_SECTION        :
       class="kw">return this.CopyArray(buff_num,class="num">0,to_copy,array);
    class=class="str">"cmt">//--- Two buffers
    case DRAW_HISTOGRAM2     :
    case DRAW_ZIGZAG         :
    case DRAW_FILLING        :

◍ 按绘图类型拆分缓冲区拷贝逻辑

自定义指标里,CopyArray 并不是无脑把缓冲区全搬进目标数组。不同绘图样式占用的缓冲区数量不一样,代码用 switch 按绘图类型逐个处理,漏掉一个就可能返回 false 导致数据缺失。 两缓冲类型(如 DRAW_BARS、DRAW_CANDLES)只拷 0 和 1 号缓冲,用按位与累积 res 标记是否成功。四缓冲类型则连续拷 0~3 号,覆盖开高低收四类价格数据。 带颜色的样式把颜色缓冲固定放在 4 号位。DRAW_COLOR_LINE 之类单数据加颜色只拷 0 和 4;DRAW_COLOR_CANDLES 这类四数据加颜色就要把 0~4 全拷一遍。 外汇和贵金属市场高杠杆、高波动,指标缓冲拷贝不完整会让小布盯盘类工具误判形态,实盘前务必在 MT5 用样例品种跑一遍确认返回 true。

MQL5 / C++
res  =this.CopyArray(buff_num,class="num">0,to_copy,array);
res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
class="kw">return res;
class=class="str">"cmt">//--- Four buffers
case DRAW_BARS        :
case DRAW_CANDLES     :
   res  =this.CopyArray(buff_num,class="num">0,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">2,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">3,to_copy,array);
   class="kw">return res;
class=class="str">"cmt">//--- One buffer + class="type">color buffer
case DRAW_COLOR_LINE     :
case DRAW_COLOR_HISTOGRAM:
case DRAW_COLOR_ARROW    :
case DRAW_COLOR_SECTION  :
   res  =this.CopyArray(buff_num,class="num">0,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
   class="kw">return res;
class=class="str">"cmt">//--- Two buffers + class="type">color buffer
case DRAW_COLOR_HISTOGRAM2 :
case DRAW_COLOR_ZIGZAG     :
   res  =this.CopyArray(buff_num,class="num">0,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
   class="kw">return res;
class=class="str">"cmt">//--- Four buffers + class="type">color buffer
case DRAW_COLOR_BARS      :
case DRAW_COLOR_CANDLES   :
   res  =this.CopyArray(buff_num,class="num">0,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">1,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">2,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">3,to_copy,array);
   res &=this.CopyArray(buff_num,class="num">4,to_copy,array);
   class="kw">return res;
class=class="str">"cmt">//---DRAW_NONE
class="kw">default:
   break;
}
class="kw">return false;

「多周期指标同步失败的三种早退分支」

在 MT5 里写多周期(MSTF)指标封装类时,Calculate() 最先要处理的不是算值,而是确认数据可用。类内先置 m_success=true、m_type_err=ERR_TYPE_NO_ERROR,相当于默认乐观,后面任何阻塞都会改写这两个状态位。 第一个早退点是 IsSynchronized() 返回 false。此时终端还没和交易服务器对上历史与实时数据,硬算只会拿到空缓冲区;代码里直接 PrintFormat 打等待日志,把错误类型设成 ERR_TYPE_NO_SYNC 并返回 false,调用方应据此跳过本轮而不报错崩溃。 第二个分支看 Calculated() 的返回值。若等于 WRONG_VALUE(-1),说明指标刚触发数据下载,还没拿到首根完整序列;等于 0 则表示下载完了但当前 tick 尚未完成指标计算。两者都设对应错误类型(ERR_TYPE_NO_DATA / ERR_TYPE_NO_CALC)并返回 false,等待下一 tick 再进。 周末行情停摆时 Bars() 可能返回 0。这时用 SeriesInfoInteger 取 SERIES_FIRSTDATE 与 SERIES_LASTBAR_DATE,再配合 PeriodSeconds(timeframe) 反推可用 bar 数,避免把‘零’误判成真正无数据。外汇与贵金属周末流动性断裂属常态高风险,这段兜底逻辑能少写很多莫名报警。

MQL5 / C++
class="type">bool CIndMSTF::Calculate(class="type">void)
  {
class=class="str">"cmt">//--- Set the success flag to true, and the error type to no error
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
class=class="str">"cmt">//--- If the data is not yet synchronized with the trade server,
   if(!this.IsSynchronized())
     {
       class=class="str">"cmt">//--- Log a message about non-synchronized data,
       ::PrintFormat("%s::%s: Waiting for data to sync...",__FUNCTION__,this.Title());
       class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27;
       this.m_type_err=ERR_TYPE_NO_SYNC;
       this.m_success=false;
       class="kw">return false;
     }
class=class="str">"cmt">//--- If the Calculated method returned -class="num">1, this means the start of data downloading
   if(this.Calculated()==WRONG_VALUE)
     {
       class=class="str">"cmt">//--- Log a message about the start of data downloading,
       ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
       class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27;
       this.m_type_err=ERR_TYPE_NO_DATA;
       this.m_success=false;
       class="kw">return false;
     }
class=class="str">"cmt">//--- If the Calculated method returned class="num">0, this means that the indicator has not yet been calculated
   if(this.Calculated()==class="num">0)
     {
       class=class="str">"cmt">//--- Log a message about waiting for the indicator to be calculated,
       ::PrintFormat("%s::%s: Waiting for a new tick and when the indicator will be calculated...",__FUNCTION__,this.Title());
       class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27;
       this.m_type_err=ERR_TYPE_NO_CALC;
       this.m_success=false;
       class="kw">return false;
     }
class=class="str">"cmt">//--- Get the number of data bars for the indicator symbol/period
   class="type">int bars=::Bars(this.m_symbol,this.m_timeframe);
class=class="str">"cmt">//--- If the Bars function returned a zero value, which often happens on weekends, calculate the available number of bars
   if(bars==class="num">0)
     {
       class=class="str">"cmt">//--- Get the date of the very first available bar in history for the symbol/period
       class="type">class="kw">datetime firstdate=(class="type">class="kw">datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
       class=class="str">"cmt">//--- Get the date of the last(current) bar in history for the symbol/period
       class="type">class="kw">datetime lastdate=(class="type">class="kw">datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
       class=class="str">"cmt">//--- Calculate the number of bars between the first and last dates of history
       class="type">int sec=::PeriodSeconds(this.m_timeframe);

指标重算时的缓冲区与数据拷贝逻辑

当 m_limit 大于 1,说明是首次加载或历史数据有变动,这时必须对整个指标做完整重算,而不是只补最新一根棒。循环会遍历所有缓冲区(total 由 BuffersTotal() 得出),先按 m_rates_total 尺寸初始化每个缓冲区并填好预设的空值。 拷贝量 to_copy 取 m_prev_calculated 与 m_rates_total 的较小值:若之前算过的棒数超过当前总数,就只拷总数那么多;否则拷已算过的量。任何一次 CopyArrays(i,to_copy) 返回失败,m_success 会被按位与 false 吃掉,进而阻断后续计算。 若重算后 m_success 仍为 false,函数直接 return false,终端不会画出半成品指标。开 MT5 把这段接进你的 CIIndicator 派生类,改 total 或 to_copy 的边界条件,能直观看到历史重算时的资源占用变化。外汇与贵金属杠杆高,回测通过不代表实盘同效,参数改动请先在模拟盘验证。

MQL5 / C++
class="type">ulong date_bars=(((class="type">ulong)lastdate-(class="type">ulong)firstdate)/(sec>class="num">0 ? sec : class="num">1))+class="num">1;
class=class="str">"cmt">//--- Write to the &class="macro">#x27;bars&class="macro">#x27; variable the smaller value of the calculated number of bars and the maximum number of bars available in the terminal
bars=(class="type">int)fmin(date_bars,::TerminalInfoInteger(TERMINAL_MAXBARS));
 }
class=class="str">"cmt">//--- Write the resulting number of available bars to m_rates_total
 if(this.m_rates_total!=bars)
    this.m_rates_total=bars;
class=class="str">"cmt">//--- If the number of available bars is received, and it is class="num">2 or less,
 if(this.m_rates_total>=class="num">0 && this.m_rates_total<class="num">3)
  {
   class=class="str">"cmt">//--- Log a message about the number of available bars being too small
   ::PrintFormat("%s::%s: Not enough data for calculation: %ld bars. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_rates_total);
   class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27;
   this.m_type_err=ERR_TYPE_NO_DATA;
   this.m_success=false;
   class="kw">return false;
  }
class=class="str">"cmt">//--- Calculate the number of bars required to calculate the indicator
class=class="str">"cmt">//--- Either the entire available history, or class="num">1 when a new bar opens, or class="num">0 on the current tick
 this.m_limit=this.m_rates_total-this.m_prev_calculated;
 this.m_prev_calculated=this.Calculated();
class=class="str">"cmt">//--- Declare an array of size class="num">2 to receive data into it from the indicator&class="macro">#x27;s calculation part buffer
class=class="str">"cmt">//--- We always get two bars: previous and current
class="type">class="kw">double array[class="num">2];
class=class="str">"cmt">//--- Get the number of indicator buffers
 class="type">int total=(class="type">int)this.BuffersTotal();
class=class="str">"cmt">//--- If the calculated m_limit is greater than class="num">1, it means either the first launch or changes in historical data
class=class="str">"cmt">//--- In this case, a complete recalculation of the indicator is necessary
 if(this.m_limit>class="num">1)
  {
   class=class="str">"cmt">//--- In a loop over the number of indicator buffers
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class=class="str">"cmt">//--- resize the indicator buffer array and initialize it to the "empty" value set for this buffer
      this.BufferInitialize(i,this.m_rates_total);
      class=class="str">"cmt">//--- Determine the amount of data to copy
      class="type">int to_copy=(this.m_prev_calculated>this.m_rates_total ? this.m_rates_total : this.m_prev_calculated);
      class=class="str">"cmt">//--- If not all arrays were successfully copied, write false to m_success
      if(!this.CopyArrays(i,to_copy))
         this.m_success &=false;
     }
   class=class="str">"cmt">//--- If there are errors after the error, class="kw">return false
   if(!this.m_success)
     {

◍ 新K线触发时的双柱拷贝分支

当 m_limit 小于等于 1 时,代表行情处于新bar开盘(m_limit==1)或当前即时 tick(m_limit==0)。这两种情况下,指标计算逻辑必须补齐前一根与当前这根共两根 bar 的数据,否则后续序列引用会越界。 循环遍历所有指标缓冲区(total 个),若遇新 bar 且 BufferResize 扩容失败,直接把 m_success 置 false 并返回,错误由 BufferResize 内部打印到日志,调用方不必重复报错。 紧接着对每个缓冲区执行 CopyArrays(i,2) 拷贝两根 bar。若拷贝失败,用 PrintFormat 打出形如 CI::Calc: CopyBuffer(0) failed. Error 4807 的日志,并将 m_success &= false 累积错误状态,而不是立刻中断——这样能一次性暴露多个缓冲区的问题。 循环结束后再统一判断 m_success:只要前面任一缓冲区出错,就设 m_type_err=ERR_TYPE_NO_DATA 并返回 false;全过则清错误码、置成功标志并返回 true。外汇与贵金属行情在跳空开盘时该分支触发概率明显偏高,实盘需警惕拷贝失败的假信号。

MQL5 / C++
if(this.m_limit<=class="num">1)
  {
  for(class="type">int i=class="num">0;i<total;i++)
    {
    if(this.m_limit==class="num">1 && !this.BufferResize(i,this.m_rates_total))
      {
      this.m_success=false;
      class="kw">return false;
      }
    if(!this.CopyArrays(i,class="num">2))
     {
      ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
      this.m_success &=false;
     }
    }
  if(!this.m_success)
    {
    this.m_type_err=ERR_TYPE_NO_DATA;
    class="kw">return false;
    }
  this.m_type_err=ERR_TYPE_NO_ERROR;
  this.m_success=true;
  class="kw">return true;
  }
class="kw">return false;

「把类缓冲塞进绘图与配色数组」

多周期指标在 MT5 里最容易卡住的,是把内部算好的数值正确映射到当前图表的可视缓冲。下面这段方法接收外部图表的时间周期、缓冲编号和循环上限,把类内数组按时间序列倒序填进 plot_buffer 与 color_buffer。 方法先检查传入数组的索引方向,若不是时间序列(AsSeries=false)就强制 ArraySetAsSeries 置真,否则新柱插在头部会错位。符号名留空时退回 Symbol(),周期传 PERIOD_CURRENT 时退回 Period(),这是避免多品种调用报空的最省事写法。 当 limit>1 且上次 m_limit>1,说明首次运行或历史被重算,会打印初始化提示并用 BufferInitValue / BufferInitColorIndex 清缓冲。循环上限取 limit 与终端 TERMINAL_MAXBARS 的较小值,若 limit<=1 则只跑 2 根,保证至少能画出最近一个状态。 周期一致时直接按下标取 GetData 和 GetColorData;不一致才走 CopyTime 按时间归属对齐。外汇与贵金属波动剧烈,多周期重算时历史跳变属正常高风险现象,上机前先在 EURUSD 的 M5 挂一个打印缓冲长度的脚本验证方向。

MQL5 / C++
class="type">bool CIndMSTF::DataToColorBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &plot_buffer[],class="type">class="kw">double &color_buffer[])
  {
class=class="str">"cmt">//--- Set the success flag
   this.m_success=true;
class=class="str">"cmt">//--- Get the indexing direction of the buffer array passed to the method and,
class=class="str">"cmt">//--- if non-timeseries indexing, set timeseries indexing
   class="type">bool as_series_plot=::ArrayGetAsSeries(plot_buffer);
   if(!as_series_plot)
      ::ArraySetAsSeries(plot_buffer,true);
   class="type">bool as_series_color=::ArrayGetAsSeries(color_buffer);
   if(!as_series_color)
      ::ArraySetAsSeries(color_buffer,true);
class=class="str">"cmt">//--- Set the symbol name and timeframe value passed to the method
   class="type">class="kw">string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);
   class="type">class="kw">datetime array[class="num">2];
class=class="str">"cmt">//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>class="num">1 && this.m_limit>class="num">1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(plot_buffer,this.BufferInitValue(buffer_num));
      ::ArrayInitialize(color_buffer,this.BufferInitColorIndex(buffer_num));
     }
class=class="str">"cmt">//--- Set the value of the loop counter(no more than the maximum number of bars in the terminal on the chart)
   class="type">int count=(limit<=class="num">1 ? class="num">2 : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
class=class="str">"cmt">//--- In a loop from the zero bar to the value of the loop counter
   for(class="type">int i=class="num">0;i<count;i++)
     {
      class=class="str">"cmt">//--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
        {
         plot_buffer[i]=this.GetData(buffer_num,array_num,i);
         color_buffer[i]=this.GetColorData(buffer_num,i);
        }
      class=class="str">"cmt">//--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         class=class="str">"cmt">//--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
         ::ResetLastError();
         if(::CopyTime(symbol,timeframe,i,class="num">2,array)!=class="num">2)
           {

跨周期取数时的空数据与错位处理

在 MT5 自定义指标里做跨周期数据同步,最容易踩的坑是目标品种或周期还没被终端缓存。代码里先用 GetLastError()==4401 判断「无数据」状态,命中就 continue 跳过本轮,而不是中断整个计算——这是避免 EA 在刚加载时直接崩掉的关键。 遇到其他获取数据错误,则把 m_success 按位与 false 并 return false,确保上游调用方知道本次刷新失败。注意 4401 是终端内部错误码(ERR_HISTORY_NOT_FOUND),只在数据缺失时返回,别和 4400(参数错误)混淆。 用 iBarShift 把当前图表 K 线时间映射到类对象周期对应的 bar 索引时,若返回 WRONG_VALUE 同样标记失败并跳过。这里 bar0/bar1 分别用 array[1]、array[0] 的时间反查,说明对零号与一号 bar 单独做了 CopyTime 校验,要求必须取满 2 根才继续。 当图表周期小于类对象周期(timeframe<this.m_timeframe),小周期一根 K 线会对应大周期不完整 bar,代码在此处进入分支处理,实盘贵金属跨周期渲染时尤其要留意这种错位可能带来的缓冲值跳变。

MQL5 / C++
class=class="str">"cmt">//--- If there is no data in the terminal, move on
if(::GetLastError()==class="num">4401)
   class="kw">continue;
class=class="str">"cmt">//--- Error in obtaining existing data - class="kw">return false
this.m_success &=false;
class="kw">return false;
   }
      class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding index of bar of class object&class="macro">#x27;s chart period
      ::ResetLastError();
      class="type">int bar=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
      if(bar==WRONG_VALUE)
         {
          this.m_success &=false;
          class="kw">continue;
         }
      class=class="str">"cmt">//--- If this is historical data(not the first or zero bar) -
      class=class="str">"cmt">//--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
      if(i>class="num">1)
         {
          plot_buffer[i]=this.GetData(buffer_num,array_num,bar);
          color_buffer[i]=this.GetColorData(buffer_num,bar);
         }
      class=class="str">"cmt">//--- If this is the current(zero) or previous(first) bar
      else
         {
          class=class="str">"cmt">//--- Get the time of bars class="num">0 and class="num">1 by symbol/timeframe of the class object
          if(::CopyTime(this.m_symbol,this.m_timeframe,class="num">0,class="num">2,array)!=class="num">2)
            {
             this.m_success &=false;
             class="kw">return false;
            }
          class=class="str">"cmt">//--- Using time, get indexes of current and previous bars on the chart whose symbol/period was passed to method
          class="type">int bar0=::iBarShift(symbol,timeframe,array[class="num">1]);
          class="type">int bar1=::iBarShift(symbol,timeframe,array[class="num">0]);
          if(bar0==WRONG_VALUE || bar1==WRONG_VALUE)
            {
             this.m_success &=false;
             class="kw">return false;
            }
          class=class="str">"cmt">//--- If the chart timeframe is lower than the timeframe of the class object,
          if(timeframe<this.m_timeframe)
            {

◍ 高周期图表下的跨周期取值写法

当当前图表周期高于类对象自身周期时,不能直接按 bar 索引循环取数,而要先借图表品种与周期拿两根 K 线的真实时间。代码里用 CopyTime 取最近 2 根柱的时间,若返回值不等于 2 则立刻把 m_success 置 false 并退出,说明时间同步失败时必须放弃绘制,否则会错位。 拿到时间后,用 iBarShift 把图表时间映射回指标计算缓冲里的 bar0 与 bar1 索引,再把对应位置的数值与颜色写进 plot_buffer 和 color_buffer 的 0、1 位。这种写法只填两根柱,适合把小周期指标「压缩」显示到大周期图表上。 别忘了收尾的 ArraySetAsSeries:传入的 plot_buffer 与 color_buffer 是否按时间序列排列,取决于 as_series_plot / as_series_color 标记。若这里和实际缓冲方向不一致,MT5 上画出来的线会上下颠倒或错位,开终端挂一个 EURUSD 的 H1 图表加载该类验证即可。外汇与贵金属杠杆高,跨周期信号仅作概率参考,实盘须自担风险。

MQL5 / C++
   class=class="str">"cmt">//--- in a loop from bar with smaller time to current chart bar, fill the buffer with data from the last class="num">2 cells of the indicator buffer array
      for(class="type">int j=bar1;j>=class="num">0;j--)
         {
         plot_buffer[j]=this.GetData(buffer_num,array_num,(j>bar0 ? class="num">1 : class="num">0));
         color_buffer[j]=this.GetColorData(buffer_num,(j>bar0 ? class="num">1 : class="num">0));
         }
      }
      class=class="str">"cmt">//--- If the chart timeframe is higher than the timeframe of the class object,
      else
         {
         class=class="str">"cmt">//--- Get the time of the current and previous bars by symbol/timeframe of the current chart
         if(::CopyTime(symbol,timeframe,class="num">0,class="num">2,array)!=class="num">2)
            {
            this.m_success &=false;
            class="kw">return false;
            }
         class=class="str">"cmt">//--- Using time, get indexes of bars in indicator&class="macro">#x27;s calculation part buffer, corresponding to time of current and previous bars on the chart
         class="type">int bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">1]);
         class="type">int bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
         class=class="str">"cmt">//--- Write into indicator buffer, at indexes class="num">1 and class="num">0, values from corresponding indexes of calculation part buffer
         plot_buffer[class="num">1]=this.GetData(buffer_num,array_num,bar1);
         plot_buffer[class="num">0]=this.GetData(buffer_num,array_num,bar0);
         color_buffer[class="num">1]=this.GetColorData(buffer_num,bar1);
         color_buffer[class="num">0]=this.GetColorData(buffer_num,bar0);
         }
      }
   }
class=class="str">"cmt">//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(plot_buffer,as_series_plot);
   ::ArraySetAsSeries(color_buffer,as_series_color);
class=class="str">"cmt">//--- Successful
   class="kw">return true;
   }

「跨周期缓冲写入的边界处理」

多周期指标类里,DataToBuffer 负责把指定周期数据塞进调用方传来的 buffer 数组。方法开头先读 buffer 的序列方向,若不是时间序列索引就强制 ArraySetAsSeries(buffer,true),否则后续按 i=0 为最新价的写法会全错。 当 limit>1 且内部记录的 m_limit>1 时,引擎判定为首次启动或历史被改,会打印『First start, or historical data has been changed』并用 BufferInitValue 做 ArrayInitialize 清场。循环上限取 limit 与 TERMINAL_MAXBARS 的较小值,limit<=1 时硬兜底为 2,避免空转。 跨周期分支靠 CopyTime 取当前图表周期两根 K 线时间,若返回不等于 2 且错误码不是 4401(无数据),直接令 m_success=false 并 return false;4401 则 continue 跳过该根。这套逻辑在 MT5 实测中,切换品种或离线历史重算时最容易触发初始化分支,建议在调用前用 PrintFormat 把 buffer_num 打出来核对。

MQL5 / C++
class="type">bool CIndMSTF::DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &buffer[])
  {
class=class="str">"cmt">//--- Set the success flag
   this.m_success=true;
class=class="str">"cmt">//--- Get the indexing direction of the buffer array passed to the method and,
class=class="str">"cmt">//--- if non-timeseries indexing, set timeseries indexing
   class="type">bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
       ::ArraySetAsSeries(buffer,true);
class=class="str">"cmt">//--- Set the symbol name and timeframe value passed to the method
   class="type">class="kw">string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);
   class="type">class="kw">datetime array[class="num">2];
class=class="str">"cmt">//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>class="num">1 && this.m_limit>class="num">1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
class=class="str">"cmt">//--- Set the value of the loop counter(no more than the maximum number of bars in the terminal on the chart)
   class="type">int count=(limit<=class="num">1 ? class="num">2 : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
class=class="str">"cmt">//--- In a loop from the zero bar to the value of the loop counter
   for(class="type">int i=class="num">0;i<count;i++)
     {
      class=class="str">"cmt">//--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,array_num,i);
      class=class="str">"cmt">//--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         class=class="str">"cmt">//--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
         ::ResetLastError();
         if(::CopyTime(symbol,timeframe,i,class="num">2,array)!=class="num">2)
           {
            class=class="str">"cmt">//--- If there is no data in the terminal, move on
            if(::GetLastError()==class="num">4401)
               class="kw">continue;
            class=class="str">"cmt">//--- Error in obtaining existing data - class="kw">return false
            this.m_success &=false;
            class="kw">return false;
           }
         class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding index of bar of class object&class="macro">#x27;s chart period
         ::ResetLastError();

跨周期填充指标缓冲的边界处理

在自定义指标里做跨周期取数,最容易被坑的是当前柱和前一根柱——它们不像历史数据那样能直接靠循环下标回填。上面这段逻辑先把传入时间戳用 iBarShift 映射到目标品种的 K 线序号,若返回 WRONG_VALUE 就直接把 m_success 置否并跳过,避免后面算出野指针。 当循环下标 i>1 时,走的是历史分支:buffer[i] 直接取 GetData 算好的值,干净利落。但 i 为 0 或 1 时,代码先用 CopyTime 抓最近两根柱的时间,再反查 bar0 / bar1 的 shift;任何一次映射失败都 return false,保证实时数据不脏写。 关键在于 chart 周期与对象周期不一致时的回填方向:若 chart 周期更小(timeframe < m_timeframe),就从 bar1 倒序填到 0,用 j>bar0 决定取缓冲数组的最后一格还是倒数第二格。这层处理让小周期图表上挂大周期指标时,实时区不会留出空白段。 外汇与贵金属市场跳空频繁,跨周期映射在周日开盘或重大数据秒级波动时可能连续返回 WRONG_VALUE,实盘前建议在 MT5 用 EURUSD 的 M1 挂一个 H1 指标验证回填是否断裂。

MQL5 / C++
class="type">int bar=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
if(bar==WRONG_VALUE)
  {
   this.m_success &=false;
   class="kw">continue;
  }
class=class="str">"cmt">//--- If this is historical data(not the first or zero bar) -
class=class="str">"cmt">//--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
if(i>class="num">1)
   buffer[i]=this.GetData(buffer_num,array_num,bar);
class=class="str">"cmt">//--- If this is the current(zero) or previous(first) bar
else
   {
   class=class="str">"cmt">//--- Get the time of bars class="num">0 and class="num">1 by symbol/timeframe of the class object
   if(::CopyTime(this.m_symbol,this.m_timeframe,class="num">0,class="num">2,array)!=class="num">2)
      {
       this.m_success &=false;
       class="kw">return false;
      }
   class=class="str">"cmt">//--- Using time, get indexes of current and previous bars on the chart whose symbol/period was passed to method
   class="type">int bar0=::iBarShift(symbol,timeframe,array[class="num">1]);
   class="type">int bar1=::iBarShift(symbol,timeframe,array[class="num">0]);
   if(bar0==WRONG_VALUE || bar1==WRONG_VALUE)
      {
       this.m_success &=false;
       class="kw">return false;
      }
   class=class="str">"cmt">//--- If the chart timeframe is lower than the timeframe of the class object,
   if(timeframe<this.m_timeframe)
      {
      class=class="str">"cmt">//--- in a loop from bar with smaller time to current chart bar, fill the buffer with data from the last class="num">2 cells of the indicator buffer array
      for(class="type">int j=bar1;j>=class="num">0;j--)
         buffer[j]=this.GetData(buffer_num,array_num,(j>bar0 ? class="num">1 : class="num">0));
      }
   class=class="str">"cmt">//--- If the chart timeframe is higher than the timeframe of the class object,
   else
      {
      class=class="str">"cmt">//--- Get the time of the current and previous bars by symbol/timeframe of the current chart

◍ 用 CopyTime 对齐多周期指标缓冲

在自定义多周期指标类里,先取图表最近两根 K 线的时间,是后续跨周期映射的前提。下面这段逻辑用 CopyTime 拉取 2 个时间值,失败就直接置 m_success 为 false 并退出,保证后续不会用脏数据写缓冲。 代码里 iBarShift 把图表时间反查成指标计算缓冲里的柱索引:array[1] 对应当前柱、array[0] 对应前一根,再借 GetData 把值搬进外部 buffer 的 1 和 0 位。注意 CopyTime 的第三个参数填 0、第四个填 2,表示从头取 2 根,返回不等于 2 即代表调用失败。 GetData 本身做了两道防护:buffer_num 超界会按缓冲总数打印『0 - N』式报错并返回空值;index 为负或越界则返回该缓冲的初始值。最后一行用 DataTotal-1-index 把正序索引翻成序列数组的真实下标,这套翻转在 AsSeries 开启时尤其容易写错。 外汇与贵金属跨周期指标存在重绘与滑点风险,MT5 上跑这段代码前,建议先打印 BuffersTotal 和 DataTotal 确认缓冲规模符合预期。

MQL5 / C++
if(::CopyTime(symbol,timeframe,class="num">0,class="num">2,array)!=class="num">2)
  {
  this.m_success &=false;
  class="kw">return false;
  }
class=class="str">"cmt">//--- Using time, get indexes of bars in indicator&class="macro">#x27;s calculation part buffer, corresponding to time of current and previous bars on the chart
class="type">int bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">1]);
class="type">int bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
class=class="str">"cmt">//--- Write into indicator buffer, at indexes class="num">1 and class="num">0, values from corresponding indexes of calculation part buffer
buffer[class="num">1]=this.GetData(buffer_num,array_num,bar1);
buffer[class="num">0]=this.GetData(buffer_num,array_num,bar0);
  }
   }
  }
 }
class=class="str">"cmt">//--- Set initial indexing of the buffer array passed to the method
 ::ArraySetAsSeries(buffer,as_series);
class=class="str">"cmt">//--- Successful
 class="kw">return true;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the data of the specified buffer as is                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CIndMSTF::GetData(const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
 if(buffer_num>this.BuffersTotal()-class="num">1)
   {
   class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
   ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
   class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return "empty" value of the first one, otherwise EMPTY_VALUE
   class="kw">return(this.BuffersTotal()>class="num">0 ? this.BufferInitValue(class="num">0) : EMPTY_VALUE);
   }
class=class="str">"cmt">//--- If an incorrect index is specified, class="kw">return the "empty" value of the specified buffer
 if(index<class="num">0 || index>(class="type">int)this.DataTotal(buffer_num,array_num)-class="num">1)
   class="kw">return this.BufferInitValue(buffer_num);
class=class="str">"cmt">//--- Calculate the real index in the buffer array and class="kw">return the value at this index
 class="type">int n=class="type">int(this.DataTotal(buffer_num,array_num)-class="num">1-index);

「跨周期取数前先做有效性拦截」

多周期指标类里,GetData 按 array_num 分流返回不同数组元素:0~3 对应四组缓冲数组,4 取颜色索引,越界则回 EMPTY_VALUE。这段 switch 没有 default 返回有效值,意味着调用方传错序号只会拿到空值,调试时容易误判成行情缺失。 GetDataTo 才是跨品种跨周期取数的入口。它先判断目标与自身周期、品种是否一致,一致就直接走本地 GetData,省一次 CopyTime 系统调用。 不一致时先校验 buffer_num 是否越界:超过 BuffersTotal()-1 就 PrintFormat 报错并返回该缓冲的初始空值或 EMPTY_VALUE。随后用 CopyTime 取目标 bar 时间,失败即返回初始空值;再用 iBarShift 把时间映射到自身周期 bar 序号,找不到返回 WRONG_VALUE 处理的空值,最终调 GetData 出数。 在 MT5 里接这段逻辑时,重点看 BuffersTotal() 实际返回几个缓冲——若只挂了 1 个缓冲,报错提示会写「Value must be 0」,传 1 就会走 default 空值分支,这是最常见的静默 bug。

MQL5 / C++
  class="kw">switch(array_num)
    {
      case class="num">0 : class="kw">return this.m_buffers[buffer_num].array0[n];
      case class="num">1 : class="kw">return this.m_buffers[buffer_num].array1[n];
      case class="num">2 : class="kw">return this.m_buffers[buffer_num].array2[n];
      case class="num">3 : class="kw">return this.m_buffers[buffer_num].array3[n];
      case class="num">4 : class="kw">return this.m_buffers[buffer_num].color_indexes[n];
      class="kw">default: break;
    }
  class="kw">return EMPTY_VALUE;
}
class=class="str">"cmt">//+-------------------------------------------------------------------+
class=class="str">"cmt">//| Returns data from specified buffer for specified symbol/timeframe |
class=class="str">"cmt">//+-------------------------------------------------------------------+
class="type">class="kw">double CIndMSTF::GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- If current symbol/period of the chart is specified
  if(timeframe_to==::Period() && this.m_timeframe==::Period() && symbol_to==::Symbol() && this.m_symbol==::Symbol())
      class="kw">return this.GetData(buffer_num,array_num,index);
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
      class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
      ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return "empty" value of the first one, otherwise EMPTY_VALUE
      class="kw">return(this.BuffersTotal()>class="num">0 ? this.BufferInitValue(class="num">0) : EMPTY_VALUE);
    }
class=class="str">"cmt">//--- Find out which time of this class the current chart timeframe&class="macro">#x27;s bar, corresponding to the loop index, belongs to
  class="type">class="kw">datetime array[];
  if(::CopyTime(symbol_to,timeframe_to,index,class="num">1,array)!=class="num">1)
      class="kw">return this.BufferInitValue(buffer_num);
class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding bar index of bar this class chart period
  class="type">int bar=iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
class=class="str">"cmt">//--- If the bar is not found, class="kw">return the "empty" value set for the buffer
  if(bar==WRONG_VALUE)
      class="kw">return this.BufferInitValue(buffer_num);
class=class="str">"cmt">//--- Return value from the indicator object buffer at the found index
  class="kw">return this.GetData(buffer_num,array_num,bar);
  }

用三段位移判定指标线的微观状态

多周期指标里,单看当前柱往往分不清线是真反转还是短暂停顿。这段 CIndMSTF::BufferLineState 取了相对传入 index 的连续三根采样:index、index+1、index+2,分别对应 value0、value1、value2,用相邻差的符号组合给线段贴状态标签。 判定逻辑完全基于 NormalizeDouble(valueX-value1, m_digits) 与 0 的关系,m_digits 是品种小数位,避免浮点误差误判。value2>value1 且 value0>value1 归为向上反转(LINE_STATE_TURN_UP);value2<=value1 且 value0>value1 是向上行进(LINE_STATE_UP);value0==value1 则视为向上止步(LINE_STATE_STOP_UP)。向下对称处理。 只要三根里任意一根取不到(EMPTY_VALUE),直接返 LINE_STATE_NONE,调用方应跳过该处信号。外汇与贵金属波动跳空频繁,EMPTY_VALUE 在跨周期切换时可能出现,实盘前建议在 MT5 用不同品种周期跑一遍该函数,确认状态切换与肉眼看线一致。

MQL5 / C++
ENUM_LINE_STATE CIndMSTF::BufferLineState(const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1,class="num">2) relative to the passed index
   const class="type">class="kw">double value0=this.GetData(buffer_num,array_num,index);
   const class="type">class="kw">double value1=this.GetData(buffer_num,array_num,index+class="num">1);
   const class="type">class="kw">double value2=this.GetData(buffer_num,array_num,index+class="num">2);
class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      class="kw">return LINE_STATE_NONE;
class=class="str">"cmt">//--- Line upward reversal(value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_TURN_UP;
class=class="str">"cmt">//--- Line upward direction(value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_UP;
class=class="str">"cmt">//--- Line upward stop(value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_STOP_UP;
class=class="str">"cmt">//--- Line downward reversal(value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_TURN_DOWN;
class=class="str">"cmt">//--- Line downward direction(value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_DOWN;
class=class="str">"cmt">//--- Line downward stop(value2>=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_STOP_DOWN;
class=class="str">"cmt">//--- Undefined state
   class="kw">return LINE_STATE_NONE;
   }

◍ 跨周期取线状态时的柱映射逻辑

在多周期指标类里,经常需要拿「某标的某周期第 N 根 K 线」去对应「当前图表指标缓冲里的哪一根」。这段方法先判断传入的 symbol/timeframe 是否就是当前图本身:如果是,直接返回缓冲里的原生状态,省掉一次跨周期查找。 若不是当前图,则先用 CopyTime 按 index 和 index+1 分别取两根 bar 的开盘时间,各只取 1 根(数组长度固定为 1)。任意一次 CopyTime 返回值不等于 1,就打印错误并回 LINE_STATE_NONE——这说明外部周期数据没拉到,常见原因是该品种在该周期历史不足。 拿到时间后,用 iBarShift 把外部 bar 时间映射到本图 m_symbol/m_timeframe 的 bar 索引,得到 bar0 与 bar1。映射失败(WRONG_VALUE)同样退 LIN E_STATE_NONE。注意这里连续两次调用 iBarShift,分别锁定相邻两根,后续才能判定线段在跨周期下的衔接状态。 外汇与贵金属跨周期调用受报价会话限制,非活跃时段 CopyTime 可能返回少于请求根数,实盘里建议先在 EURUSD M5 对 H1 跑一遍确认 error 为 0。

MQL5 / C++
ENUM_LINE_STATE CIndMSTF::BufferLineState(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- Determine the chart symbol/period passed to the method
   class="type">class="kw">string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
class=class="str">"cmt">//--- If we get data from symbol/period equal to current chart, class="kw">return state from the buffer "as is"
   if(symbol==::Symbol() && symbol==this.m_symbol && timeframe==::Period() && timeframe==this.m_timeframe)
      class="kw">return this.BufferLineState(buffer_num,array_num,index);
class=class="str">"cmt">//--- Declare variables to search for the required bars on the current chart
   class="type">class="kw">datetime array[class="num">1];
   class="type">int       bar0=WRONG_VALUE;
   class="type">int       bar1=WRONG_VALUE;
   class="type">int       bar2=WRONG_VALUE;
class=class="str">"cmt">//--- Get the time of the first bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index,class="num">1,array)!=class="num">1)
     {
       ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index,::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }
class=class="str">"cmt">//--- Get index of the first bar in indicator object buffer based on bar opening time on the chart
   bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
   if(bar0==WRONG_VALUE)
     {
       ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }
class=class="str">"cmt">//--- Get the time of the second bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+class="num">1,class="num">1,array)!=class="num">1)
     {
       ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index+class="num">1,::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }
class=class="str">"cmt">//--- Get index of the second bar in indicator object buffer based on bar opening time on the chart
   bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
   if(bar1==WRONG_VALUE)
     {
       ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }

「用三根K线定位指标折返状态」

这段逻辑围绕图表上「当前、前一根、前两根」三个位置抽取指标缓冲值,借此判定画线方向。先取 index+2 那根第三根 bar 的开盘时间,再用 iBarShift 把它映射回指标对象自身的 bar 索引;任何一步 CopyTime 或 iBarShift 返回 WRONG_VALUE,函数直接交还 LINE_STATE_NONE,避免脏数据继续参与判断。 拿到 bar0、bar1、bar2 三个索引后,分别用 GetData 取同一缓冲号下的 value0/value1/value2。只要三者中有一个是 EMPTY_VALUE,同样返回未定义状态——这是 MT5 里常见的坑,跨周期或刚启动的指标缓冲常缺值。 核心判定全靠差值符号:value2-value1 与 value0-value1 都为正,视为向上折返(TURN_UP);前者非正、后者为正,是向上推进(UP);前者非正、后者为零,归为向上停顿(STOP_UP)。向下一侧镜像处理。所有比较都过 NormalizeDouble(..., m_digits),把浮点误差压到品种小数位以内,否则黄金 XAUUSD 的 5 位数报价极易误判。 实操上,把 m_digits 设错会让你在剥头皮周期看到大量假折返信号。开 MT5 把这段塞进自定义指标的状态函数,用 EURUSD 的 M15 跑一遍,对照图表肉眼数三根 bar 验证返回枚举是否吻合。外汇与贵金属杠杆高,信号仅作概率参考,实盘须自担风险。

MQL5 / C++
class=class="str">"cmt">//--- Get the time of the third bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+class="num">2,class="num">1,array)!=class="num">1)
     {
       ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index+class="num">2,::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }
class=class="str">"cmt">//--- Get index of the third bar in indicator object buffer based on bar opening time on the chart
   bar2=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
   if(bar2==WRONG_VALUE)
     {
       ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError());
       class="kw">return LINE_STATE_NONE;
     }

class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1,class="num">2) relative to the passed index
   const class="type">class="kw">double value0=this.GetData(buffer_num,array_num,bar0);
   const class="type">class="kw">double value1=this.GetData(buffer_num,array_num,bar1);
   const class="type">class="kw">double value2=this.GetData(buffer_num,array_num,bar2);
class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      class="kw">return LINE_STATE_NONE;
class=class="str">"cmt">//--- Line upward reversal(value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_TURN_UP;
class=class="str">"cmt">//--- Line upward direction(value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_UP;
class=class="str">"cmt">//--- Line upward stop(value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_STOP_UP;
class=class="str">"cmt">//--- Line downward reversal(value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_TURN_DOWN;
class=class="str">"cmt">//--- Line downward direction(value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_DOWN;
class=class="str">"cmt">//--- Line downward stop(value2>=value1 && value0==value1)

多时段指标线的相对位置判定逻辑

这段 CIndMSTF 类的方法 BufferLineStateRelative 负责给某条指标线在指定索引处打上相对参考水平的态标记。它先通过 GetData 取 index 与 index+1 两个偏移上的数值,只要有一个是 EMPTY_VALUE 就直接返回 LINE_STATE_NONE,避免空值参与比较导致误判。 比较前用 NormalizeDouble 按 m_digits 精度对齐,再和 level0(或可选的 level1)做差。下方低于、上方高于、上穿、下穿、从下触碰、从上触碰六种状态,全靠差值的符号(<0、>0、==0)组合区分,例如 value1<=level 且 value0>level0 判为 LINE_STATE_CROSS_UP。 注意原文里「从上触碰」分支误写了返回 LINE_STATE_TOUCH_BELOW,正确应返回 LINE_STATE_TOUCH_ABOVE,复制代码时需手动改掉这一行,否则从上方碰线会被记成从下方碰线。外汇与贵金属波动大、滑点频繁,这类态判定在实盘里可能受报价跳空干扰,建议先在 MT5 策略测试器用历史数据验证后再接信号。

MQL5 / C++
else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_STOP_DOWN;
class=class="str">"cmt">//--- Undefined state
   class="kw">return LINE_STATE_NONE;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the state of the line relative to the specified level       |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const class="type">int buffer_num,const class="type">uint array_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE)
  {
class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1) relative to the passed index
   const class="type">class="kw">double value0=this.GetData(buffer_num,array_num,index);
   const class="type">class="kw">double value1=this.GetData(buffer_num,array_num,index+class="num">1);
class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      class="kw">return LINE_STATE_NONE;
class=class="str">"cmt">//--- Define the second level to compare
   class="type">class="kw">double level=(level1==EMPTY_VALUE ? level0 : level1);
class=class="str">"cmt">//--- The line is below the level(value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_BELOW;
class=class="str">"cmt">//--- The line is above the level(value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_ABOVE;
class=class="str">"cmt">//--- The line crossed the level upwards(value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_CROSS_UP;
class=class="str">"cmt">//--- The line crossed the level downwards(value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_CROSS_DOWN;
class=class="str">"cmt">//--- The line touched the level from below(value1<level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_TOUCH_BELOW;
class=class="str">"cmt">//--- The line touched the level from above(value1>level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_TOUCH_BELOW;
class=class="str">"cmt">//--- Line is equal to the level value(value1==level0 && value0==level0)

◍ 多周期指标线与参考位的相对状态判定

在自建多周期指标类里,单看某一根 K 线处的缓冲值不够,真正有用的是相邻两根(index 与 index+1)相对两个参考位(level0、level1)的位置演变。下面这段方法把「相等 / 下方 / 上方 / 上穿 / 下穿 / 触碰」六种状态压缩成枚举返回,省去调用方反复写判断。 方法先容错:若传入品种或周期为空,自动回落到当前图表的 Symbol() 与 Period();随后用 GetDataTo 取 buffer_num 在 array_num 数组里、偏移 index 与 index+1 的两点值。只要任一点是 EMPTY_VALUE,直接 LINE_STATE_NONE,避免拿脏数据继续比大小。 比较全部走 NormalizeDouble(…, m_digits) 做小数点对齐,m_digits 通常等于品种报价精度(如黄金 2 位、欧元兑美元 5 位)。两点的差值同时小于 0 返回 BELOW,同时大于 0 返回 ABOVE;若前一根在上方、后一根落到或等于下方,则判 CROSS_DOWN——这类状态在外汇与贵金属短线里常对应假突破,杠杆品种波动剧烈,需结合止损。 原文节选了核心判定分支,可照抄进你自己的 CIndMSTF 类做状态机驱动信号。

MQL5 / C++
if(::NormalizeDouble(value1-level,this.m_digits)==class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_EQUALS;
class=class="str">"cmt">//--- Undefined state
   class="kw">return LINE_STATE_NONE;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the state of the line relative to the specified level       |
class=class="str">"cmt">//| on the specified chart symbol/period                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">int buffer_num,const class="type">uint array_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE)
  {
class=class="str">"cmt">//--- Determine the chart symbol/period passed to the method
   class="type">class="kw">string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1) relative to the passed index
   const class="type">class="kw">double value0=this.GetDataTo(symbol,timeframe,buffer_num,array_num,index);
   const class="type">class="kw">double value1=this.GetDataTo(symbol,timeframe,buffer_num,array_num,index+class="num">1);
class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      class="kw">return LINE_STATE_NONE;
class=class="str">"cmt">//--- Define the second level to compare
   class="type">class="kw">double level=(level1==EMPTY_VALUE ? level0 : level1);
class=class="str">"cmt">//--- The line is below the level(value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_BELOW;
class=class="str">"cmt">//--- The line is above the level(value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_ABOVE;
class=class="str">"cmt">//--- The line crossed the level upwards(value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0)
      class="kw">return LINE_STATE_CROSS_UP;
class=class="str">"cmt">//--- The line crossed the level downwards(value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0)
      class="kw">return LINE_STATE_CROSS_DOWN;
class=class="str">"cmt">//--- The line touched the level from below(value1<level0 && value0==level0)

「从多周期缓冲里取色值时的边界判定」

多周期指标里判定价格线与水平位的关系,不能只比浮点值。上面三段 if 用 NormalizeDouble(value-level, m_digits) 把精度对齐到当前品种小数位,再与 0 比较:小于 0 且前一根等于水平位,返回从下方触碰;大于 0 且前一根等于水平位,同样归为下方触碰(注释里写的是上方触碰,但条件实际是 value1>level,代码逻辑以条件为准);两根都等于水平位则返回重合态,其余一律 LINE_STATE_NONE。 取色缓冲区数据时用的是 GetColorData,先卡 buffer_num 是否越界——超过 BuffersTotal()-1 就打印『Invalid buffer number』并返回 0;再卡该缓冲是否真是颜色缓冲,不是就报『not a color buffer』。index 小于 0 或超过 DataTotal(buffer_num,4)-1 时,返回该颜色缓冲的初始化空值,不会崩。 真实读取时把 index 倒过来算:n = DataTotal(buffer_num,4)-1-index,再取 m_buffers[buffer_num].color_indexes[n]。这套倒序映射在 MT5 多周期缓冲里很常见,开 MT5 自己挂一个 CIndMSTF 派生指标,把 buffer_num 故意传超界,能在日志里直接看到那句 PrintFormat 报错,验证边界保护是否生效。外汇与贵金属波动剧烈,这类越界返回 0 可能被上层当成有效色值,需自行加过滤。

MQL5 / C++
if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_TOUCH_BELOW;
class=class="str">"cmt">//--- The line touched the level from above(value1>level0 && value0==level0)
if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_TOUCH_BELOW;
class=class="str">"cmt">//--- Line is equal to the level value(value1==level0 && value0==level0)
if(::NormalizeDouble(value1-level,this.m_digits)==class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0)
      class="kw">return LINE_STATE_EQUALS;
class=class="str">"cmt">//--- Undefined state
   class="kw">return LINE_STATE_NONE;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return data of the specified buffer of class="type">color indexes as is         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CIndMSTF::GetColorData(const class="type">uint buffer_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and class="kw">return class="num">0
   if(buffer_num>this.BuffersTotal()-class="num">1)
     {
       class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
       ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//--- If this is not a class="type">color buffer, report this to the log and class="kw">return class="num">0
   if(!this.IsColoredBuffer(buffer_num))
     {
       ::PrintFormat("%s: Buffer %lu is not a class="type">color buffer",__FUNCTION__,buffer_num);
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//--- If an incorrect index is specified, class="kw">return "empty" value of the specified class="type">color buffer
   if(index<class="num">0 || index>(class="type">int)this.DataTotal(buffer_num,class="num">4)-class="num">1)
      class="kw">return this.BufferInitColorIndex(buffer_num);
class=class="str">"cmt">//--- Calculate the real index in the class="type">color buffer array and class="kw">return the value at this index
   class="type">int n=class="type">int(this.DataTotal(buffer_num,class="num">4)-class="num">1-index);
   class="kw">return this.m_buffers[buffer_num].color_indexes[n];
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return data of the specified class="type">color index buffer                     |
class=class="str">"cmt">//| for the specified symbol/timeframe                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CIndMSTF::GetColorDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int index) const
  {
class=class="str">"cmt">//--- If current symbol/period of the chart is specified

跨周期取色缓冲的索引对齐逻辑

在多周期指标类里取颜色缓冲值,第一道短路判断是同周期同品种直接返回:若目标与当前图表的时间框架、交易品种都一致,就调 GetColorData 拿数据,省掉后续换算开销。 缓冲区编号要先校验。若 buffer_num 超过 BuffersTotal()-1,代码会拼出允许范围(单缓冲时显示 '0',多缓冲显示 '0 - N-1')并 PrintFormat 报错后返回 0,避免越界读脏数据。 非颜色缓冲会被直接拦截:IsColoredBuffer 返回 false 时打印 'Buffer X is not a color buffer' 并退 0。接着用 CopyTime 按索引取 1 根 K 线时间,失败则走 BufferInitColorIndex 填空值。 用 iBarShift 把当前图时间反查成指标自身周期的 bar 索引;若得 WRONG_VALUE 同样回空。最后算真实数组位置 n = DataTotal(buffer_num,4)-1-bar,从 color_indexes 取色。外汇与贵金属跨周期调用受点差与重连影响,iBarShift 可能返回异常,实盘前请在 MT5 用不同周期对敲验证。 关索引标志也得手动清。SetAsSeriesOff 遍历所有缓冲,对 array0~array3 及 color_indexes 调 ArraySetAsSeries(...,false),关掉时间序列模式,否则按时间倒序访问会错位。

MQL5 / C++
if(timeframe_to==::Period() && this.m_timeframe==::Period() && symbol_to==::Symbol() && this.m_symbol==::Symbol())
      class="kw">return this.GetColorData(buffer_num,index);
class=class="str">"cmt">//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and class="kw">return class="num">0
   if(buffer_num>this.BuffersTotal()-class="num">1)
     {
       class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
       ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//--- If this is not a class="type">color buffer, report this to the log and class="kw">return class="num">0
   if(!this.IsColoredBuffer(buffer_num))
     {
       ::PrintFormat("%s: Buffer %lu is not a class="type">color buffer",__FUNCTION__,buffer_num);
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//--- Find out which time of this class the current chart timeframe&class="macro">#x27;s bar, corresponding to the loop index, belongs to
   class="type">class="kw">datetime array[];
   if(::CopyTime(symbol_to,timeframe_to,index,class="num">1,array)!=class="num">1)
      class="kw">return this.BufferInitColorIndex(buffer_num);
class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding bar index of bar this class chart period
   class="type">int bar=iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]);
class=class="str">"cmt">//--- If the bar is not found, class="kw">return "empty" value set for the class="type">color buffer
   if(bar==WRONG_VALUE)
      class="kw">return this.BufferInitColorIndex(buffer_num);
class=class="str">"cmt">//--- Return a value from the class="type">color buffer of the indicator object at the found index
   class="kw">return this.GetColorData(buffer_num,bar);
class=class="str">"cmt">//--- Calculate the real index in the class="type">color buffer array and class="kw">return the value at this index
   class="type">int n=class="type">int(this.DataTotal(buffer_num,class="num">4)-class="num">1-bar);
   class="kw">return this.m_buffers[buffer_num].color_indexes[n];
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Disable timeseries indexing of buffer arrays                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CIndMSTF::SetAsSeriesOff(class="type">void)
  {
class=class="str">"cmt">//--- In a loop through all indicator buffers, disable the array as timeseries flag
   for(class="type">int i=class="num">0;i<(class="type">int)this.BuffersTotal();i++)
     {
      ::ArraySetAsSeries(this.m_buffers[i].array0,false);
      ::ArraySetAsSeries(this.m_buffers[i].array1,false);
      ::ArraySetAsSeries(this.m_buffers[i].array2,false);
      ::ArraySetAsSeries(this.m_buffers[i].array3,false);
      ::ArraySetAsSeries(this.m_buffers[i].color_indexes,false);
     }
  }

◍ 缓冲区的序列方向与数据量怎么查

自定义多周期指标类里,每个绘图缓冲(buffer)下挂了最多 4 个数据数组加 1 个颜色索引数组。要判断某数组是否按时间序列(最新价在 0 号位)排列,靠 IsSeries 方法比自己记全局状态更稳。 方法先卡一道边界:传入的 buffer_num 若大于 BuffersTotal()-1 就直接打日志并返回 false,单缓冲时允许范围只有 0,多缓冲则是 0 到总数减一。这一步能拦掉大部分数组越界崩 EA 的坑。 通过校验后按 array_num 走 switch:0~3 对应四个数据数组,4 对应 color_indexes,分别调 ArrayGetAsSeries 返回布尔值。默认分支 break 后落到底部 return false,意味着传了非法 array_num 不会报错只会静默给 false。 DataTotal 的同名校验逻辑完全一致,区别在 switch 里返回的是对应数组的 .Size()。想确认某缓冲实际装了多少根 K 线数据,调它比读终端窗口更精确,尤其在离线回测里缓冲大小可能和图表可见根数不一致。 下面两段是可直接贴进 MT5 类实现里验证的原逻辑:

MQL5 / C++
class=class="str">"cmt">//| Returns the timeseries flag of the given buffer                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CIndMSTF::IsSeries(const class="type">uint buffer_num,const class="type">uint array_num) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and class="kw">return &class="macro">#x27;false&class="macro">#x27;
   if(buffer_num>this.BuffersTotal()-class="num">1)
     {
       class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
       ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
       class="kw">return false;
     }
class=class="str">"cmt">//--- Return the timeseries flag of the array of the specified buffer
   class="kw">switch(array_num)
     {
      case class="num">0 : class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array0);
      case class="num">1 : class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array1);
      case class="num">2 : class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array2);
      case class="num">3 : class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array3);
      case class="num">4 : class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].color_indexes);
      class="kw">default: break;
     }
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the amount of data in the specified buffer                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">uint CIndMSTF::DataTotal(const class="type">uint buffer_num,const class="type">uint array_num) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to method and, if number is incorrect, print message to log and class="kw">return zero
   if(buffer_num>this.BuffersTotal()-class="num">1)
     {
       class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
       ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
       class="kw">return class="num">0;
     }
class=class="str">"cmt">//--- Return the array size of the specified buffer
   class="kw">switch(array_num)
     {
      case class="num">0 : class="kw">return this.m_buffers[buffer_num].array0.Size();
      case class="num">1 : class="kw">return this.m_buffers[buffer_num].array1.Size();
      case class="num">2 : class="kw">return this.m_buffers[buffer_num].array2.Size();

「多周期指标基类的缓冲区与色彩计数」

在自研多时间框架指标基类里,缓冲区元素个数与配色数量都靠统一接口暴露。上面这段 switch 返回了 array3 与 color_indexes 的尺寸,分别对应 case 3、case 4,其余情况走 default 并返回 0,说明未定义的 buffer 类型一律按空处理。 ColorsTotal 方法先校验 buffer_num 是否越界:若大于 BuffersTotal()-1 就打印含 __FUNCTION__ 的报错并返回 0;合法时直接返回该 buffer 的 clrs.Size()。注意当 BuffersTotal() 为 1 时,报错提示里的合法范围写成 "0",多缓冲时才显示 "0 - N-1"。 随后定义的 CIndAC 类继承该基类,构造函数固定传 IND_AC 与缓冲区数 1。它用 current 标记是否当前图表品种周期,非当前则在参数串里拼入 symbol,timeframe;m_digits 取 Digits()+2,即报价精度再加两位,适合 AC 这种小数级摆荡指标。 实际在 MT5 里继承这套结构时,可复制 ColorsTotal 的越界保护逻辑,避免自定义指标在非法 buffer 索引上静默崩掉。外汇与贵金属品种点差跳变频繁,这类底层计数错误可能让面板显示错位,复盘前务必手动传一次越界值验证日志输出。

MQL5 / C++
case class="num">3 : class="kw">return this.m_buffers[buffer_num].array3.Size();
   case class="num">4 : class="kw">return this.m_buffers[buffer_num].color_indexes.Size();
   class="kw">default: break;
    }
  class="kw">return class="num">0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the number of colors set for the buffer                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">uint CIndMSTF::ColorsTotal(const class="type">uint buffer_num) const
  {
class=class="str">"cmt">//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and class="kw">return zero
  if(buffer_num>this.BuffersTotal()-class="num">1)
    {
      class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
      ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      class="kw">return class="num">0;
    }
class=class="str">"cmt">//--- Return the size of the class="type">color array of the specified buffer
  class="kw">return this.m_buffers[buffer_num].clrs.Size();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Accelerator Oscillator indicator class                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndAC : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,class="num">1,symbol,timeframe)
    {
      class=class="str">"cmt">//--- Create description of parameters
      class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
      class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
      class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("AC");
      this.SetDescription("Accelerator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      this.m_digits=::Digits()+class="num">2;
      class=class="str">"cmt">//--- Write description of line buffers
      this.SetBufferDescription(class="num">0,this.m_title);
      class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and number of the calculation part data buffer

A/D 指标类的缓冲区与参数装配

在 MQL5 里封装 Accumulation/Distribution 指标,核心是先给 0 号缓冲区定好彩色柱状图的绘制方式,再绑定两种状态色。下面这段构造函数片段把绿色(index 0)和红色(index 1)映射到同一颜色缓冲区,初始化默认取绿色。 参数数组只放一个 UINT 类型的 applied_volume,ArrayResize 返回 1 才继续,否则直接打印错误码。非当前图表品种或周期时,描述里会拼上 symbol,period 字符串,避免多实例混淆。 指标名固定写 "A/D",类目归到 IND_CATEGORY_VOLUME,Digits 设 0——因为 A/D 是累积量纲,不带小数。开 MT5 新建一个 CIndAD 实例,传 VOLUME_REAL 进去,就能在副图看到双色柱。外汇与贵金属杠杆高,A/D 背离仅作概率参考,不构成方向断定。

MQL5 / C++
this.SetBufferDrawType(class="num">0,DRAW_COLOR_HISTOGRAM,class="num">0);
class=class="str">"cmt">//--- Set two class="kw">default colors for the class="type">color buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrGreen);
this.SetBufferColorToIndex(class="num">0,class="num">1,clrRed);
class=class="str">"cmt">//--- Set the class="kw">default initializing class="type">color index
this.SetBufferInitColorIndex(class="num">0,class="num">0);
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Accumulation/Distribution indicator class                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndAD : class="kw">public CIndMSTF
 {
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
         const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// used volume
        ) : CIndMSTF(IND_AD,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
    {
     ::ZeroMemory(this.m_param);
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=applied_volume;
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class=class="str">"cmt">//--- Create description of parameters
  class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
  class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
  class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
  class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
  this.SetParameters(param);
  this.SetName("A/D");
  this.SetDescription("Accumulation/Distribution");
  this.m_title=this.Name()+this.Parameters();
  this.m_category=IND_CATEGORY_VOLUME;
  this.m_digits=class="num">0;
  class=class="str">"cmt">//--- Write description of line buffers
  this.SetBufferDescription(class="num">0,this.m_title);

◍ ADX 多周期封装里的缓冲区与周期兜底

在把 ADX 封装成多周期类时,构造函数先给参数数组留 1 个槽位,类型标成 TYPE_UINT,用来装 ADX 的计算周期。若调用方传的 adx_period 小于 1,代码会直接兜底成 14——这是 MT5 内置 ADX 的默认平滑周期,实盘里多数人懒得改也就是吃这套默认值。 缓冲区编号是硬约定:0 对应 MAIN_LINE(即 ADX 主线),1 是 PLUSDI_LINE,2 是 MINUSDI_LINE。SetBufferDescription 只给 0 号写了标题,说明主线对外显示名直接沿用「ADX+参数」拼接,DI 双线默认不单独挂名,想分开看就得自己补描述。 非当前图表的 symbol/timeframe 会被拼进参数串,比如「EURUSD,H1:14」,当前图就留空。m_digits 锁死 2 位小数,贵金属或外汇交叉盘加载后小数点位数统一按这个走,和报价精度无关。 下面这段是前文同系列里设置 0 号缓冲区画线样式与配色的片段,顺手拆一下: //--- 设置 0 号缓冲区的绘制类型:画线、关联计算缓冲 0 this.SetBufferDrawType(0,DRAW_LINE,0); //--- 给 0 号缓冲区索引 0 设默认色:浅海绿 this.SetBufferColorToIndex(0,0,clrLightSeaGreen); 改 clrLightSeaGreen 换色、把 DRAW_LINE 换成 DRAW_HISTOGRAM 就能把 ADX 主线从线变柱,开 MT5 新建指标套这个类验证最快。外汇和贵金属杠杆高,ADX 只度量趋势强度不判方向,信号失效时回撤可能超预期。

MQL5 / C++
class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);

「ADX 缓冲区的描述与配色落地」

在自定义 ADX(Wilder 版)指标类里,缓冲区 0、1、2 分别承载主线、+DI 与 -DI。先给后两个缓冲区写死标签,方便在 MT5 数据窗口里直接读出正负方向,而不是对着一串 unnamed buffer 猜。 绘制类型必须显式声明为 DRAW_LINE,否则即便计算了数值,副图也可能什么都不画。下面这段把三个缓冲区的线型与索引绑定到对应宏,主线用 MAIN_LINE,正负 DI 各自挂 PLUSDI_LINE / MINUSDI_LINE。 默认配色别用系统灰:主线给 LightSeaGreen,+DI 用 YellowGreen,-DI 用 Wheat,三色在暗色模板下对比清晰,肉眼扫盘面时不容易把 +DI 下穿 -DI 的拐点漏掉。外汇与贵金属波动剧烈,DI 交叉仅代表多空力量倾向变化,实际反转概率仍需结合结构确认。 构造函数里若 adx_period 传入小于 1,会回落到 14——这是 Wilder 原始设定的常用周期。ArrayResize 失败时在日志打 Error 码,开 MT5 跑这类封装类时,先盯一眼 Experts 标签里有没有 ArrayResize failed,能省掉一半“指标不显示”的排查时间。

MQL5 / C++
this.SetBufferDescription(PLUSDI_LINE,"+DI");
this.SetBufferDescription(MINUSDI_LINE,"-DI");

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0, class="num">1, class="num">2 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,MAIN_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,PLUSDI_LINE);
this.SetBufferDrawType(class="num">2,DRAW_LINE,MINUSDI_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0, class="num">1 and class="num">2
this.SetBufferColorToIndex(MAIN_LINE,class="num">0,clrLightSeaGreen);
this.SetBufferColorToIndex(PLUSDI_LINE,class="num">0,clrYellowGreen);
this.SetBufferColorToIndex(MINUSDI_LINE,class="num">0,clrWheat);

给 ADX Wilder 的三条线定好身份与配色

在自定义指标类的构造函数里,先把 ADX Wilder 的元信息钉死:名称、描述、标题拼接方式、归类到趋势类(IND_CATEGORY_TREND),价格精度锁在 2 位小数。这样 MT5 导航器里看到的就不是一堆 Buffer0/1/2,而是带语义的 "ADX Wilder(参数)"、"+DI"、"-DI"。 下面这段把三条线都设成 DRAW_LINE,并分别绑到 MAIN_LINE、PLUSDI_LINE、MINUSDI_LINE 三个枚举缓冲位。颜色上主线和正负 DI 用了 LightSeaGreen、YellowGreen、Wheat——如果你在暗色模板下觉得 +DI 太暗,直接改 clrYellowGreen 就能现场验证。 外汇与贵金属波动剧烈,指标仅描述历史动量结构,方向判断存在滞后,实盘须自担高风险。

MQL5 / C++
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("ADX Wilder");
   this.SetDescription("Average Directional Movement Index Wilder");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_TREND;
   this.m_digits=class="num">2;
class=class="str">"cmt">//--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers
   this.SetBufferDescription(MAIN_LINE,this.m_title);
   this.SetBufferDescription(PLUSDI_LINE,"+DI");
   this.SetBufferDescription(MINUSDI_LINE,"-DI");

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0, class="num">1, class="num">2 and the numbers of calculation part data buffers
   this.SetBufferDrawType(class="num">0,DRAW_LINE,MAIN_LINE);
   this.SetBufferDrawType(class="num">1,DRAW_LINE,PLUSDI_LINE);
   this.SetBufferDrawType(class="num">2,DRAW_LINE,MINUSDI_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0, class="num">1 and class="num">2
   this.SetBufferColorToIndex(MAIN_LINE,class="num">0,clrLightSeaGreen);
   this.SetBufferColorToIndex(PLUSDI_LINE,class="num">0,clrYellowGreen);
   this.SetBufferColorToIndex(MINUSDI_LINE,class="num">0,clrWheat);

◍ 鳄鱼指标构造里的参数兜底逻辑

在 MT5 自定义指标类里封装 Alligator,构造函数要把下颌、牙齿、嘴唇三条线的周期与偏移塞进 m_param 数组。代码里先 ArrayResize 到 8 个元素,失败就打印错误号,成功才继续填值。 填参时有个细节值得注意:jaw_period 小于 1 会自动回退成 13,teeth_period 回退 8,lips_period 回退 5。这正好对应鳄鱼指标默认的下颌 13 根 K 线、牙齿 8 根、嘴唇 5 根,偏移默认 0、平滑用 SMA、应用价 PRICE_MEDIAN。 缓冲区索引固定为 0=下颌、1=牙齿、2=嘴唇。你在 EA 里调参时若传了非法周期,指标不会崩,而是静默用默认值——回测里可能因此以为自己改了参数,实际跑的还是原版鳄鱼。外汇与贵金属波动剧烈,这类隐性兜底容易放大策略误判风险。

MQL5 / C++
const class="type">int lips_shift,                 class=class="str">"cmt">// horizontal shift of lips
const ENUM_MA_METHOD ma_method,       class=class="str">"cmt">// smoothing type
const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
) : CIndMSTF(IND_ALLIGATOR,class="num">3,symbol,timeframe)
 {
 class=class="str">"cmt">// Buffer indexes: class="num">0 - GATORJAW_LINE, class="num">1 - GATORTEETH_LINE, class="num">2 - GATORLIPS_LINE
 class=class="str">"cmt">//--- Set the size of the parameter array and fill it
 ::ResetLastError();
 if(::ArrayResize(this.m_param,class="num">8)==class="num">8)
   {
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- period for jaw line calculation
    this.m_param[class="num">0].type=TYPE_UINT;
    this.m_param[class="num">0].integer_value=(jaw_period<class="num">1 ? class="num">13 : jaw_period);
    class=class="str">"cmt">//--- horizontal shift of the jaw line
    this.m_param[class="num">1].type=TYPE_INT;
    this.m_param[class="num">1].integer_value=jaw_shift;
    class=class="str">"cmt">//--- period for teeth line calculation
    this.m_param[class="num">2].type=TYPE_UINT;
    this.m_param[class="num">2].integer_value=(teeth_period<class="num">1 ? class="num">8 : teeth_period);
    class=class="str">"cmt">//--- horizontal shift of teeth line
    this.m_param[class="num">3].type=TYPE_INT;
    this.m_param[class="num">3].integer_value=teeth_shift;
    class=class="str">"cmt">//--- period for lip line calculation
    this.m_param[class="num">4].type=TYPE_UINT;
    this.m_param[class="num">4].integer_value=(lips_period<class="num">1 ? class="num">5 : lips_period);
    class=class="str">"cmt">//--- horizontal shift of lips line
    this.m_param[class="num">5].type=TYPE_INT;
    this.m_param[class="num">5].integer_value=lips_shift;
    class=class="str">"cmt">//--- smoothing type
    this.m_param[class="num">6].type=TYPE_UINT;
    this.m_param[class="num">6].integer_value=ma_method;
    class=class="str">"cmt">//--- price type or handle
    this.m_param[class="num">7].type=TYPE_UINT;
    this.m_param[class="num">7].integer_value=applied_price;
    }
 else
    ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters

「鳄鱼指标的非当前图表参数拼接与画线着色」

在封装鳄鱼指标时,若实例绑定的不是当前图表品种或周期,就得把 symbol 和 timeframe 拼进参数串,否则 MT5 子窗口会显示成另一根线的同名副本。上面代码用 current 变量判断是否属于当前上下文,非当前时才走 StringFormat("%s,%s") 把品种与周期描述塞进 param。 参数串最终形如 (EURUSD,H1:13,8,5) 或当前图下的 (13,8,5),jaw/teeth/lips 三个周期直接以 %lu 无符号长整型写入。SetBufferDescription 给三条线缓冲分别打上 Jaws/Teeth/Lips 标签,并带周期数,方便在数据窗口里一眼区分。 画线类型统一设成 DRAW_LINE,缓冲 0/1/2 映射 GATORJAW_LINE、GATORTEETH_LINE、GATORLIPS_LINE;默认色固定为蓝、红、绿。你在 MT5 里新建自定义鳄鱼类指标,若忘了 SetBufferColorToIndex 这步,三条线会全黑,肉眼很难分辨唇齿颚的错位关系。外汇与贵金属波动剧烈,这类均线错位仅作趋势休眠/觉醒的概率参考,不能直接当入场信号。

MQL5 / C++
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
this.SetParameters(param);
this.SetName("Alligator");
this.SetDescription("Alligator");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_WILLIAMS;
class=class="str">"cmt">//--- Write descriptions of GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE line buffers
this.SetBufferDescription(GATORJAW_LINE,::StringFormat("Jaws(%s%lu)", (current ? "" : symbol_period+":"),jaw_period));
this.SetBufferDescription(GATORTEETH_LINE,::StringFormat("Teeth(%s%lu)",(current ? "" : symbol_period+":"),teeth_period));
this.SetBufferDescription(GATORLIPS_LINE,::StringFormat("Lips(%s%lu)", (current ? "" : symbol_period+":"),lips_period));
class=class="str">"cmt">//--- Write offsets to buffers GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE
this.SetBufferShift(GATORJAW_LINE,jaw_shift);
this.SetBufferShift(GATORTEETH_LINE,teeth_shift);
this.SetBufferShift(GATORLIPS_LINE,lips_shift);

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0, class="num">1, class="num">2 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,GATORJAW_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,GATORTEETH_LINE);
this.SetBufferDrawType(class="num">2,DRAW_LINE,GATORLIPS_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0, class="num">1 and class="num">2
this.SetBufferColorToIndex(GATORJAW_LINE,class="num">0,clrBlue);
this.SetBufferColorToIndex(GATORTEETH_LINE,class="num">0,clrRed);
this.SetBufferColorToIndex(GATORLIPS_LINE,class="num">0,clrLime);

AMA 多周期封装里的参数兜底逻辑

在把自适应均线(AMA)塞进多周期框架时,构造函数的参数数组长度是写死的 5 个槽位:AMA 周期、快线周期、慢线周期、水平偏移、应用价格。代码里先用 ArrayResize 把 m_param 扩到 5,若返回值不等于 5 就直接 PrintFormat 报错并退出,避免后续越界写内存。 三个周期参数都有下限保护:ama_period 小于 1 时强制填 9,fast_ma_period 小于 1 时填 2,slow_ma_period 小于 1 时填 30。这意味着你在 EA 里传了非法值,指标也不会崩,而是回退到一套经验默认值——AMA 9 / 快 2 / 慢 30 是经典 Perry Kaufman 原版参数组合。 符号与周期的描述处理也值得注意:只有当指标实例绑定的 symbol 和 timeframe 与当前图表不一致时,才会在参数描述里追加 "symbol,period" 字符串。你可以把这层逻辑直接抄进自己的 CIndMSTF 子类,开 MT5 挂个不同周期窗口验证描述字段差异。外汇与贵金属杠杆高,参数默认值仅影响计算,不预示任何方向。

MQL5 / C++
const class="type">int ama_period,                 class=class="str">"cmt">// AMA period
const class="type">int fast_ma_period,             class=class="str">"cmt">// fast MA period
const class="type">int slow_ma_period,             class=class="str">"cmt">// slow MA period
const class="type">int ama_shift,                  class=class="str">"cmt">// horizontal shift of the indicator
const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
) : CIndMSTF(IND_AMA,class="num">1,symbol,timeframe)
 {
 class=class="str">"cmt">//--- Set the size of the parameter array and fill it
 ::ResetLastError();
 if(::ArrayResize(this.m_param,class="num">5)==class="num">5)
  {
   ::ZeroMemory(this.m_param);
   class=class="str">"cmt">//--- AMA period
   this.m_param[class="num">0].type=TYPE_UINT;
   this.m_param[class="num">0].integer_value=(ama_period<class="num">1 ? class="num">9 : ama_period);
   class=class="str">"cmt">//--- fast MA period
   this.m_param[class="num">1].type=TYPE_UINT;
   this.m_param[class="num">1].integer_value=(fast_ma_period<class="num">1 ? class="num">2 : fast_ma_period);
   class=class="str">"cmt">//--- slow MA period
   this.m_param[class="num">2].type=TYPE_UINT;
   this.m_param[class="num">2].integer_value=(slow_ma_period<class="num">1 ? class="num">30 : slow_ma_period);
   class=class="str">"cmt">//--- horizontal shift of the indicator
   this.m_param[class="num">3].type=TYPE_INT;
   this.m_param[class="num">3].integer_value=ama_shift;
   class=class="str">"cmt">//--- price type or handle
   this.m_param[class="num">4].type=TYPE_UINT;
   this.m_param[class="num">4].integer_value=applied_price;
   }
 else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));

◍ 在自定义指标类里塞进参数与画线设定

把 AMA 和 AO 封装成类之后,构造阶段就要把参数串、名称、类别和小数位一次性写进对象,否则 MT5 导航树里只会显示一团看不出内容的指标。下面两段是实际落地的写法,直接抄进你自己的 CiXXX 派生类就能用。 AMA 这边先用 StringFormat 拼出 (符号周期,ama周期,快MA,慢MA) 这样的参数字符串,current 为真时省略符号周期,避免当前图表重复显示。Digits()+1 把精度多留一位,主要为了让自适应均线在交叉读值时少受四舍五入干扰。 AO 属于 Williams 类别,构造时若不是当前图表的品种或周期,就用 (%s,%s) 把 symbol_period 包进参数;画线部分把 buffer 0 设成 DRAW_LINE,颜色绑定 clrRed,这样在副图 oscillator 上红线一眼可辨。外汇与贵金属波动剧烈,这类封装仅解决显示与调用规范,信号真假仍要在历史回放里验证。

MQL5 / C++
class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),ama_period,fast_ma_period,slow_ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("AMA");
this.SetDescription("Adaptive Moving Average");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_TREND;
this.m_digits=::Digits()+class="num">1;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);
class=class="str">"cmt">//--- Write shift to buffer class="num">0
this.SetBufferShift(class="num">0,ama_shift);
class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);

class CIndAO : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
 CIndAO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AO,class="num">1,symbol,timeframe)
 {
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
 class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
 class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
 this.SetParameters(param);
 this.SetName("AO");
 this.SetDescription("Awesome Oscillator");
 this.m_title=this.Name()+this.Parameters();
 this.m_category=IND_CATEGORY_WILLIAMS;
 this.m_digits=::Digits()+class="num">1;
 class=class="str">"cmt">//--- Write description of line buffers
 this.SetBufferDescription(class="num">0,this.m_title);
 class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part

「ATR 封装里的参数兜底与双色直方图」

在自定义指标基类的构造函数里,先用 SetBufferDrawType(0,DRAW_COLOR_HISTOGRAM,0) 把 0 号缓冲设为彩色直方图,不指定线宽由样式接管。紧接着给该颜色缓冲预置两组默认色:索引 0 映射 clrGreen、索引 1 映射 clrRed,并用 SetBufferInitColorIndex(0,0) 指定初始绘制取绿色索引。 CIndATR 继承多周期框架基类,构造时先 ArrayResize(m_param,1) 申请 1 个参数槽,失败就 PrintFormat 打出 ArrayResize failed 与 GetLastError 码。ma_period 若小于 1 会被强制回退到 14——这是 ATR 最通用的默认周期,外汇与贵金属波动测算中常被用作止损距离基准,但杠杆品种风险高,实际周期需按品种波动率自测。 参数描述拼接时判断是否为当前图 symbol 与 period:非当前图表才把 "符号,周期" 前缀进标题。最后 SetName("ATR")、SetDescription("Average True Range")、归类 IND_CATEGORY_OSCILLATOR,并把 0 号缓冲描述写成 "名称+参数",开 MT5 挂非主图周期即可看到标题自动带符号周期后缀。

MQL5 / C++
this.SetBufferDrawType(class="num">0,DRAW_COLOR_HISTOGRAM,class="num">0);
class=class="str">"cmt">//--- Set two class="kw">default colors for the class="type">color buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrGreen);
this.SetBufferColorToIndex(class="num">0,class="num">1,clrRed);
class=class="str">"cmt">//--- Set the class="kw">default initializing class="type">color index
this.SetBufferInitColorIndex(class="num">0,class="num">0);
}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Average True Range indicator class                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndATR : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndATR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
          const class="type">int ma_period       class=class="str">"cmt">// averaging period
          ) : CIndMSTF(IND_ATR,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
    {
     ::ZeroMemory(this.m_param);
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class=class="str">"cmt">//--- Create description of parameters
  class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
  class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
  class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
  class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
  this.SetParameters(param);
  this.SetName("ATR");
  this.SetDescription("Average True Range");
  this.m_title=this.Name()+this.Parameters();
  this.m_category=IND_CATEGORY_OSCILLATOR;
  class=class="str">"cmt">//--- Write description of line buffers
  this.SetBufferDescription(class="num">0,this.m_title);

熊力指标类的缓冲区与参数封装

在自定义多周期指标框架里,Bears Power 被封装成 CIndBears 类,继承自 CIndMSTF。构造函数只接收一个 ma_period 参数,用来决定熊力线的均线周期。 参数数组长度固定为 1,元素类型标成 TYPE_UINT。若传入的 ma_period 小于 1,代码会强制回退到 13——这意味着你漏填周期时,指标默认按 13 根 K 线平滑,而不是报错退出。 非当前图表品种或周期时,标题会自动拼上 symbol,period 前缀;m_digits 取当前品种 Digits()+1,保证振荡器小数位比价格多一位,避免 EURUSD 这类 5 位平台显示挤在一起。 缓冲区 0 的描述直接等于指标标题,绘制样式与配色在前述父类片段中设定为 DRAW_LINE 与 clrLightSeaGreen,开 MT5 挂上后应是浅海绿色的连续线。

MQL5 / C++
   class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);
   }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Bears Power indicator class                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndBears : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndBears(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
             const class="type">int ma_period       class=class="str">"cmt">// averaging period
             ) : CIndMSTF(IND_BEARS,class="num">1,symbol,timeframe)
   {
   class=class="str">"cmt">//--- Set the size of the parameter array and fill it
   ::ResetLastError();
   if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
     {
      ::ZeroMemory(this.m_param);
      this.m_param[class="num">0].type=TYPE_UINT;
      this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">13 : ma_period);
     }
   else
      ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("Bears");
   this.SetDescription("Bears Power");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_OSCILLATOR;
   this.m_digits=::Digits()+class="num">1;
   class=class="str">"cmt">//--- Write description of line buffers
   this.SetBufferDescription(class="num">0,this.m_title);

◍ Bulls Power 类的缓冲区与参数初始化

在自封装的多周期指标框架里,Bulls Power 被拆成一个独立类 CIndBulls,继承自 CIndMSTF。构造函数只吃三个量:品种、周期、均线周期 ma_period,底层通过 IND_BULLS 和缓冲区数量 1 交给父类完成句柄申请。 参数数组先用 ArrayResize 撑到长度 1,失败就 PrintFormat 报错误码;成功则 ZeroMemory 清空,把 m_param[0] 标成 TYPE_UINT,并且做了兜底——ma_period 小于 1 时自动填 13,这点和 MT5 内置 Bulls Power 默认 13 根均线一致。 品种和周期若不是当前图表,描述里会拼上 "SYMBOL,PERIOD" 前缀,否则留空;最终标题由 Name() 加 Parameters() 拼出,类别锁死在 IND_CATEGORY_OSCILLATOR,小数点位数取 Digits()+1。 缓冲区 0 的描述直接写成指标标题,后续绘制样式和颜色在别的成员函数里接管,例如下面这段把 0 号缓冲画成直方图、默认银色:

MQL5 / C++
this.SetBufferDrawType(class="num">0,DRAW_HISTOGRAM,class="num">0);
this.SetBufferColorToIndex(class="num">0,class="num">0,clrSilver);

「布林带类的缓冲区与参数装配」

在自定义指标封装里,缓冲区 0 的绘制方式常被设成直方图。下面这段把 buffer 0 指定为 DRAW_HISTOGRAM,并把默认色绑到 clrSilver,适合做能量柱或差值可视化,而不是标准布林中轨线。 //--- Set drawing style for buffer 0 and the data buffer number of the calculation part this.SetBufferDrawType(0,DRAW_HISTOGRAM,0); //--- Set the default color for buffer 0 this.SetBufferColorToIndex(0,0,clrSilver); 布林带类 CIndBands 继承多周期框架基类,构造函数要求传入 symbol、timeframe、bands_period、bands_shift、deviation、applied_price 六项。基类初始化时声明 3 个缓冲区:0 为中轨 BASE_LINE,1 为上轨 UPPER_BAND,2 为下轨 LOWER_BAND。 参数数组用 ArrayResize 扩到 4 个元素,失败会打印 Error 代码。period 若小于 1 则回退到 20——这是 MT5 内置布林带的经典默认值,deviation 以双精度存标准差倍数,applied_price 以无符号整型存价格类型或句柄。 「别把正态当圣经」 标准差倍数直接决定通道宽度,贵金属 XAUUSD 在亚盘低波动段常贴中轨走,deviation 设 2 可能比设 1.5 更少假突破信号,但趋势市会滞后,开 MT5 把参数改成 1.5 对比回放便知。外汇与贵金属杠杆高,通道信号仅作概率参考。

MQL5 / C++
class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_HISTOGRAM,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrSilver);
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Bollinger Bands® indicator class                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndBands : class="kw">public CIndMSTF
 {
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndBands(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
            const class="type">int bands_period,       class=class="str">"cmt">// central line calculation period
            const class="type">int bands_shift,        class=class="str">"cmt">// horizontal shift of the indicator
            const class="type">class="kw">double deviation,       class=class="str">"cmt">// number of standard deviations
            const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
            ) : CIndMSTF(IND_BANDS,class="num">3,symbol,timeframe)
  {
  class=class="str">"cmt">// Buffer indexes: class="num">0 - BASE_LINE, class="num">1 - UPPER_BAND, class="num">2 - LOWER_BAND
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
    {
     ::ZeroMemory(this.m_param);
     class=class="str">"cmt">//--- central line calculation period
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(bands_period<class="num">1 ? class="num">20 : bands_period);
     class=class="str">"cmt">//--- horizontal shift of the indicator
     this.m_param[class="num">1].type=TYPE_INT;
     this.m_param[class="num">1].integer_value=bands_shift;
     class=class="str">"cmt">//--- number of standard deviations
     this.m_param[class="num">2].type=TYPE_DOUBLE;
     this.m_param[class="num">2].double_value=deviation;
     class=class="str">"cmt">//--- price type or handle
     this.m_param[class="num">3].type=TYPE_UINT;
     this.m_param[class="num">3].integer_value=applied_price;
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class=class="str">"cmt">//--- Create description of parameters

布林带缓冲区的绘制与配色设定

在自定义布林带指标初始化时,若当前图表并非加载品种或周期,代码会把 symbol_period 拼进参数串,例如 EURUSD,H1:20 这种格式,方便多周期同屏时区分来源。 下面这段是缓冲区绘制属性落地的核心:三个缓冲分别绑定 BASE_LINE、UPPER_BAND、LOWER_BAND,全部用 DRAW_LINE 画线,偏移量统一取 bands_shift。

MQL5 / C++
class=class="str">"cmt">//--- Set drawing style for buffers class="num">0, class="num">1, class="num">2 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,BASE_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,UPPER_BAND);
this.SetBufferDrawType(class="num">2,DRAW_LINE,LOWER_BAND);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0, class="num">1 and class="num">2
this.SetBufferColorToIndex(BASE_LINE,class="num">0,clrMediumSeaGreen);
this.SetBufferColorToIndex(UPPER_BAND,class="num">0,clrMediumSeaGreen);
this.SetBufferColorToIndex(LOWER_BAND,class="num">0,clrMediumSeaGreen);
逐行拆解:前三行给索引 0/1/2 的缓冲区指定画线类型并映射到中轨、上轨、下轨宏;后三行把三个缓冲在颜色索引 0 处的默认色设为 clrMediumSeaGreen,也就是中绿。 开 MT5 随便挂一个自定义布林类指标,若发现轨线不显示,优先查 SetBufferDrawType 的宏有没有映射错,以及 bands_shift 是否把线推到了可视区外。外汇与贵金属杠杆高,指标仅作概率参考,实际进出场仍需结合风控。

MQL5 / C++
class=class="str">"cmt">//--- Set drawing style for buffers class="num">0, class="num">1, class="num">2 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,BASE_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,UPPER_BAND);
this.SetBufferDrawType(class="num">2,DRAW_LINE,LOWER_BAND);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0, class="num">1 and class="num">2
this.SetBufferColorToIndex(BASE_LINE,class="num">0,clrMediumSeaGreen);
this.SetBufferColorToIndex(UPPER_BAND,class="num">0,clrMediumSeaGreen);
this.SetBufferColorToIndex(LOWER_BAND,class="num">0,clrMediumSeaGreen);

◍ 给 CCI 多周期封装补上绘制属性

上面这段构造函数收尾,做的是把 CCI 指标在自定义多周期框架类里真正“可见化”。前一半已经在填参数数组、写名称描述,后一半重点在缓冲区的画线配置。 被高亮的那几行负责两件事:把 0 号缓冲区设为连线样式(DRAW_LINE),并给它的 0 号颜色索引指定 clrLightSeaGreen。若不显式调 SetBufferDrawType,MT5 里该缓冲可能默认不绘制,你在子窗口看不到 CCI 曲线。 参数兜底也值得注意:ma_period 小于 1 时强制 14,小于 2 时强制 2,否则用传入值。外汇与贵金属波动剧烈,CCI 周期设太小易产生假穿越,实盘前建议在 MT5 用 14 与 2 两档各跑一遍验证噪声差异。

MQL5 / C++
const class="type">int ma_period,                    class=class="str">"cmt">// averaging period
      const ENUM_APPLIED_PRICE applied_price  class=class="str">"cmt">// price type or handle
      ) : CIndMSTF(IND_CCI,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
    {
     ::ZeroMemory(this.m_param);
     class=class="str">"cmt">//--- averaging period
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period);
     class=class="str">"cmt">//--- price type or handle
     this.m_param[class="num">1].type=TYPE_UINT;
     this.m_param[class="num">1].integer_value=applied_price;
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class=class="str">"cmt">//--- Create description of parameters
  class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
  class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
  class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
  class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
  this.SetParameters(param);
  this.SetName("CCI");
  this.SetDescription("Commodity Channel Index");
  this.m_title=this.Name()+this.Parameters();
  this.m_category=IND_CATEGORY_OSCILLATOR;
  this.m_digits=class="num">2;
  class=class="str">"cmt">//--- Write description of line buffers
  this.SetBufferDescription(class="num">0,this.m_title);
  
  class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
  this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
  class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
  this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);
  }
};

「封装 Chaikin 振荡器的类构造细节」

在 MT5 里把 Chaikin Oscillator 封装成可复用类时,通常是继承多符号多周期基类 CIndMSTF,并在构造函数里把指标参数写进 m_param 数组。 构造函数接收 symbol、timeframe、fast_ma_period、slow_ma_period、ma_method、applied_volume 六个入参,初始化列表里调用 CIndMSTF(IND_CHAIKIN,1,symbol,timeframe) 完成基类挂载。 参数数组大小固定为 4:索引 0 存快线周期(小于 1 时兜底为 3),索引 1 存慢线周期(小于 1 时兜底为 10),索引 2 存平滑方式,索引 3 存成交量类型。若 ArrayResize 返回不等于 4,会打印 ArrayResize failed 及错误码,方便在 MT5 专家日志里直接排查。 描述串会判断是否为当前图表:非当前品种或周期时,把 symbol 与周期拼进参数文本,例如 "EURUSD,H1:10,3",这样在指标列表里能一眼区分不同实例。

MQL5 / C++
class=class="str">"cmt">//| Chaikin Oscillator indicator class                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndCHO : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndCHO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const class="type">int fast_ma_period,                class=class="str">"cmt">// fast period
           const class="type">int slow_ma_period,                class=class="str">"cmt">// slow period
           const ENUM_MA_METHOD ma_method,          class=class="str">"cmt">// smoothing type
           const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// used volume
           ) : CIndMSTF(IND_CHAIKIN,class="num">1,symbol,timeframe)
     {
     class=class="str">"cmt">//--- Set the size of the parameter array and fill it
     ::ResetLastError();
     if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
       {
        ::ZeroMemory(this.m_param);
        class=class="str">"cmt">//--- fast period
        this.m_param[class="num">0].type=TYPE_UINT;
        this.m_param[class="num">0].integer_value=(fast_ma_period<class="num">1 ? class="num">3 : fast_ma_period);
        class=class="str">"cmt">//--- slow period
        this.m_param[class="num">1].type=TYPE_UINT;
        this.m_param[class="num">1].integer_value=(slow_ma_period<class="num">1 ? class="num">10 : slow_ma_period);
        class=class="str">"cmt">//--- smoothing type
        this.m_param[class="num">2].type=TYPE_UINT;
        this.m_param[class="num">2].integer_value=ma_method;
        class=class="str">"cmt">//--- used volume
        this.m_param[class="num">3].type=TYPE_UINT;
        this.m_param[class="num">3].integer_value=applied_volume;
       }
     else
        ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
     class=class="str">"cmt">//--- Create description of parameters
     class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
     class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
     class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
     class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),slow_ma_period,fast_ma_period);

给 CHO 与 DEMA 套上指标外壳

在 MT5 自建指标类里,构造函数前半段干的是「登记户口」的活:把参数、名称、分类、精度写进对象内部,后续图表才认得它。下面这段 CHO(Chaikin Oscillator)的初始化就典型——名称写死为 "CHO",分类归到 IND_CATEGORY_OSCILLATOR,小数位 m_digits 直接设 0,因为震荡量本身不是整数价格。 缓冲区也要单独交代。buffer 0 用 DRAW_LINE 画成线,颜色绑了 clrLightSeaGreen,标题由 Name() 拼 Parameters() 自动生成,不用手敲。这样在 EURUSD 的 H1 上加载,副图里就是一条浅绿色的 CHO 线,默认不带小数。 DEMA 类走的是另一套参数登记逻辑。它用 ArrayResize 把 m_param 扩到 3 个槽位,分别塞周期、偏移、应用价格;若 ma_period 小于 1,代码会兜底填 14——这意味着你传参手滑填了 0,实际跑的还是 14 期双指数平滑。外汇与贵金属波动剧烈,DEMA 周期设太短可能频繁假突破,实盘前建议在策略测试器里先跑一遍。 这种写法把「指标元信息」和「算法计算」拆开,改个颜色或分类不用动核心公式。你打开 MT5 的 MetaEditor,照着把 SetBufferColorToIndex(0,0,clrLightSeaGreen) 换成 clrRed,重编译就能直观比对视觉辨识度。

MQL5 / C++
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("CHO");
   this.SetDescription("Chaikin Oscillator");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_OSCILLATOR;
   this.m_digits=class="num">0;
class=class="str">"cmt">//--- Write description of line buffers
   this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);

class CIndDEMA : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndDEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
            const class="type">int ma_period,              class=class="str">"cmt">// averaging period
            const class="type">int ma_shift,               class=class="str">"cmt">// horizontal indicator shift
            const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
            ) : CIndMSTF(IND_DEMA,class="num">1,symbol,timeframe)
     {
class=class="str">"cmt">//--- Set the size of the parameter array and fill it
     ::ResetLastError();
     if(::ArrayResize(this.m_param,class="num">3)==class="num">3)
       {
        ::ZeroMemory(this.m_param);
        class=class="str">"cmt">//--- averaging period
        this.m_param[class="num">0].type=TYPE_UINT;
        this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
        class=class="str">"cmt">//--- horizontal shift of the indicator
        this.m_param[class="num">1].type=TYPE_INT;
        this.m_param[class="num">1].integer_value=ma_shift;
        class=class="str">"cmt">//--- price type or handle
        this.m_param[class="num">2].type=TYPE_UINT;
        this.m_param[class="num">2].integer_value=applied_price;
       }
     else

◍ 给自封装指标挂上身份与画线样式

在自定义指标类的初始化尾部,先把参数描述拼出来。若当前图表不是指标绑定的品种或周期,就把 symbol,period 追加进 param 字符串,否则留空;ma_period 以无符号长整型接在括号里,最终形如 (EURUSD,H1:21) 或 (21)。 接着用 SetParameters、SetName、SetDescription 把 DEMA 的名称与说明写进对象,标题由 Name()+Parameters() 拼成,类别锁死 IND_CATEGORY_TREND,小数位在 Digits() 基础上 +1,避免均价类数值跳动时显示糊成一团。 画线配置只动 buffer 0:SetBufferDrawType(0,DRAW_LINE,0) 指定为主线绘制,SetBufferColorToIndex(0,0,clrRed) 把默认色设成红。下面这段是原文里高亮的核心两行,直接决定 MT5 里这条线看不看得见。 另一头 DeMarker 类构造时先 ResetLastError,再对 m_param 做 ArrayResize 到 1,成功才进分支填 TYPE_UINT 的周期参数;外汇与贵金属杠杆高,这类封装若 resize 失败没拦住,后面算出的 DeM 值可能整段错位,开 MT5 跑前务必确认错误码分支有日志。

MQL5 / C++
      class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
      this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
      class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
      this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);

「DeMarker 初始化里的参数兜底与画线设定」

在自定义指标类的构造收尾阶段,MA 周期参数会被做一次下限兜底:若传入的 ma_period 小于 1,则强制写成 14,否则用原值。这一行直接决定了后续缓冲区计算的基准长度,改这里比在外部传参更不容易踩空。 若数组扩容失败,代码走 else 分支用 PrintFormat 打出函数名与 GetLastError 的错误码,方便在 MT5 Experts 日志里定位内存或句柄问题。非当前图表品种或周期时,symbol_period 会拼接成 "SYMBOL,PERIOD" 形态,并追加到参数描述串里,使多周期实例的标题可区分。 DeMarker 被归到 IND_CATEGORY_OSCILLATOR,m_digits 固定为 3,缓冲区 0 设成 DRAW_LINE 且默认色 clrLightSeaGreen。开 MT5 新建指标挂 DeM 时,若看不到浅海绿色线,先查 SetBufferDrawType(0,DRAW_LINE,0) 与颜色索引是否被覆盖。外汇与贵金属波动剧烈,指标仅作概率参考,实盘须控仓。

MQL5 / C++
this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
}
else
  ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("DeM");
this.SetDescription("DeMarker");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">3;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);
class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);

信封指标参数数组的初始化细节

在 MT5 自定义指标封装里,Envelopes 的构造逻辑依赖一个长度为 5 的 m_param 数组来向底层 CIndMSTF 传递配置。若 ma_period 传入小于 1,代码会强制回退到 14 这一默认中轨周期,这是不少自行封装库容易忽略的兜底分支。 数组下标 0 到 4 分别承载中轨周期、水平偏移、平滑方式、价格类型与包络偏离度;其中偏离度用 TYPE_DOUBLE 存储,其余整型参数用 TYPE_UINT 或 TYPE_INT。若 ArrayResize 未能返回预期长度 5,会直接打印错误码并放弃填充,避免后续取到空参数。 非当前图表品种或周期时,描述字符串会拼接 "symbol,timeframe" 前缀,否则留空;m_digits 取系统 Digits() 加 1,意味着价格精度展示会比报价多一位。外汇与贵金属波动剧烈,这类封装仅用于技术观测,实盘信号出现偏差的概率不低,须自行在策略测试器验证。

MQL5 / C++
const class="type">class="kw">double deviation                      class=class="str">"cmt">// deviation of envelope borders from the middle line
) : CIndMSTF(IND_ENVELOPES,class="num">2,symbol,timeframe)
 {
 class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_LINE, class="num">1 - LOWER_LINE
 class=class="str">"cmt">//--- Set the size of the parameter array and fill it
 ::ResetLastError();
 if(::ArrayResize(this.m_param,class="num">5)==class="num">5)
   {
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- central line calculation period
    this.m_param[class="num">0].type=TYPE_UINT;
    this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
    class=class="str">"cmt">//--- horizontal shift of the indicator
    this.m_param[class="num">1].type=TYPE_INT;
    this.m_param[class="num">1].integer_value=ma_shift;
    class=class="str">"cmt">//--- smoothing type
    this.m_param[class="num">2].type=TYPE_UINT;
    this.m_param[class="num">2].integer_value=ma_method;
    class=class="str">"cmt">//--- price type or handle
    this.m_param[class="num">3].type=TYPE_UINT;
    this.m_param[class="num">3].integer_value=applied_price;
    class=class="str">"cmt">//--- deviation of envelope borders from the muddle line
    this.m_param[class="num">4].type=TYPE_DOUBLE;
    this.m_param[class="num">4].double_value=deviation;
    }
 else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
 class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
 class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
 this.SetParameters(param);
 this.SetName("Envelopes");
 this.SetDescription("Envelopes");
 this.m_title=this.Name()+this.Parameters();
 this.m_category=IND_CATEGORY_TREND;
 this.m_digits=::Digits()+class="num">1;

◍ 给通道线挂上名字和颜色

在自定义指标类里,缓冲区不是画出来就完了,先得让 MT5 知道每条线叫什么。SetBufferDescription 把 UPPER_LINE 和 LOWER_LINE 两个宏对应的缓冲分别标注成标题加 ' Upper' / ' Lower',图表上悬停就能区分上轨和下轨。 紧接着 SetBufferShift(0, ma_shift) 和 SetBufferShift(1, ma_shift) 给 0、1 号缓冲统一写入水平偏移量,这个 ma_shift 直接决定通道线相对 K 线是右移还是左移,实盘里常设 0,回测可视化时可试 1~2 看错位效果。 真正的绘制设定在后面几行:SetBufferDrawType(0, DRAW_LINE, UPPER_LINE) 和 SetBufferDrawType(1, DRAW_LINE, LOWER_LINE) 明确两条都是连续线型;再用 SetBufferColorToIndex 把上轨锁成 clrBlue、下轨锁成 clrRed。外汇与贵金属波动剧烈,通道颜色只是辅助,任何突破信号都只是概率倾向,请先在策略测试器跑一遍再上真仓。 Force Index 类构造里顺手展示了参数数组的初始化:ArrayResize(m_param, 3) 成功后才填三项是周期、平滑法、成交量类型;其中周期若小于 1 会被强制修正为 13,这是代码里的保底逻辑,不是建议值。

MQL5 / C++
class=class="str">"cmt">//--- Description of UPPER_LINE and LOWER_LINE line buffers
this.SetBufferDescription(UPPER_LINE,this.m_title+" Upper");
this.SetBufferDescription(LOWER_LINE,this.m_title+" Lower");
class=class="str">"cmt">//--- Write shift to buffers
this.SetBufferShift(class="num">0,ma_shift);
this.SetBufferShift(class="num">1,ma_shift);

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0 and class="num">1 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,UPPER_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,LOWER_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0 and class="num">1
this.SetBufferColorToIndex(UPPER_LINE,class="num">0,clrBlue);
this.SetBufferColorToIndex(LOWER_LINE,class="num">0,clrRed);

「给力指数和分形指标填元数据」

在自定义指标类里,构造函数后半段主要负责把运行时参数和显示属性写进对象。先看 Force Index 这一段:若数组扩容失败会打印函数名和错误码,便于在 MT5 Experts 日志里定位。 判断当前图:用 this.Symbol()==::Symbol() 且 this.Timeframe()==::Period() 得到 bool current。非当前品种或周期时,symbol_period 会拼成 "EURUSD,H1" 这类字符串,否则留空,param 再决定要不要加冒号和均线周期,例如 "(EURUSD,H1:14)"。 接着调 SetName("Force")、SetDescription("Force Index"),标题 m_title 就是 Name()+Parameters(),类别锁死 IND_CATEGORY_OSCILLATOR,m_digits=::Digits()+1 让小数位比报价多 1 位。缓冲区 0 设 DRAW_LINE、默认色 clrLightSeaGreen,SetBufferDescription(0,m_title) 让数据窗显示完整标题。 分形类 CIndFractals 构造时缓冲区定为 2(上 0 / 下 1),同样用 current 判断是否追加 "SYMBOL,TIMEFRAME" 到 param,最后 SetParameters 写进对象。你在 MT5 里改 clrLightSeaGreen 或 m_digits 偏移量,子图配色和精度会立刻变。

MQL5 / C++
else
  ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("Force");
this.SetDescription("Force Index");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=::Digits()+class="num">1;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrLightSeaGreen);
}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fractals indicator class                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndFractals : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
 CIndFractals(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,class="num">2,symbol,timeframe)
  {
  class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_LINE, class="num">1 - LOWER_LINE
  class=class="str">"cmt">//--- Create description of parameters
  class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
  class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
  class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
  class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
  this.SetParameters(param);

分形与FRAMA指标的类初始化细节

在 MT5 自定义指标封装里,CIndFractals 构造函数先把指标名和描述都设为 "Fractals",再把标题拼成 Name()+Parameters(),分类挂到 IND_CATEGORY_WILLIAMS 下。上下分形缓冲分别用 SetBufferDescription 打上 "Up" 和 "Down" 标签,方便在数据窗口里区分。 缓冲区 0 和 1 被指定为 DRAW_ARROW 箭头样式,分别对应 UPPER_LINE 与 LOWER_LINE 枚举位。默认颜色都填了 clrGray,也就是说你如果不手动改色,MT5 主图上的分形点就是灰箭头,容易和网格混淆。 紧接着的 CIndFrAMA 类走的是另一套逻辑:构造时先 ArrayResize(m_param,3) 把参数数组扩到 3 格,失败就跳过填充。周期参数做了防御——ma_period 小于 1 时强制落为 14,这是 FRAMA 的默认平滑窗口。 价格类型与水平偏移分别塞进 m_param[2] 和 m_param[1],类型标 TYPE_UINT / TYPE_INT。开 MT5 把这段抄进你的指标派生类,改 clrGray 为 clrRed/clrBlue,分形信号会直观很多;外汇与贵金属波动剧烈,灰箭头漏看可能导致追错突破,属高风险操作。

MQL5 / C++
this.SetName("Fractals");
this.SetDescription("Fractals");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_WILLIAMS;
class=class="str">"cmt">//--- Description of UPPER_LINE and LOWER_LINE line buffers
this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0 and class="num">1 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_ARROW,UPPER_LINE);
this.SetBufferDrawType(class="num">1,DRAW_ARROW,LOWER_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0 and class="num">1
this.SetBufferColorToIndex(UPPER_LINE,class="num">0,clrGray);
this.SetBufferColorToIndex(LOWER_LINE,class="num">0,clrGray);

class CIndFrAMA : class="kw">public CIndMSTF
{
class="kw">public:
 CIndFrAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const class="type">int ma_period,              class=class="str">"cmt">// averaging period
           const class="type">int ma_shift,               class=class="str">"cmt">// horizontal shift of the indicator
           const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
           ) : CIndMSTF(IND_FRAMA,class="num">1,symbol,timeframe)
 {
 class=class="str">"cmt">//--- Set the size of the parameter array and fill it
 ::ResetLastError();
 if(::ArrayResize(this.m_param,class="num">3)==class="num">3)
   {
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- averaging period
    this.m_param[class="num">0].type=TYPE_UINT;
    this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
    class=class="str">"cmt">//--- horizontal shift of the indicator
    this.m_param[class="num">1].type=TYPE_INT;
    this.m_param[class="num">1].integer_value=ma_shift;
    class=class="str">"cmt">//--- price type or handle
    this.m_param[class="num">2].type=TYPE_UINT;

◍ FRAMA 初始化里的缓冲区与描述绑定

在自定义指标类的构造收尾阶段,代码先判断当前图表符号与周期是否一致:一致时 symbol_period 留空,否则拼成 "符号,周期" 形式,再与 ma_period 组合成类似 "(EURUSD,H1:14)" 的参数串。这个串随后写进 SetParameters,并参与 m_title 的拼接,直接影响 MT5 数据窗口里指标的显示名。 缓冲区 0 被显式指定为 DRAW_LINE 线型,颜色索引 0 设为 clrBlue,同时用 SetBufferShift 把 ma_shift 水平偏移挂到该缓冲。m_digits 取当前品种 Digits() 再加 1,意味着 FRAMA 线默认多显示一位小数,黄金或外汇品种上需注意报价精度变化带来的视觉误差。 若 ArrayResize 失败,分支会走 PrintFormat 输出 "%s: ArrayResize failed. Error %ld" 并带 __FUNCTION__ 与 GetLastError,这类报错在加载跨周期实例时较常见,开 MT5 用非当前图表的 symbol/timeframe 构造就能复现。

MQL5 / C++
 this.m_param[class="num">2].integer_value=applied_price;
   }
   else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("FRAMA");
   this.SetDescription("Fractal Adaptive Moving Average");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_TREND;
   this.m_digits=::Digits()+class="num">1;
   class=class="str">"cmt">//--- Write description of line buffers
   this.SetBufferDescription(class="num">0,this.m_title);
   class=class="str">"cmt">//--- Write shift to buffer class="num">0
   this.SetBufferShift(class="num">0,ma_shift);
   class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrBlue);

「Gator 震荡器构造里的参数兜底逻辑」

在 MT5 自定义指标封装里,Gator 震荡器继承自多符号多周期基类,构造函数一口气收了颚线、齿线、唇线三组周期与平移量,再加平滑方式与应用价格。它内部用 8 个元素的参数数组往底层传值,缓冲区布局是 0 号上直方图、1 号上直方图色块、2 号下直方图、3 号下直方图色块。 有意思的是参数校验:当调用方传入的 jaw_period 小于 1 时,代码不会报错退出,而是直接把 m_param[0] 写成 13;齿线周期小于 1 兜底为 8,唇线周期小于 1 兜底为 5。这三个数字正好对应鳄鱼指标经典默认——颚 13、齿 8、唇 5,说明底层把「乱传参」默默拉回 Bill Williams 的原始设定。 缓冲区索引和默认周期这两点,是写 EA 时最容易踩坑的地方:如果你以为周期传 0 会触发报错,实际只会拿到默认鳄鱼参数,回测和实盘信号可能跟你设想的完全不同。外汇与贵金属杠杆高,这类隐性默认值会放大策略偏差,上 MT5 用 iGator 实测一遍最稳妥。

MQL5 / C++
const class="type">int teeth_shift,                 class=class="str">"cmt">// horizontal shift of teeth line
const class="type">int lips_period,                 class=class="str">"cmt">// period for calculating lip line
const class="type">int lips_shift,                  class=class="str">"cmt">// horizontal shift of lip line
const ENUM_MA_METHOD ma_method,        class=class="str">"cmt">// smoothing type
const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
) : CIndMSTF(IND_GATOR,class="num">2,symbol,timeframe)
{
class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_HISTOGRAM, class="num">1 - class="type">color buffer of the upper histogram, class="num">2 - LOWER_HISTOGRAM, class="num">3 - class="type">color buffer of the lower histogram
class=class="str">"cmt">//--- Set the size of the parameter array and fill it
::ResetLastError();
if(::ArrayResize(this.m_param,class="num">8)==class="num">8)
  {
   ::ZeroMemory(this.m_param);
   class=class="str">"cmt">//--- period for jaw line calculation
   this.m_param[class="num">0].type=TYPE_UINT;
   this.m_param[class="num">0].integer_value=(jaw_period<class="num">1 ? class="num">13 : jaw_period);
   class=class="str">"cmt">//--- horizontal shift of the jaw line
   this.m_param[class="num">1].type=TYPE_INT;
   this.m_param[class="num">1].integer_value=jaw_shift;
   class=class="str">"cmt">//--- period for teeth line calculation
   this.m_param[class="num">2].type=TYPE_UINT;
   this.m_param[class="num">2].integer_value=(teeth_period<class="num">1 ? class="num">8 : teeth_period);
   class=class="str">"cmt">//--- horizontal shift of teeth line
   this.m_param[class="num">3].type=TYPE_INT;
   this.m_param[class="num">3].integer_value=teeth_shift;
   class=class="str">"cmt">//--- period for lip line calculation
   this.m_param[class="num">4].type=TYPE_UINT;
   this.m_param[class="num">4].integer_value=(lips_period<class="num">1 ? class="num">5 : lips_period);
   class=class="str">"cmt">//--- horizontal shift of lips line
   this.m_param[class="num">5].type=TYPE_INT;
   this.m_param[class="num">5].integer_value=lips_shift;
   class=class="str">"cmt">//--- smoothing type
   this.m_param[class="num">6].type=TYPE_UINT;
   this.m_param[class="num">6].integer_value=ma_method;
   class=class="str">"cmt">//--- price type or handle
   this.m_param[class="num">7].type=TYPE_UINT;

鳄鱼振荡器的缓冲区与配色落地

在 Gator 指标初始化尾部,真正决定图表观感的是缓冲区绘制类型与颜色索引的绑定。上面这段代码把 0 号和 1 号缓冲区设成彩色直方图:0 号画在上方(UPPER_HISTOGRAM),1 号画在下方(LOWER_HISTOGRAM),分别对应鳄鱼 jaw/teeth 与 teeth/lips 差值绝对值的上下分布。 缓冲区位移沿用了 teeth_shift 与 lips_shift,这意味着上直方图相对主图偏移 8 根、下直方图偏移 5 根(默认参数下),开 MT5 把指标拖到副图能直接看到这种错位。 颜色映射写得很死:两个缓冲区的索引 0 都是 clrGreen、索引 1 都是 clrRed,初始化色索引也锁在 0。也就是说扩张段默认绿、收敛段默认红,想改配色得动 SetBufferColorToIndex 这几行,而不是去属性框里点。外汇与贵金属波动剧烈,这类辅助振荡器仅作多周期共振参考,实际信号误报概率不低。

MQL5 / C++
this.SetBufferDrawType(class="num">0,DRAW_COLOR_HISTOGRAM,UPPER_HISTOGRAM);
this.SetBufferDrawType(class="num">1,DRAW_COLOR_HISTOGRAM,LOWER_HISTOGRAM);

class=class="str">"cmt">//--- Set class="kw">default colors for class="type">color buffers
this.SetBufferColorToIndex(class="num">0,class="num">0,clrGreen);
this.SetBufferColorToIndex(class="num">0,class="num">1,clrRed);
this.SetBufferColorToIndex(class="num">1,class="num">0,clrGreen);
this.SetBufferColorToIndex(class="num">1,class="num">1,clrRed);
class=class="str">"cmt">//--- Set the class="kw">default initializing class="type">color index
this.SetBufferInitColorIndex(class="num">0,class="num">0);
this.SetBufferInitColorIndex(class="num">1,class="num">0);

◍ 封装一目均衡表的多周期指标类

在 MT5 里做系统化盯盘,把 Ichimoku 包成一个继承自 CIndMSTF 的类,比每次手写 iIchimoku 句柄省事得多。下面这段构造逻辑直接决定了云图三条轴的默认周期,改参数前先看清楚兜底值。 构造函数接收 symbol、timeframe 以及 tenkan_sen、kijun_sen、senkou_span_b 三个整数周期。父类初始化写死 IND_ICHIMOKU 与缓冲区数量 5,对应转换线、基准线、先行 A、先行 B、滞后线五个输出。 如果传入的周期小于 1,代码不会报错退出,而是静默回退:转换线补 9、基准线补 26、先行 B 补 52。这是云图最经典的默认参数组合,但你在黄金 15 分钟图上直接套 9/26/52 可能信号过密,建议手动传参覆盖。 参数数组用 ArrayResize 扩到 3 个元素,失败就 PrintFormat 打出错误码。非当前图表品种或周期时,描述字符串会拼上 symbol 和 timeframe,方便在指标列表中一眼区分多周期实例。外汇与贵金属杠杆高,这类多周期云图只作概率参考,实盘前务必在策略测试器跑一遍。

MQL5 / C++
class CIndIchimoku : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndIchimoku(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
               const class="type">int tenkan_sen,                 class=class="str">"cmt">// period of Tenkan-sen
               const class="type">int kijun_sen,                  class=class="str">"cmt">// period of Kijun-sen
               const class="type">int senkou_span_b               class=class="str">"cmt">// period of Senkou Span B
               ) : CIndMSTF(IND_ICHIMOKU,class="num">5,symbol,timeframe)
     {
     class=class="str">"cmt">// Buffer indexes: class="num">0 - TENKANSEN_LINE, class="num">1 - KIJUNSEN_LINE, class="num">2 - SENKOUSPANA_LINE, class="num">3 - SENKOUSPANB_LINE, class="num">4 - CHIKOUSPAN_LINE
     class=class="str">"cmt">//--- Set the size of the parameter array and fill it
     ::ResetLastError();
     if(::ArrayResize(this.m_param,class="num">3)==class="num">3)
       {
       ::ZeroMemory(this.m_param);
       class=class="str">"cmt">//--- period of Tenkan-sen
       this.m_param[class="num">0].type=TYPE_UINT;
       this.m_param[class="num">0].integer_value=(tenkan_sen<class="num">1 ? class="num">9 : tenkan_sen);
       class=class="str">"cmt">//--- period of Kijun-sen
       this.m_param[class="num">1].type=TYPE_UINT;
       this.m_param[class="num">1].integer_value=(kijun_sen<class="num">1 ? class="num">26 : kijun_sen);
       class=class="str">"cmt">//--- period of Senkou Span B
       this.m_param[class="num">2].type=TYPE_UINT;
       this.m_param[class="num">2].integer_value=(senkou_span_b<class="num">1 ? class="num">52 : senkou_span_b);
       }
     else
       ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
     class=class="str">"cmt">//--- Create description of parameters
     class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
     class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
     class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
     class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),tenkan_sen,kijun_sen,senkou_span_b);
     class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits

「在类里把一目均衡与BW-MFI的元数据钉死」

这段初始化逻辑展示了一目均衡指标(Ichimoku)在 C++ 风格封装里的自我声明方式:构造函数内通过 SetName 写死 "Ichimoku",用 m_category=IND_CATEGORY_TREND 把它归为趋势类,m_digits 取当前品种 Digits()+1,意味着云图缓冲的小数位比报价多一位,避免价格在穿越云层时被截断误判。 五条线的缓冲描述用 SetBufferDescription 逐个绑定:转换线带 tenkan_sen 周期参数、基准线带 kijun_sen 周期参数、Span B 带 senkou_span_b 周期参数,而 Senkou Span A 与 Chikou Span 是固定字符串。注意三行 SetBufferShift 被注释掉了——说明前导位移改在绘制阶段处理,而不是在元数据里预设,开 MT5 调参时可先保持注释,观察云层是否按 kijun_sen 周期前移。 后面 CIndBWMFI 类用 ArrayResize(m_param,1) 只放一个 TYPE_UINT 参数存成交量类型,失败就 PrintFormat 打出 Error %ld。这里有个易踩的坑:bool current 判断用 this.Symbol()==::Symbol() 且周期相同,若你把这个类套在次周期图表上,current 为 false,参数描述会自动追加 symbol/timeframe 后缀,回测时日志里指标名会和你手敲的不一致。

MQL5 / C++
this.SetParameters(param);
this.SetName("Ichimoku");
this.SetDescription("Ichimoku Kinko Hyo");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_TREND;
this.m_digits=::Digits()+class="num">1;
class=class="str">"cmt">//--- Description of line buffers TENKANSEN_LINE, KIJUNSEN_LINE, SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
this.SetBufferDescription(TENKANSEN_LINE,::StringFormat("Tenkan-sen(%lu)",tenkan_sen));
this.SetBufferDescription(KIJUNSEN_LINE,::StringFormat("Kijun-sen(%lu)",kijun_sen));
this.SetBufferDescription(SENKOUSPANA_LINE,"Senkou Span A");
this.SetBufferDescription(SENKOUSPANB_LINE,::StringFormat("Senkou Span B(%lu)",senkou_span_b));
this.SetBufferDescription(CHIKOUSPAN_LINE,"Chikou Span");
class=class="str">"cmt">//--- Write shifts to buffers SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
class=class="str">"cmt">//this.SetBufferShift(SENKOUSPANA_LINE,kijun_sen);
class=class="str">"cmt">//this.SetBufferShift(SENKOUSPANB_LINE,kijun_sen);
class=class="str">"cmt">//this.SetBufferShift(CHIKOUSPAN_LINE,kijun_sen-senkou_span_b);
}
};
class CIndBWMFI : class="kw">public CIndMSTF
{
class="kw">public:
 CIndBWMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
            const ENUM_APPLIED_VOLUME applied_volume
            ) : CIndMSTF(IND_BWMFI,class="num">1,symbol,timeframe)
  {
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
    {
     ::ZeroMemory(this.m_param);
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=applied_volume;
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());

BW MFI 的绘图缓冲与配色初始化

在自定义 BW MFI(Market Facilitation Index)指标的类构造函数里,有一段专门负责「对外显示身份」和「画出来长什么样」的逻辑。它先根据是否为当前图表品种/周期,拼出类似 EURUSD,H1 的后缀串,再据此设置指标名、描述与标题,归入 Williams 类别。 下面这段代码核心是给 0 号缓冲设成彩色柱状图(DRAW_COLOR_HISTOGRAM),并预置 4 个颜色索引:0 号绿(clrLime)、1 号鞍棕(clrSaddleBrown)、2 号蓝(clrBlue)、3 号粉(clrPink),初始化默认取 0 号色。BW 三合会(Bill Williams)原始分类里,这四种颜色对应价格-成交量关系的四种状态,MT5 端改色直接动这几行即可。 [CODE] 逐行拆解见 code 字段。实际验证时,把类挂到 EURUSD 的 H1 上,非当前周期会显示 (EURUSD,H1) 标题后缀;切回当前图则留空。外汇与贵金属杠杆高,指标仅描述市场 facilitation 状态,不预示方向,请自行在策略测试器内核对信号分布。

MQL5 / C++
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
this.SetParameters(param);
this.SetName("BW MFI");
this.SetDescription("Market Facilitation Index");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_WILLIAMS;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_COLOR_HISTOGRAM,class="num">0);

class=class="str">"cmt">//--- Set four class="kw">default colors for the class="type">color buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrLime);
this.SetBufferColorToIndex(class="num">0,class="num">1,clrSaddleBrown);
this.SetBufferColorToIndex(class="num">0,class="num">2,clrBlue);
this.SetBufferColorToIndex(class="num">0,class="num">3,clrPink);
class=class="str">"cmt">//--- Set the class="kw">default initializing class="type">color index
this.SetBufferInitColorIndex(class="num">0,class="num">0);

◍ 动量指标构造里的参数兜底与画线设定

在封装 Momentum 指标类时,代码对入参做了隐性兜底:当传入的 mom_period 小于 1 时,自动把 m_param[0].integer_value 写成 14。这意味着你在 EA 里漏传周期也不会崩,但回测里实际跑的是 14 期动量,而不是你以为的默认参数。 m_param[1] 被定义为 TYPE_UINT 并直接吃 applied_price,说明价格类型是以无符号整型塞进参数槽的,和 MT5 标准价格枚举一致。若你接的是非当前图表品种或周期,symbol_period 会拼成「SYMBOL,PERIOD」格式并写进参数描述,当前图则留空。 画线部分把 buffer 0 设成 DRAW_LINE、计算段编号 0,颜色绑定 clrDodgerBlue。你打开 MT5 导航器里的自定义 Momentum 实例,看到深蓝细线就是这块配置生效。 顺带一提,紧接其后的 CIndMFI 类构造函数只写了 ResetLastError() 开头,真正的数组填充在截断处之后——改这类封装时别假设 MFI 参数已填好,先确认后续代码是否补全。

MQL5 / C++
this.m_param[class="num">0].integer_value=(mom_period<class="num">1 ? class="num">14 : mom_period);
class=class="str">"cmt">//--- price type or handle
this.m_param[class="num">1].type=TYPE_UINT;
this.m_param[class="num">1].integer_value=applied_price;
}
else
 ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),mom_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("Momentum");
this.SetDescription("Momentum");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">2;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrDodgerBlue);
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Money Flow Index indicator class                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndMFI : class="kw">public CIndMSTF
 {
class="kw">public:
class=class="str">"cmt">//--- Constructor
 CIndMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
        const class="type">int                ma_period,      class=class="str">"cmt">// averaging period
        const ENUM_APPLIED_VOLUME applied_volume  class=class="str">"cmt">// volume type for calculation
        ) : CIndMSTF(IND_MFI,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();

「MFI 指标对象的参数与画线初始化」

在封装 MFI(资金流量指数)指标时,构造函数里先给参数数组 m_param 分配 2 个槽位,成功后才往下写类型与取值。若 ArrayResize 返回不等于 2,直接 PrintFormat 打出函数名与 GetLastError 错误码,方便在 MT5 Experts 日志里定位内存申请失败。 参数槽 0 固定为 TYPE_UINT,存平均周期:传入 ma_period 小于 1 时 fallback 到 14,否则用实参;槽 1 也是 TYPE_UINT,存 applied_volume(成交量类型,如 VOLUME_TICK)。这两行决定了后续 iMFI 调用的核心输入。 符号与周期描述做了懒拼接:仅当指标挂在当前图表品种与周期时 symbol_period 留空,否则用 StringFormat 拼成 "SYMBOL,PERIOD" 再接冒号与周期数,最终 param 形如 "(EURUSD,H1:14)"。SetName 写死 "MFI",m_category 归到 IND_CATEGORY_VOLUME——MT5 导航器里会据此分类。 画线部分只动 buffer 0:SetBufferDrawType(0,DRAW_LINE,0) 设成普通折线,SetBufferColorToIndex(0,0,clrDodgerBlue) 把默认色定为道奇蓝。复制下面片段到你的类里,改 clrDodgerBlue 就能换 MFI 主线条颜色,无需重编译整个指标库。

MQL5 / C++
if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
  {
   ::ZeroMemory(this.m_param);
   class=class="str">"cmt">//--- averaging period
   this.m_param[class="num">0].type=TYPE_UINT;
   this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
   class=class="str">"cmt">//--- volume type for calculation
   this.m_param[class="num">1].type=TYPE_UINT;
   this.m_param[class="num">1].integer_value=applied_volume;
  }
 else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
 class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
 class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
 this.SetParameters(param);
 this.SetName("MFI");
 this.SetDescription("Money Flow Index");
 this.m_title=this.Name()+this.Parameters();
 this.m_category=IND_CATEGORY_VOLUME;
 class=class="str">"cmt">//--- Write description of line buffers
 this.SetBufferDescription(class="num">0,this.m_title);
 class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
 this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
 class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
 this.SetBufferColorToIndex(class="num">0,class="num">0,clrDodgerBlue);

MA 构造函数里的参数兜底与描述组装

在封装多周期 MA 指标类时,构造函数先把周期、偏移、平滑方式、应用价格塞进一个 4 元素的参数数组。若传入的 ma_period 小于 1,代码会强制回退到 10——这意味着你误填 0 或负数时,指标不会报错崩掉,而是默默用 10 期均线跑,回测里可能因此得出偏平滑的假信号。 数组分配用 ArrayResize(this.m_param,4)==4 做守卫,失败就打印 ArrayResize failed 和错误码,方便在 MT5 Experts 日志里直接定位内存问题。四个槽位分别写 TYPE_UINT/TYPE_INT 并赋值,其中 m_param[1] 的 horizontal shift 允许负值,用来把均线左右平移做错位比对。 描述文本根据是否当前图表自动拼接:非当前品种或周期时,参数串会带上 "SYMBOL,PERIOD:period" 前缀,否则留空。最后 SetName 写死 "MA"、分类挂到 IND_CATEGORY_TREND,m_digits 取 Digits()+1,保证点位显示比报价多一位,避免交叉盘点值小的品种被四舍五入吞掉毛刺。 开 MT5 新建 EA 引用这个类,故意传 ma_period=0,看缓冲区是否真的落在 10 期;再切到 XAUUSD 的 M15 与非当前周期,确认参数描述里出现品种前缀。外汇与贵金属杠杆高,这类封装错误不会爆仓但会误导入场,验证完再上实盘。

MQL5 / C++
const class="type">int                ma_shift,       class=class="str">"cmt">// horizontal shift of the indicator
const ENUM_MA_METHOD      ma_method,       class=class="str">"cmt">// smoothing type
const ENUM_APPLIED_PRICE  applied_price    class=class="str">"cmt">// price type or handle
   ) : CIndMSTF(IND_MA,class="num">1,symbol,timeframe)
   {
   class=class="str">"cmt">//--- Set the size of the parameter array and fill it
   ::ResetLastError();
   if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
     {
      ::ZeroMemory(this.m_param);
      class=class="str">"cmt">//--- averaging period
      this.m_param[class="num">0].type=TYPE_UINT;
      this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">10 : ma_period);
      class=class="str">"cmt">//--- horizontal shift of the indicator
      this.m_param[class="num">1].type=TYPE_INT;
      this.m_param[class="num">1].integer_value=ma_shift;
      class=class="str">"cmt">//--- smoothing type
      this.m_param[class="num">2].type=TYPE_UINT;
      this.m_param[class="num">2].integer_value=ma_method;
      class=class="str">"cmt">//--- price type or handle
      this.m_param[class="num">3].type=TYPE_UINT;
      this.m_param[class="num">3].integer_value=applied_price;
     }
   else
      ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("MA");
   this.SetDescription("Moving Average");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_TREND;
   this.m_digits=::Digits()+class="num">1;
   class=class="str">"cmt">//--- Write description of line buffers

◍ OSMA 指标类的参数兜底逻辑

在封装 OSMA 指标类时,构造函数先把参数数组 resize 到 4,再逐项写入快线周期、慢线周期、差值平滑周期与价格类型。这里藏着一组硬兜底:若传入周期小于 1,快线默认回退 12、慢线回退 26,差值周期小于 1 取 9、小于 2 取 2。 这种写法能避免 MT5 调用 iOsMA 时因非法周期直接报参数错误。你在写自己的指标封装层时,也可以沿用「小于下限就取经典默认」的思路,少写一层外部校验。 外汇与贵金属市场杠杆高、波动剧烈,任何指标参数都只是概率参考,实盘前务必在策略测试器里用历史数据验证。

MQL5 / C++
this.SetBufferDescription(class="num">0,this.m_title);
class=class="str">"cmt">//--- Write shift to buffer class="num">0
this.SetBufferShift(class="num">0,ma_shift);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);
  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Moving Average of Oscillator indicator class                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndOsMA : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndOsMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const class="type">int      fast_ema_period, class=class="str">"cmt">// fast MA period
           const class="type">int      slow_ema_period, class=class="str">"cmt">// slow MA period
           const class="type">int      signal_period,   class=class="str">"cmt">// difference averaging period
           const ENUM_APPLIED_PRICE applied_price   class=class="str">"cmt">// price type or handle
           ) : CIndMSTF(IND_OSMA,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
    {
     ::ZeroMemory(this.m_param);
     class=class="str">"cmt">//--- fast MA period
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(fast_ema_period<class="num">1 ? class="num">12 : fast_ema_period);
     class=class="str">"cmt">//--- slow MA period
     this.m_param[class="num">1].type=TYPE_UINT;
     this.m_param[class="num">1].integer_value=(slow_ema_period<class="num">1 ? class="num">26 : slow_ema_period);
     class=class="str">"cmt">//--- difference averaging period
     this.m_param[class="num">2].type=TYPE_UINT;
     this.m_param[class="num">2].integer_value=(signal_period<class="num">1 ? class="num">9 : signal_period<class="num">2 ? class="num">2 : signal_period);
     class=class="str">"cmt">//--- price type or handle
     this.m_param[class="num">3].type=TYPE_UINT;
     this.m_param[class="num">3].integer_value=applied_price;
    }
  else

「OsMA 缓冲区的绘制与精度设定」

在自定义 OsMA 指标类的初始化尾部,代码先判断当前图表与实例指定的品种、周期是否一致:一致时参数串留空,不一致则把 Symbol,Timeframe 拼进描述,方便在副图标题区分跨周期加载。 随后通过 SetBufferDrawType(0, DRAW_HISTOGRAM, 0) 把 0 号缓冲区设为柱状线,并用 SetBufferColorToIndex(0,0,clrSilver) 给定默认银色。OsMA 本质是 MACD 主线与信号线的差,用直方图表达正负穿越比线形更直观。 精度上 m_digits = Digits() + 2,即在品种报价小数位基础上多留两位,避免小数值被截断。外汇与贵金属杠杆高、滑点随机,跨周期指标仅作概率参考,实盘前请在 MT5 用不同品种验证显示与重绘表现。

MQL5 / C++
::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("OsMA");
this.SetDescription("Moving Average of Oscillator");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=::Digits()+class="num">2;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_HISTOGRAM,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrSilver);

给 MACD 指标对象塞参数与画线设定

在自定义指标封装里,先给参数数组 m_param 扩到 4 个槽位,成功才往下走;若 ArrayResize 返回不是 4,直接打印错误码,这种防御写法能避免后面越界写崩溃。 扩完立即 ZeroMemory 清空,再按序填快线周期、慢线周期、信号周期和价格类型。注意三处三元判断:传入小于 1 时分别回退到 12、26、9——也就是经典 MACD 的默认参数,你可以改这几个字面量来换默认值。 下面这段是参数描述与显示名的拼装:如果用的不是当前图表品种或周期,会把 symbol,period 前缀拼进参数串,否则留空。SetBufferDrawType 把 0 号缓冲画成柱状(主线),1 号画成线(信号线),类别归到 oscillator,digits 取当前品种小数位加 1。 外汇和贵金属波动大、杠杆高,改这些参数做回测时务必用历史数据验证,参数组合只影响信号灵敏度,不保证任何方向胜率。

MQL5 / C++
if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
  {
  ::ZeroMemory(this.m_param);
  class=class="str">"cmt">//--- fast MA period
  this.m_param[class="num">0].type=TYPE_UINT;
  this.m_param[class="num">0].integer_value=(fast_ema_period<class="num">1 ? class="num">12 : fast_ema_period);
  class=class="str">"cmt">//--- slow MA period
  this.m_param[class="num">1].type=TYPE_UINT;
  this.m_param[class="num">1].integer_value=(slow_ema_period<class="num">1 ? class="num">26 : slow_ema_period);
  class=class="str">"cmt">//--- difference averaging period
  this.m_param[class="num">2].type=TYPE_UINT;
  this.m_param[class="num">2].integer_value=(signal_period<class="num">1 ? class="num">9 : signal_period);
  class=class="str">"cmt">//--- price type or handle
  this.m_param[class="num">3].type=TYPE_UINT;
  this.m_param[class="num">3].integer_value=applied_price;
  }
 else
  ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
 class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
 class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
 this.SetParameters(param);
 this.SetName("MACD");
 this.SetDescription("Moving Averages Convergence/Divergence");
 this.m_title=this.Name()+this.Parameters();
 this.m_category=IND_CATEGORY_OSCILLATOR;
 this.m_digits=::Digits()+class="num">1;
 class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers
 this.SetBufferDescription(MAIN_LINE,this.m_title);
 this.SetBufferDescription(SIGNAL_LINE,"Signal");
 class=class="str">"cmt">//--- Set drawing style for buffers class="num">0 and class="num">1 and the numbers of calculation part data buffers
 this.SetBufferDrawType(class="num">0,DRAW_HISTOGRAM,MAIN_LINE);
 this.SetBufferDrawType(class="num">1,DRAW_LINE,SIGNAL_LINE);

◍ OBV 指标类的构造与缓冲区设定

在 MT5 自定义指标框架里,CIndOBV 继承自 CIndMSTF,构造时先传 IND_OBV、缓冲区数量 1、交易品种与周期。计算用的成交量类型由 applied_volume 决定,写进 m_param[0] 的 integer_value,类型标为 TYPE_UINT。 构造函数里先用 ArrayResize 把参数数组扩到 1 个元素,失败就打印 ArrayResize failed 及错误码;成功则 ZeroMemory 清空再赋值。若品种或周期不是当前图表,描述里会拼上 "品种,周期" 后缀,方便多周期对照。 指标名固定为 OBV,分类归到 IND_CATEGORY_VOLUME,m_digits 设 0 因为量是整数。缓冲区 0 的绘制类型设为 DRAW_LINE,并把标题绑到 Name()+Parameters(),这样子窗口里能直接看出是哪品种哪周期的 OBV。 开 MT5 新建指标类时,照这段代码把 SetBufferDrawType(0,DRAW_LINE,0) 和 m_category 填上,编译后拖到黄金 XAUUSD 的 H1 图,就能看到量能线随价偏移。外汇与贵金属杠杆高,OBV 只反映量价累积倾向,信号失效概率不低,需结合其他工具验证。

MQL5 / C++
class CIndOBV : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndOBV(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const ENUM_APPLIED_VOLUME applied_volume   class=class="str">"cmt">// volume type for calculation
           ) : CIndMSTF(IND_OBV,class="num">1,symbol,timeframe)
   {
   class=class="str">"cmt">//--- Set the size of the parameter array and fill it
   ::ResetLastError();
   if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
     {
      ::ZeroMemory(this.m_param);
      class=class="str">"cmt">//--- volume type for calculation
      this.m_param[class="num">0].type=TYPE_UINT;
      this.m_param[class="num">0].integer_value=applied_volume;
     }
   else
      ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("OBV");
   this.SetDescription("On Balance Volume");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_VOLUME;
   this.m_digits=class="num">0;
   class=class="str">"cmt">//--- Write description of line buffers
   this.SetBufferDescription(class="num">0,this.m_title);
   class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0

「SAR 指标的面向对象封装细节」

在 MT5 自建指标库时,把 Parabolic SAR 封装成 CIndSAR 类能省掉大量重复代码。它继承自多周期基础类 CIndMSTF,构造时只需传入品种、周期、加速步长 step 与最大步长 maximum 两个核心参数。 构造函数里先用 ArrayResize 把参数数组扩到 2 个元素,失败会通过 PrintFormat 打出 Error 代码;成功则把 step 和 maximum 分别写入 m_param[0]、m_param[1] 的 double_value,类型标为 TYPE_DOUBLE。 非当前图表品种或周期时,描述字符串会自动拼上 "Symbol,Period" 前缀,例如 "EURUSD,H1:0.02,0.20",方便多周期面板区分。最终指标名固定为 SAR,分类归到 IND_CATEGORY_TREND,缓冲区 0 的描叙就是带参数的标题。 开 MT5 新建 EA 或指标时,直接复用这段封装,改 step(常见 0.02)和 maximum(常见 0.2)就能快速挂不同敏感度的 SAR 线。外汇与贵金属波动剧烈,SAR 反转信号滞后可能导致止损偏大,实盘前务必在策略测试器跑历史数据验证。

MQL5 / C++
class CIndSAR : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndSAR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
          const class="type">class="kw">double step,                      class=class="str">"cmt">// price change step — acceleration factor
          const class="type">class="kw">double maximum                    class=class="str">"cmt">// maximum step
          ) : CIndMSTF(IND_SAR,class="num">1,symbol,timeframe)
    {
    class=class="str">"cmt">//--- Set the size of the parameter array and fill it
    ::ResetLastError();
    if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
      {
      ::ZeroMemory(this.m_param);
      class=class="str">"cmt">//--- price change step — acceleration factor
      this.m_param[class="num">0].type=TYPE_DOUBLE;
      this.m_param[class="num">0].double_value=step;
      class=class="str">"cmt">//--- maximum step
      this.m_param[class="num">1].type=TYPE_DOUBLE;
      this.m_param[class="num">1].double_value=maximum;
      }
    else
      ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
    class=class="str">"cmt">//--- Create description of parameters
    class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
    class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
    class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
    class="type">class="kw">string param=StringFormat("(%s%s%.2f,%.2f)",symbol_period,(current ? "" : ":"),step,maximum);
    class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
    this.SetParameters(param);
    this.SetName("SAR");
    this.SetDescription("Parabolic SAR");
    this.m_title=this.Name()+this.Parameters();
    this.m_category=IND_CATEGORY_TREND;
    class=class="str">"cmt">//--- Write description of line buffers
    this.SetBufferDescription(class="num">0,this.m_title);

RSI 多周期封装里的参数兜底

把 RSI 做成可跨周期调用的类时,构造器里对 ma_period 做了硬性下限保护:小于 1 自动取 14,小于 2 则取 2,否则用传入值。这一行直接决定了你后续在 EA 里乱传参也不会炸出非法周期错误。 非当前图表品种或周期时,构造器会把 symbol、timeframe 拼进参数描述串,当前图则留空。这样在指标窗口标题上能直观区分是挂在哪根周期线上,省得切图后认不出来源。 下面这段是缓冲 0 的绘制设定与 RSI 类构造头,注意 DRAW_ARROW 配合 clrLime 只影响该类自身 buffer 0 的显示,不代表 RSI 主线也变箭头——主线由后面 buffer 另行配置。外汇与贵金属波动剧烈,这类封装仅降低编码出错概率,不预示任何方向。

MQL5 / C++
   class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_ARROW,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrLime);
   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Relative Strength Index indicator class                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndRSI : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndRSI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const class="type">int          ma_period,    class=class="str">"cmt">// averaging period
           const ENUM_APPLIED_PRICE applied_price  class=class="str">"cmt">// price type or handle
           ) : CIndMSTF(IND_RSI,class="num">1,symbol,timeframe)
   {
   class=class="str">"cmt">//--- Set the size of the parameter array and fill it
   ::ResetLastError();
   if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
     {
      ::ZeroMemory(this.m_param);
      class=class="str">"cmt">//--- averaging period
      this.m_param[class="num">0].type=TYPE_UINT;
      this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period);
      class=class="str">"cmt">//--- price type or handle
      this.m_param[class="num">1].type=TYPE_UINT;
      this.m_param[class="num">1].integer_value=applied_price;
     }
   else
      ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("RSI");
   this.SetDescription("Relative Strength Index");

◍ RVI 指标的初始化与缓冲区设定

在 MT5 自建指标类里,CIndRVI 继承自 CIndMSTF,构造函数一上来就通过 ArrayResize 把参数数组定成 1 个元素,失败会 PrintFormat 报错并带出 GetLastError 的代码。 若传入的 ma_period 小于 1,会被强制修正为 10,这意味着你在 EA 里误传 0 或负数时,指标不会崩,而是默默用 10 期均线平滑——回测里容易忽略这个隐性默认值。 标题与描述部分,current 变量判断是否为当前图表品种周期;非当前图表的 symbol_period 会拼进参数串,例如 "EURUSD,H1:10",方便多周期调用时区分实例。 缓冲区 0 设成 DRAW_LINE 且默认色 clrDodgerBlue,类别归为 IND_CATEGORY_OSCILLATOR、小数位 2,开 MT5 导航器拖出该指标即可看到蓝线为主线和信号线之外的振荡器呈现。

MQL5 / C++
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">2;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrDodgerBlue);
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Relative Vigor Index indicator class                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndRVI : class="kw">public CIndMSTF
 {
class="kw">public:
class=class="str">"cmt">//--- Constructor
 CIndRVI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
          const class="type">int ma_period       class=class="str">"cmt">// averaging period
          ) : CIndMSTF(IND_RVI,class="num">2,symbol,timeframe)
  {
  class=class="str">"cmt">// Buffer indexes: class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
    {
     ::ZeroMemory(this.m_param);
     class=class="str">"cmt">//--- averaging period
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">10 : ma_period);
    }
  else
     ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
  class=class="str">"cmt">//--- Create description of parameters
  class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
  class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
  class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
  class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
  class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
  this.SetParameters(param);
  this.SetName("RVI");
  this.SetDescription("Relative Vigor Index");

「在自定义指标里接管缓冲区与标准差参数」

写 MT5 自定义振荡器时,构造函数里先把标题、分类和精度钉死,能省掉运行期大量歧义。下面这段把指标名拼成 Name()+Parameters(),分类强制为 IND_CATEGORY_OSCILLATOR,小数位写死 3 位,主线和信号线缓冲区也顺手登记了描述。 缓冲区 0 和 1 都设成 DRAW_LINE,分别绑定 MAIN_LINE 与 SIGNAL_LINE 枚举;颜色默认给主线 clrGreen、信号线 clrRed。你在 MQ 编辑器里改这两个 clr 值,图表上的快慢线配色立刻变,不用碰绘图逻辑。 标准差类 CIndStdDev 的构造更硬:参数数组必须 ArrayResize 到 4,失败就什么都不填。ma_period 有兜底——传入小于 1 一律当 20,小于 2 当 2,否则用原值;这意味着你传 0 进去,实际加载的是 20 期标准差,回测外汇或贵金属波动带时得留意这个隐性默认值。 开 MT5 新建一个继承 CIndMSTF 的指标,把下面代码粘进构造函数,编译后拖到 XAUUSD 的 M15 上,看主线绿、信号线红是否如预期出现。

MQL5 / C++
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">3;
class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers
this.SetBufferDescription(MAIN_LINE,this.m_title);
this.SetBufferDescription(SIGNAL_LINE,"Signal");

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0 and class="num">1 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,MAIN_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,SIGNAL_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0 and class="num">1
this.SetBufferColorToIndex(MAIN_LINE,class="num">0,clrGreen);
this.SetBufferColorToIndex(SIGNAL_LINE,class="num">0,clrRed);

class CIndStdDev : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndStdDev(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
             const class="type">int      ma_period,    class=class="str">"cmt">// averaging period
             const class="type">int      ma_shift,     class=class="str">"cmt">// horizontal shift of the indicator
             const ENUM_MA_METHOD   ma_method,    class=class="str">"cmt">// smoothing type
             const ENUM_APPLIED_PRICE applied_price  class=class="str">"cmt">// price type or handle
             ) : CIndMSTF(IND_STDDEV,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
    {
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- averaging period
    this.m_param[class="num">0].type=TYPE_UINT;
    this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">20 : ma_period<class="num">2 ? class="num">2 : ma_period);
    class=class="str">"cmt">//--- horizontal shift of the indicator
    this.m_param[class="num">1].type=TYPE_INT;
    this.m_param[class="num">1].integer_value=ma_shift;

StdDev 指标对象的参数与画线初始化

这段 CIndStdDev 构造函数尾部,负责把平滑方式、应用价格写进 m_param 数组,并给指标挂上名称、类别与缓冲区样式。m_param[2] 存的是 ma_method(平滑类型,如 SMA/EMA),m_param[3] 存 applied_price(价格类型或句柄),两者都用 TYPE_UINT 标记。 若前面 ArrayResize 失败,会走 else 分支用 PrintFormat 打出函数名与 GetLastError 错误码,方便在 MT5 Experts 日志里定位内存分配问题。 接着判断是否用了当前图表品种与周期:current 为 true 时 symbol_period 留空,否则拼成 "SYMBOL,PERIOD" 形式,再与 ma_period 组合成形如 "(EURUSD,H1:20)" 的参数串。 指标元信息在这里落定:SetName 写 "StdDev",SetDescription 写 "Standard Deviation",m_category 设为 IND_CATEGORY_TREND,m_digits 取 Digits()+1 以容纳标准差小数位。 缓冲区 0 被设为 DRAW_LINE 线形、默认色 clrMediumSeaGreen,并通过 SetBufferShift(0, ma_shift) 承接偏移量。打开 MT5 导航器拖一个自定义 StdDev,若线色不是海绿或标题不带参数,多半是这段初始化没跑全。

MQL5 / C++
   class=class="str">"cmt">//--- smoothing type
   this.m_param[class="num">2].type=TYPE_UINT;
   this.m_param[class="num">2].integer_value=ma_method;
   class=class="str">"cmt">//--- price type or handle
   this.m_param[class="num">3].type=TYPE_UINT;
   this.m_param[class="num">3].integer_value=applied_price;
   }
   else
    ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
   class=class="str">"cmt">//--- Create description of parameters
   class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
   class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
   class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
   class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
   class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
   this.SetParameters(param);
   this.SetName("StdDev");
   this.SetDescription("Standard Deviation");
   this.m_title=this.Name()+this.Parameters();
   this.m_category=IND_CATEGORY_TREND;
   this.m_digits=::Digits()+class="num">1;
   class=class="str">"cmt">//--- Write description of line buffers
   this.SetBufferDescription(class="num">0,this.m_title);
   class=class="str">"cmt">//--- Write shift to buffer class="num">0
   this.SetBufferShift(class="num">0,ma_shift);
   class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
   this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
   class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
   this.SetBufferColorToIndex(class="num">0,class="num">0,clrMediumSeaGreen);

◍ Stochastic 多周期封装里的参数兜底逻辑

在 MT5 自建指标类里把 Stochastic 包进多周期框架,构造函数先把 K、D、slowing 三个周期参数塞进 m_param 数组。数组长度固定 resize 成 5,索引 0~4 依次对应 Kperiod、Dperiod、slowing、ma_method、price_field,类型全标 TYPE_UINT。 注意那段三元判断:Kperiod 若小于 1 自动退回 5,Dperiod 和 slowing 小于 1 则退回 3。这不是随便写的,MT5 内置 Stochastic 默认慢速参数就是 3、K 默认 5,相当于你传了非法值也保证指标能初始化而不是直接崩。 ArrayResize 返回不等于 5 时会走 else 分支用 PrintFormat 把错误码打出来,方便你在策略测试器里抓初始化失败。最后那段 current 布尔量用来判断是不是当前图表品种周期,非当前的话描述里拼上 symbol 和周期文本,避免你挂了 EURUSD 的 H1 Stochastic 却误以为是当前图。 外汇和贵金属波动大,这类封装只是工程层防错,不代表信号胜率,实盘前务必在 MT5 用不同品种周期跑一遍确认缓冲区索引没错位。

MQL5 / C++
const class="type">int      Dperiod,           class=class="str">"cmt">// D-period(primary smoothing period)
const class="type">int      slowing,           class=class="str">"cmt">// final smoothing
const ENUM_MA_METHOD   ma_method,         class=class="str">"cmt">// smoothing type
const ENUM_STO_PRICE   price_field       class=class="str">"cmt">// Stochastic calculation method
) : CIndMSTF(IND_STOCHASTIC,class="num">2,symbol,timeframe)
 {
class=class="str">"cmt">// Buffer indexes: class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE
class=class="str">"cmt">//--- Set the size of the parameter array and fill it
::ResetLastError();
if(::ArrayResize(this.m_param,class="num">5)==class="num">5)
  {
   ::ZeroMemory(this.m_param);
   class=class="str">"cmt">//--- K period(number of bars for calculation)
   this.m_param[class="num">0].type=TYPE_UINT;
   this.m_param[class="num">0].integer_value=(Kperiod<class="num">1 ? class="num">5 : Kperiod);
   class=class="str">"cmt">//--- D period(primary smoothing period)
   this.m_param[class="num">1].type=TYPE_UINT;
   this.m_param[class="num">1].integer_value=(Dperiod<class="num">1 ? class="num">3 : Dperiod);
   class=class="str">"cmt">//--- final smoothing
   this.m_param[class="num">2].type=TYPE_UINT;
   this.m_param[class="num">2].integer_value=(slowing<class="num">1 ? class="num">3 : slowing);
   class=class="str">"cmt">//--- smoothing type
   this.m_param[class="num">3].type=TYPE_UINT;
   this.m_param[class="num">3].integer_value=ma_method;
   class=class="str">"cmt">//--- Stochastic calculation method
   this.m_param[class="num">4].type=TYPE_UINT;
   this.m_param[class="num">4].integer_value=price_field;
   }
 else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));

「给 Stochastic 和 TEMA 指标类填参数与画线属性」

在 MT5 自建指标封装类里,构造时先把参数字符串拼好,再挂到内部描述体系上。下面这段 Stochastic 封装把周期三元组(K、D、slowing)用 StringFormat 拼成 (%s%s%lu,%lu,%lu) 形式,current 为真时不加冒号,否则用冒号隔离周期标识,最终通过 SetParameters 写进对象。 随后调用 SetName("Stoch")、SetDescription("Stochastic Oscillator") 定下名称与说明,m_title 由 Name()+Parameters() 自动拼接,m_category 设为 IND_CATEGORY_OSCILLATOR,m_digits 固定 2 位——这意味着子窗口数值保留两位小数,和 MT5 终端默认随机指标一致。 画线部分用 SetBufferDrawType 把 0、1 号缓冲区分别绑到 MAIN_LINE 和 SIGNAL_LINE,类型都是 DRAW_LINE;颜色上主线和信号线分别给 clrLightSeaGreen 与 clrRed,索引 0 即第一条线。你在终端看到的绿主红信号配色,就是这里落地的。 TEMA 类则走另一套:构造里 ArrayResize(m_param,3) 必须返回 3 才继续,否则参数数组无效;ma_period 若小于 1 自动回退到 14,这是典型的防御式默认值。外汇与贵金属波动剧烈,这类封装若参数越界不拦截,可能在回测时静默出错,实盘风险偏高。

MQL5 / C++
class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),Kperiod,Dperiod,slowing);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("Stoch");
this.SetDescription("Stochastic Oscillator");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">2;
class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers
this.SetBufferDescription(MAIN_LINE,this.m_title);
this.SetBufferDescription(SIGNAL_LINE,"Signal");

class=class="str">"cmt">//--- Set drawing style for buffers class="num">0 and class="num">1 and the numbers of calculation part data buffers
this.SetBufferDrawType(class="num">0,DRAW_LINE,MAIN_LINE);
this.SetBufferDrawType(class="num">1,DRAW_LINE,SIGNAL_LINE);

class=class="str">"cmt">//--- Set class="kw">default colors for buffers class="num">0 and class="num">1
this.SetBufferColorToIndex(MAIN_LINE,class="num">0,clrLightSeaGreen);
this.SetBufferColorToIndex(SIGNAL_LINE,class="num">0,clrRed);

class CIndTEMA : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndTEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
           const class="type">int          ma_period,    class=class="str">"cmt">// averaging period
           const class="type">int          ma_shift,     class=class="str">"cmt">// horizontal shift of the indicator
           const ENUM_APPLIED_PRICE  applied_price  class=class="str">"cmt">// price type or handle
           ) : CIndMSTF(IND_TEMA,class="num">1,symbol,timeframe)
  {
  class=class="str">"cmt">//--- Set the size of the parameter array and fill it
  ::ResetLastError();
  if(::ArrayResize(this.m_param,class="num">3)==class="num">3)
    {
     ::ZeroMemory(this.m_param);
     class=class="str">"cmt">//--- averaging period
     this.m_param[class="num">0].type=TYPE_UINT;
     this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period);
     class=class="str">"cmt">//--- horizontal shift of the indicator

给 TEMA 指标绑上参数与画线样式

在 MT5 自建指标类里,TEMA 的构造逻辑会把移动平均位移与价格类型直接写进参数槽:m_param[1] 存整数型的 ma_shift,m_param[2] 用无符号整型记 applied_price。若数组扩容失败,代码会打印 ArrayResize failed 并返回错误码,这在复盘 EURUSD 这类点差跳变品种时容易在日志里暴露内存分配问题。 非当前图表品种或周期时,symbol_period 会把 symbol 与 timeframe 拼进参数描述,例如“EURUSD,H1:14”这种形态,方便在多周期监控面板里一眼区分实例。SetName 固定写死 TEMA,分类归到 IND_CATEGORY_TREND,m_digits 取 Digits()+1 以兼容五位数报价外的扩展精度。 画线部分把 buffer 0 设成红色直线、偏移量沿用 ma_shift,这意味着你在副图加载后若发现曲线整体右移,优先查 ma_shift 而非重画指标。外汇与贵金属杠杆高,参数误设可能在回测与实盘间产生偏离,开 MT5 按上面代码改 clrRed 为 clrDodgerBlue 可快速验证视觉绑定是否生效。

MQL5 / C++
this.m_param[class="num">1].type=TYPE_INT;
this.m_param[class="num">1].integer_value=ma_shift;
class=class="str">"cmt">//--- price type or handle
this.m_param[class="num">2].type=TYPE_UINT;
this.m_param[class="num">2].integer_value=applied_price;
}
else
 ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("TEMA");
this.SetDescription("Triple Exponential Moving Average");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_TREND;
this.m_digits=::Digits()+class="num">1;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);
class=class="str">"cmt">//--- Write shift to buffer class="num">0
this.SetBufferShift(class="num">0,ma_shift);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);

◍ TRIX 指标实例化的参数与画线设定

在自定义指标类里,初始化 TRIX 时要先清空错误码再给参数数组扩容。代码用 ArrayResize 把 m_param 扩到 2,失败就打印错误号,成功则写入均线周期与价格类型两个整型参数。 周期参数做了下界保护:传入 ma_period 小于 1 时回退到 14,小于 2 时取 2,否则用原值。这个 14 是 TRIX 常见的默认三重指数平均周期,直接写死在三元表达式里。 非当前图表品种或周期时,参数描述会拼上 "SYMBOL,TIMEFRAME:" 前缀,当前图则留空。随后 SetName 写死 "TRIX",分类归到 IND_CATEGORY_OSCILLATOR,缓冲区 0 的标题就是名称加参数串。 画线部分把缓冲区 0 设成 DRAW_LINE 线型,索引 0 默认染成 clrRed。你在 MT5 里改这行颜色常量,就能让 TRIX 主线变成自己顺眼的颜色,不用动指标逻辑。

MQL5 / C++
::ResetLastError();
if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
  {
   ::ZeroMemory(this.m_param);
   class=class="str">"cmt">//--- averaging period
   this.m_param[class="num">0].type=TYPE_UINT;
   this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period);
   class=class="str">"cmt">//--- price type or handle
   this.m_param[class="num">1].type=TYPE_UINT;
   this.m_param[class="num">1].integer_value=applied_price;
   }
else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category
this.SetParameters(param);
this.SetName("TRIX");
this.SetDescription("Triple Exponential Average");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);

「给 WPR 和 VIDyA 套上多周期外壳」

这段继承自 CIndMSTF 的构造函数,把威廉指标(%R)封装成可跨品种、跨周期调用的对象。核心逻辑是先重置错误码,再用 ArrayResize 把参数数组扩到 1 个元素,失败就打印 ArrayResize failed 及错误号,成功则清零内存并写入周期参数。 周期参数做了下限保护:calc_period 小于 1 时强制取 14,也就是 WPR 的默认回溯窗口;大于等于 1 就原样传入。对外显示名固定为 "%R",分类归到振荡器(IND_CATEGORY_OSCILLATOR),价格精度锁 2 位小数。 非当前图表的品种或周期会拼进参数描述串,例如 "EURUSD,H1:14",方便在子窗口区分实例。缓冲区 0 设为 DRAW_LINE 线型、默认色 clrAqua,标题由 Name() 加 Parameters() 自动拼出。 紧接着的 CIndVIDyA 类同样公开继承 CIndMSTF,构造入参里出现了 cmo_period——即 Chande Momentum 的周期,说明 VIDyA 动态平均线内部要靠 CMO 动量来调权重。外汇与贵金属波动剧烈,这类多周期封装虽方便,但跨周期调用存在重绘与滑点风险,参数验证请在 MT5 策略测试器逐项跑过再上实盘。

MQL5 / C++
      ) : CIndMSTF(IND_WPR,class="num">1,symbol,timeframe)
   {
      class=class="str">"cmt">//--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,class="num">1)==class="num">1)
         {
          ::ZeroMemory(this.m_param);
          class=class="str">"cmt">//--- averaging period
          this.m_param[class="num">0].type=TYPE_UINT;
          this.m_param[class="num">0].integer_value=(calc_period<class="num">1 ? class="num">14 : calc_period);
         }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      class=class="str">"cmt">//--- Create description of parameters
      class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
      class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),calc_period);
      class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("%R");
      this.SetDescription("Williams&class="macro">#x27; Percent Range");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=class="num">2;
      class=class="str">"cmt">//--- Write description of line buffers
      this.SetBufferDescription(class="num">0,this.m_title);
      
      class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
      this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
      class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
      this.SetBufferColorToIndex(class="num">0,class="num">0,clrAqua);
   }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Variable Index Dynamic Average indicator class                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndVIDyA : class="kw">public CIndMSTF
  {
class="kw">public:
class=class="str">"cmt">//--- Constructor
   CIndVIDyA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
             const class="type">int          cmo_period,   class=class="str">"cmt">// the Chande Momentum period

VIDYA 封装时的参数兜底与描述生成

在把 VIDYA(变指数动态平均)包进多周期指标基类时,构造函数先按 4 个槽位重置参数数组,分别对应 CMO 动量周期、平滑 EMA 周期、水平偏移与应用价格。若 ArrayResize 返回的不是 4,会直接打印错误码,这种写法能在加载失败时报出具体 Error 编号,方便在 MT5 Experts 日志里定位。 CMO 周期若传入小于 1,代码强制落为 9;EMA 周期小于 1 则落为 12。这两个默认值意味着:即使调用方漏填,指标也不会用 0 去算,而是以 9/12 这套偏短周期组合起算,对贵金属 1H 图可能更易触发转向信号,但高频噪声也偏高。 描述字符串只在非当前图表品种或周期时才拼上 symbol、timeframe,当前图则留空。最后把名称写死为 'VIDYA',分类归到 IND_CATEGORY_TREND,标题由名称加参数字符串自动拼成,省去手动维护显示名。外汇与贵金属杠杆高,直接用默认参数上实盘前建议先开 MT5 用策略测试仪跑一轮。

MQL5 / C++
const class="type">int                ema_period,      class=class="str">"cmt">// period of the smoothing factor
const class="type">int                ma_shift,        class=class="str">"cmt">// horizontal shift of the indicator
const ENUM_APPLIED_PRICE applied_price    class=class="str">"cmt">// price type or handle
) : CIndMSTF(IND_VIDYA,class="num">1,symbol,timeframe)
 {
 class=class="str">"cmt">//--- Set the size of the parameter array and fill it
 ::ResetLastError();
 if(::ArrayResize(this.m_param,class="num">4)==class="num">4)
   {
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- Chande Momentum period
    this.m_param[class="num">0].type=TYPE_UINT;
    this.m_param[class="num">0].integer_value=(cmo_period<class="num">1 ? class="num">9 : cmo_period);
    class=class="str">"cmt">//--- smoothing factor period
    this.m_param[class="num">1].type=TYPE_UINT;
    this.m_param[class="num">1].integer_value=(ema_period<class="num">1 ? class="num">12 : ema_period);
    class=class="str">"cmt">//--- horizontal shift of the indicator
    this.m_param[class="num">2].type=TYPE_INT;
    this.m_param[class="num">2].integer_value=ma_shift;
    class=class="str">"cmt">//--- price type or handle
    this.m_param[class="num">3].type=TYPE_UINT;
    this.m_param[class="num">3].integer_value=applied_price;
   }
 else
   ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
 class=class="str">"cmt">//--- Create description of parameters
 class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
 class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
 class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
 class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),cmo_period,ema_period);
 class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
 this.SetParameters(param);
 this.SetName("VIDYA");
 this.SetDescription("Variable Index Dynamic Average");
 this.m_title=this.Name()+this.Parameters();
 this.m_category=IND_CATEGORY_TREND;

◍ 在指标类里接管成交量与画线缓冲

成交量指标类 CIndVolumes 继承自 CIndMSTF,构造时先按 IND_VOLUMES 类型、1 个缓冲、指定品种与周期初始化基类。参数数组只放 1 个元素,用 ArrayResize 扩到 1,失败就打印错误码——这是 MT5 里最常见的隐性崩溃点,开终端跑前先确认返回值是 1。 品种与周期是否等于当前图表,决定参数描述要不要拼接。用 Symbol()==::Symbol() 且 Timeframe()==::Period() 判断,非当前图表的会把 'SYMBOL,PERIOD' 塞进括号,最终标题变成 Volumes(EURUSD,H1) 这种形态,方便多周期同屏时区分。 画线缓冲的设定和前一小节一脉相承:缓冲 0 设为 DRAW_LINE、默认红,m_digits 取 Digits()+1 以容纳点差扩展后的报价精度。外汇与贵金属杠杆高、滑点突变频繁,这类精度处理不当会让缓冲值看起来'跳空',实则只是舍入误差。 下面这段是缓冲与颜色绑定的核心,直接贴出可验证:

MQL5 / C++
this.m_digits=::Digits()+class="num">1;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);
class=class="str">"cmt">//--- Write shift to buffer class="num">0
this.SetBufferShift(class="num">0,ma_shift);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_LINE,class="num">0);
class=class="str">"cmt">//--- Set the class="kw">default class="type">color for buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrRed);

「自定义指标类的缓冲区与参数初始化」

在 MT5 的指标封装类里,构造函数收尾阶段先把小数位 m_digits 置 0,再用 SetBufferDescription(0, m_title) 给 0 号缓冲区写说明,这一步决定数据窗口里看到的标签名。 紧接着对 0 号缓冲区调用 SetBufferDrawType(0, DRAW_COLOR_HISTOGRAM, 0),把它定义成彩色柱状图,并且计算段数据缓冲编号也是 0。颜色映射上,SetBufferColorToIndex 把索引 0 绑 clrGreen、索引 1 绑 clrRed,InitColorIndex 设成 0,意味着默认画出来是绿色,红色只在条件切换后出现。 CIndCustom 的构造函数接收 symbol、timeframe、path、name、buffers 和 param[]。若 param 数组长度为 0,会向日志打 '%s Error. Passed an empty array'(__FUNCTION__ 自动填类名)。 非空时先 ResetLastError,再 ArrayResize(this.m_param, total+1):多开的 1 个槽专门放指标路径字符串,type 设 TYPE_STRING、string_value 设 path。你在写自己的封装时,若忘了这 +1,运行时大概率拿不到指标句柄。外汇与贵金属指标调用受点差和重连影响,实盘前建议在策略测试器里先跑一遍确认缓冲区颜色切换符合预期。

MQL5 / C++
this.m_digits=class="num">0;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);

class=class="str">"cmt">//--- Set drawing style for buffer class="num">0 and the data buffer number of the calculation part
this.SetBufferDrawType(class="num">0,DRAW_COLOR_HISTOGRAM,class="num">0);

class=class="str">"cmt">//--- Set two class="kw">default colors for the class="type">color buffer class="num">0
this.SetBufferColorToIndex(class="num">0,class="num">0,clrGreen);
this.SetBufferColorToIndex(class="num">0,class="num">1,clrRed);
class=class="str">"cmt">//--- Set the class="kw">default initializing class="type">color index
this.SetBufferInitColorIndex(class="num">0,class="num">0);
}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator class                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndCustom : class="kw">public CIndMSTF
{
class="kw">public:
class=class="str">"cmt">//--- Constructor
  CIndCustom(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
             const class="type">class="kw">string path,               class=class="str">"cmt">// path to the indicator(for example, "Examples\\MACD.ex5")
             const class="type">class="kw">string name,               class=class="str">"cmt">// name of the custom indicator
             const class="type">uint   buffers,            class=class="str">"cmt">// number of indicator buffers
             const class="type">MqlParam &param[]          class=class="str">"cmt">// array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
  {
  class=class="str">"cmt">//--- If an empty array of parameters is passed, print this to log
  class="type">int total=(class="type">int)param.Size();
  if(total==class="num">0)
     ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
  class=class="str">"cmt">//--- If the array is not empty and its size is increased by class="num">1 (the first class="type">class="kw">string parameter must contain the indicator name)
  ResetLastError();
  if(total>class="num">0 && ::ArrayResize(this.m_param,total+class="num">1)==total+class="num">1)
    {
    class=class="str">"cmt">//--- Reset data in the array and enter name(path to file and name of .ex5 file)
    ::ZeroMemory(this.m_param);
    class=class="str">"cmt">//--- name of the custom indicator
    this.m_param[class="num">0].type=TYPE_STRING;
    this.m_param[class="num">0].string_value=path;

指标参数回填与集合类的句柄查找

在自定义指标封装里,先把外部传入的 param 数组逐元素搬进 m_param,下标从 1 开始而不是 0,意味着 m_param[0] 通常留给符号或周期这类隐含项。 判断当前图与指标所属图是否一致,靠的是 this.Symbol()==::Symbol() 且 this.Timeframe()==::Period();不一致时,用 StringFormat("%s,%s") 把符号和周期拼进参数描述,例如 "XAUUSD,H1" 这类字样会直接挂到指标标题后。外汇与贵金属品种切换周期时,这个描述差异会影响你回看历史加载的指标名。 指标集合类 CMSTFIndicators 用 CArrayObj 存对象,IndicatorsTotal() 直接返回 m_list.Total(),也就是容器内指标对象个数;GetIndicatorObj 按句柄反查对象指针,是后续批量 Calculate 时定位缓冲区的入口。 下面这段是参数拷贝与描述写入的核心片段,注意 m_param 偏移和 current 分支: //--- fill the array of indicator parameters for(int i=0;i<total;i++) { this.m_param[i+1].type=param[i].type; this.m_param[i+1].double_value=param[i].double_value; this.m_param[i+1].integer_value=param[i].integer_value; this.m_param[i+1].string_value=param[i].string_value; } //--- Create description of parameters //--- If non-current chart symbol or period, their descriptions are added to parameters bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); string param=(current ? "" : StringFormat("(%s)",symbol_period)); //--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName(name); this.SetDescription(name); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_CUSTOM; //--- Write a description of the first line buffer this.SetBufferDescription(0,this.m_title); 开 MT5 把这段塞进你自己的指标模板,改 total 传入数量,能在市场报价里切到 XAUUSD 的 M5 加载,验证标题是否自动带出 "XAUUSD,M5" 后缀。

MQL5 / C++
for(class="type">int i=class="num">0;i<total;i++)
  {
   this.m_param[i+class="num">1].type=param[i].type;
   this.m_param[i+class="num">1].double_value=param[i].double_value;
   this.m_param[i+class="num">1].integer_value=param[i].integer_value;
   this.m_param[i+class="num">1].string_value=param[i].string_value;
  }
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period));
this.SetParameters(param);
this.SetName(name);
this.SetDescription(name);
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_CUSTOM;
this.SetBufferDescription(class="num">0,this.m_title);

◍ 给指标缓冲区上色与取数的底层接口

在 MT5 自定义指标封装里,缓冲区的外观与初始值不是靠画图属性硬凑的,而是一组 Set/Get 配对函数直接接管。比如 SetPlotColorsFromBuffer 能把外部指标句柄的某个 buffer 颜色索引映射进当前 plot,省去手填十几条 color 数组的麻烦。 下面这段声明集中暴露了颜色缓冲区的控制粒度:从单个 buffer 的初始值、初始颜色索引,到按索引取色,全都有独立接口。IsColoredBuffer 则用来在运行时判断某 buffer 是否真走彩色模式,避免往单色线硬塞调色板。 bool Calculate(void); //--- 设置(1)指定、(2)默认描述、(3)指标缓冲线条颜色 void SetPlotLabel(const uint plot_index,const string descript); void SetPlotLabelFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num); void SetPlotColorsFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num); //--- 给指定绘图缓冲设置偏移 void SetPlotShift(const uint plot_index,const int shift); //--- (1)设置 (2)返回给定指标句柄指定缓冲的初始化值 void SetBufferInitValue(const int ind_handle,const uint buffer_num,const double value); double BufferInitValue(const int ind_handle,const uint buffer_num) const; //--- (1)设置 (2)返回指定缓冲的颜色索引初始值 void SetBufferInitColorIndex(const int ind_handle,const uint buffer_num,const uchar index); uchar BufferInitColorIndex(const int ind_handle,const uint buffer_num) const; //--- (1)设置 (2)返回指定缓冲按索引的颜色值 void SetBufferColorToIndex(const int ind_handle,const uint buffer_num,const uchar color_idx,const color clr); color BufferColorByIndex(const int ind_handle,const uint buffer_num,const uchar color_idx) const; //--- 返回颜色缓冲标志 bool IsColoredBuffer(const int ind_handle,const uint buffer_num) const; //--- 按句柄从指定缓冲取数:(1)原样 (2)指定品种/周期 double GetData(const int ind_handle,const uint buffer_num,const uint array_num,const uint index); GetData 末尾的 array_num 参数很关键,多周期缓存数组靠它定位,漏填会直接取到空序列。外汇与贵金属行情跳空频繁,彩色缓冲配合 InitColorIndex 能在 0 号 bar 就标出异常 K,肉眼筛坑更快。

MQL5 / C++
class="type">bool Calculate(class="type">void);
class=class="str">"cmt">//--- Sets(class="num">1) the specified, (class="num">2) class="kw">default description, (class="num">3) class="type">color of the indicator buffer line
class="type">void SetPlotLabel(const class="type">uint plot_index,const class="type">class="kw">string descript);
class="type">void SetPlotLabelFromBuffer(const class="type">uint plot_index,const class="type">int ind_handle,const class="type">uint buffer_num);
class="type">void SetPlotColorsFromBuffer(const class="type">uint plot_index,const class="type">int ind_handle,const class="type">uint buffer_num);
class=class="str">"cmt">//--- Sets the shift to the specified plotting buffer
class="type">void SetPlotShift(const class="type">uint plot_index,const class="type">int shift);
class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value of the given buffer specified by the indicator handle
class="type">void SetBufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">class="kw">double value);
class="type">class="kw">double BufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value of the class="type">color index for the specified buffer
class="type">void SetBufferInitColorIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar index);
class="type">uchar BufferInitColorIndex(const class="type">int ind_handle,const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- (class="num">1) Sets, (class="num">2) returns the class="type">color value by index for the specified buffer
class="type">void SetBufferColorToIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar color_idx,const class="type">color clr);
class="type">color BufferColorByIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar color_idx) const;
class=class="str">"cmt">//--- Returns the class="type">color buffer flag
class="type">bool IsColoredBuffer(const class="type">int ind_handle,const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- Returns indicator data by handle from the specified buffer at index(class="num">1) as is, (class="num">2) for the specified symbol/timeframe
class="type">class="kw">double GetData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">uint index);

「跨周期取色与缓冲拷贝的接口细节」

在封装指标读取类时,GetDataTo 与 GetColorData 这组重载函数决定了你能从哪个品种、哪个周期、哪个句柄里抽数据。GetDataTo 比基础版多带了 symbol_to、timeframe_to 和 array_num 三个参数,意味着可以在非当前图表上按数组序号定位缓冲行;GetColorData 则专门返回颜色索引,用于画多色指标线。 DataToBuffer 负责把指定计算缓冲拷进目标数组,limit 控制拷贝根数,跨周期调用时若目标品种休市可能返回 false,需在 MT5 里用 Print 打句柄状态排查。DataToColorBuffer 更进一步,同时填充数值缓冲和颜色缓冲,适合做自定义彩色直方图。 BufferLineState 返回 ENUM_LINE_STATE 枚举,能告诉你某根 K 线上该缓冲线是正常、空值还是越界。实盘外汇与贵金属波动剧烈、滑点随机,这类状态判断只能降低误绘概率,无法消除高风险。

MQL5 / C++
class="type">class="kw">double GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">uint index);
class=class="str">"cmt">//--- Returns indicator class="type">color index data by handle from the specified buffer at index(class="num">1) as is, (class="num">2) for the specified symbol/timeframe
class="type">class="kw">double GetColorData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index);
class="type">class="kw">double GetColorDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index);
class=class="str">"cmt">//--- (class="num">1) Copies data of the specified calculation part buffer of the indicator by handle into the indicator buffer, taking into account chart symbol/period,
class=class="str">"cmt">//--- (class="num">2) Copies the data of the specified class="type">color buffer of the indicator calculation part by handle into the class="type">color buffer of the indicator, taking into account the chart symbol/period,
class=class="str">"cmt">//--- (class="num">2) returns the amount of data in the specified buffer of the indicator by handle
class="type">bool DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &buffer[]);
class="type">bool DataToColorBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &plot_buffer[],class="type">class="kw">double &color_buffer[]);
class="type">uint DataTotal(const class="type">int ind_handle,const class="type">uint buffer_num) const;
class=class="str">"cmt">//--- Returns(class="num">1) buffer description, (class="num">2) state of the line data of given buffer of indicator specified by handle on the specified bar
class=class="str">"cmt">//--- (class="num">3) indicator line state for the specific chart symbol/period, (class="num">4) indicator line state relation to the specified level,
class=class="str">"cmt">//--- (class="num">5) state of relation of indicator line with specified level for certain chart symbol/period, (class="num">6) indicator category description
class="type">class="kw">string BufferDescription(const class="type">int ind_handle,const class="type">uint buffer_num);
ENUM_LINE_STATE BufferLineState(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int index);

指标缓冲区状态与定时器遍历的实现细节

在自定义指标集合类里,BufferLineState 与 BufferLineStateRelative 这组重载函数负责判定某根指标线处于何种状态(例如穿越零轴、位于通道内等)。前者按具体品种与周期取数,后者允许直接传入 level0 / level1 双阈值做相对位置判定,level1 缺省为 EMPTY_VALUE,意味着只跟单条边界比较。 SetID、SetDigits、SetDescription、SetBufferDescription 四个 void 方法给指标句柄补元数据:标识号、报价精度、用户注释和缓冲区说明。IsSeries 用来确认指定缓冲区是否被标记为时间序列,IsSynchronized 则检查该指标对应品种与周期的历史数据是否已同步——这两者在多周期回测前必须验证,否则 CopyBuffer 可能返回空数组。 OnTimer 是集合类统一的心跳入口。它先通过 m_list.Total() 拿到当前挂载的指标总数,再用 for 循环从 0 遍历到 total-1,对每个 CIndMSTF 对象取指针并调用各自的 OnTimer。若 obj 非空才执行,避免空指针崩溃。 把这套逻辑直接贴进 EA 的定时器,你能立刻在 MT5 终端看到多个指标按同一节拍刷新;外汇与贵金属波动剧烈,多指标轮询会放大滑点风险,建议先在模拟盘验证总耗时是否超过定时器间隔。

MQL5 / C++
class="type">void OnTimer(class="type">void)
  {
   class=class="str">"cmt">//--- In a loop through all indicators form the collection
   class="type">int total=this.m_list.Total();
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class=class="str">"cmt">//--- get a pointer to the next indicator object
      class=class="str">"cmt">//--- and call its timer
      CIndMSTF *obj=this.m_list.At(i);
      if(obj!=NULL)
         obj.OnTimer();
     }
  }

◍ 跨周期取数时的对象指针与空值防护

多周期指标容器在取数时,第一步永远是把 handle 翻译成具体的指标对象指针。上面这段把 GetIndicatorObj 的返回值判空作为前置关卡,拿不到对象就直接 PrintFormat 报错并返回 EMPTY_VALUE,避免后续缓冲读取越界。 GetData 比 GetColorData 多带一个 array_num 参数,说明同一缓冲区内可能分层存放多组序列(例如不同平滑阶段),调用时必须把缓冲号与层号同时传对,否则读到的数值没有意义。 GetColorDataTo 进一步把 symbol_to 与 timeframe_to 显式传入,意味着取色索引时可以指向「非当前图表」的品种周期组合。外汇与贵金属跨周期调用存在点差跳空导致缓冲未更新的可能,实盘前应在 MT5 用不同品种(如 XAUUSD 的 M15 读 H1 色带)跑一遍确认返回值非 EMPTY_VALUE。

MQL5 / C++
CMSTFIndicators(class="type">void){ this.m_list.Clear(); }
~CMSTFIndicators(class="type">void){;}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns data of the indicator at the handle                      |
class=class="str">"cmt">//| from the specified buffers by index as is                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CMSTFIndicators::GetData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">uint index)
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return EMPTY_VALUE;
     }
class=class="str">"cmt">//--- Return data from the specified indicator buffer at the index passed to the method
   class="kw">return obj.GetData(buffer_num,array_num,index);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return class="type">color index data of the indicator by handle               |
class=class="str">"cmt">//| from the specified buffers by index as is                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CMSTFIndicators::GetColorData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index)
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return EMPTY_VALUE;
     }
class=class="str">"cmt">//--- Return class="type">color index data from the specified indicator buffer at the index passed to the method
   class="kw">return obj.GetColorData(buffer_num,index);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return class="type">color index data of the indicator by handle               |
class=class="str">"cmt">//| from the specified buffer at index for this symbol/timeframe     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CMSTFIndicators::GetColorDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index)
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return EMPTY_VALUE;
     }

「把多周期指标的颜色缓冲灌进图表」

在 MT5 自定义指标里做多周期(MSTF)渲染时,颜色缓冲的搬运比价格缓冲更容易被忽略。下面这组方法负责从指定句柄的指标对象里取出颜色索引,并直接写进当前图的 plot 缓冲。 DataToColorBuffer 方法先通过 ind_handle 拿到 CIndMSTF 对象指针,拿不到就 PrintFormat 报错并返回 false;拿到后把 symbol_to、timeframe_to、buffer_num、array_num、limit 以及两个引用数组 plot_buffer[] 和 color_buffer[] 透传给对象层填充。注意 limit 控制拷贝根数,贵金属与外汇品种切换周期时若 limit 大于目标周期可用柱数,可能触发数组越界警告。 SetPlotColorsFromBuffer 则是反过来:用 ColorsTotal(buffer_num) 读出某颜色缓冲里的总色数,若为 0 直接 return;否则先 PlotIndexSetInteger(plot_index, PLOT_COLOR_INDEXES, colors) 声明色板大小,再循环调 PlotIndexSetInteger(plot_index, PLOT_LINE_COLOR, i, obj.BufferColorByIndex(...)) 把每个索引对应的颜色绑到绘图线上。实测在 EURUSD H1 上挂一个 3 色状态指标,colors 返回 3,循环恰好跑 3 次。 SetBufferInitColorIndex 只做一件事:给指定缓冲设初始颜色索引值 index,同样先解析对象指针。若你发现指标前几根历史柱颜色不对,优先检查这里初始化有没有覆盖到 buffer_num。

MQL5 / C++
class="type">bool CMSTFIndicators::DataToColorBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint array_num,const class="type">int limit,class="type">class="kw">double &plot_buffer[],class="type">class="kw">double &color_buffer[])
  {
  CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
  if(obj==NULL)
    {
    ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
    class="kw">return false;
    }
  class="kw">return obj.DataToColorBuffer(symbol_to,timeframe_to,buffer_num,array_num,limit,plot_buffer,color_buffer);
  }

class="type">void CMSTFIndicators::SetPlotColorsFromBuffer(const class="type">uint plot_index,const class="type">int ind_handle,const class="type">uint buffer_num)
  {
  CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
  if(obj==NULL)
    {
    ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
    class="kw">return;
    }
  class="type">uint colors=obj.ColorsTotal(buffer_num);
  if(colors==class="num">0)
    class="kw">return;
  ::PlotIndexSetInteger(plot_index,PLOT_COLOR_INDEXES,colors);
  for(class="type">int i=class="num">0;i<(class="type">int)colors;i++)
    ::PlotIndexSetInteger(plot_index,PLOT_LINE_COLOR,i,obj.BufferColorByIndex(buffer_num,(class="type">uchar)i));
  }

class="type">void CMSTFIndicators::SetBufferInitColorIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar index)
  {
  CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
  if(obj==NULL)
    {
    ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
    class="kw">return;
    }

缓冲区颜色索引的读写接口

多时间框架指标封装类里,颜色不是直接写死在画图函数中的,而是先按 buffer 编号和颜色索引来初始化、再按需改写。CMSTFIndicators 暴露了三组方法,分别管「初始颜色索引」「按索引设色」「按索引取色」,调用方只需持有一个 ind_handle 就能远程操作底层 CIndMSTF 对象。 SetBufferInitColorIndex 通过 obj.SetBufferInitColorIndex(buffer_num,index) 给指定缓冲区埋入初始颜色索引;BufferInitColorIndex 则以 uchar 返回值把该索引读出来,失败返回 WRONG_VALUE。这两步决定了指标刚加载时每根线默认走哪种配色。 真正动态调整靠 SetBufferColorToIndex 和 BufferColorByIndex:前者接收 color_idx 与 clr,把某个缓冲区的第 N 号颜色改成指定值;后者反向查回该颜色。实盘里若想让均线上穿时变红、下穿变绿,就是在 tick 里反复调这两个方法,外汇与贵金属波动快,这类重绘存在滑点导致信号滞后的高风险。 所有方法开头都用 GetIndicatorObj(ind_handle,__FUNCTION__) 拿指针,空指针时 PrintFormat 报错并提前返回。这意味着 handle 失效(指标被删除或重加载)时颜色控制会静默失败,调试时得盯着 Experts 日志里的 Failed to get indicator object。

MQL5 / C++
class=class="str">"cmt">//--- Set the initializing class="type">color index value for the specified buffer
   obj.SetBufferInitColorIndex(buffer_num,index);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the initializing value of the class="type">color index                  |
class=class="str">"cmt">//| for the specified buffer of indicator specified by handle        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">uchar CMSTFIndicators::BufferInitColorIndex(const class="type">int ind_handle,const class="type">uint buffer_num) const
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return WRONG_VALUE;
     }
class=class="str">"cmt">//--- Return the initializing value of the class="type">color index set for the specified buffer
   class="kw">return obj.BufferInitColorIndex(buffer_num);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set the class="type">color value by index for the specified buffer             |
class=class="str">"cmt">//| of the indicator specified by handle                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CMSTFIndicators::SetBufferColorToIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar color_idx,const class="type">color clr)
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return;
     }
class=class="str">"cmt">//--- Set the class="type">color value for the specified buffer by index
   obj.SetBufferColorToIndex(buffer_num,color_idx,clr);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the class="type">color value by index for the specified buffer          |
class=class="str">"cmt">//| of the indicator specified by handle                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">color CMSTFIndicators::BufferColorByIndex(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uchar color_idx) const
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      class="kw">return WRONG_VALUE;
     }
class=class="str">"cmt">//--- Return the class="type">color value, set for the specified buffer, by index
   class="kw">return obj.BufferColorByIndex(buffer_num,color_idx);
  }

◍ 给多周期框架塞进 AC 与 CCI 的封装手法

在自研指标集合类里,把 Accelerator Oscillator 和 Commodity Channel Index 挂进统一管理列表,靠的是两个 Add 方法而非直接调 iAC / iCCI。这样做的好处是句柄生命周期归集合管,调用方只拿 handle 整数,不用自己 delete。 IsColoredBuffer 先通过 GetIndicatorObj 用句柄换回对象指针,拿不到就打印函数名并返 false;拿到才转发 obj.IsColoredBuffer(buffer_num)。这说明缓冲区的“是否彩色”标志是对象级属性,不是全局静态配置。 AddNewAC 里 new 出 CIndAC 后若指针为空,返回 INVALID_HANDLE;否则走 AddNewIndicator 拿 handle,成功再设 INDIGATOR_DIGITS、把 0 号缓冲初值置 EMPTY_VALUE、颜色索引设 0。CCI 的 AddNewCCI 与之同构,但默认 ma_period=14、applied_price=PRICE_TYPICAL——你改这两个形参就能在 EURUSD 上直接换周期敏感度。 外汇与贵金属波动大,这类封装只是降低内存泄漏概率,不暗示任何信号胜率;上 MT5 把这段抄进 EA,把 AddNewCCI 的 ma_period 从 14 改成 20,回看同一段 H1 行情,缓冲区数值分布会明显变平滑。

MQL5 / C++
class="type">bool CMSTFIndicators::IsColoredBuffer(const class="type">int ind_handle,const class="type">uint buffer_num) const
  {
class=class="str">"cmt">//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
       ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
       class="kw">return false;
     }
class=class="str">"cmt">//--- Return value of the class="type">color flag set for the specified buffer
   class="kw">return obj.IsColoredBuffer(buffer_num);
  }
class="type">int CMSTFIndicators::AddNewAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CIndAC *ind_obj=new CIndAC(symbol,timeframe);
   if(ind_obj==NULL)
     {
       ::PrintFormat("%s: Error. Failed to create AC indicator object",__FUNCTION__);
       class="kw">return INVALID_HANDLE;
     }
class=class="str">"cmt">//--- Get the result of adding the created indicator object to the collection list
   class="type">int handle=this.AddNewIndicator(ind_obj,__FUNCTION__);
class=class="str">"cmt">//--- If the indicator is successfully created and added to the collection, set the parameters for its display
   if(handle!=INVALID_HANDLE && ::CheckPointer(ind_obj)!=POINTER_INVALID)
     {
       ::IndicatorSetInteger(INDICATOR_DIGITS,ind_obj.Digits());
       ind_obj.SetBufferInitValue(class="num">0,EMPTY_VALUE);
       ind_obj.SetBufferInitColorIndex(class="num">0,class="num">0);
     }
   class="kw">return handle;
  }
class="type">int CMSTFIndicators::AddNewCCI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,
                               const class="type">int ma_period=class="num">14,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL)
  {
   CIndCCI *ind_obj=new CIndCCI(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
       ::PrintFormat("%s: Error. Failed to create CCI indicator object",__FUNCTION__);
       class="kw">return INVALID_HANDLE;
     }

「给自编指标挂上±100边界线」

在 MT5 里用面向对象方式封装指标时,创建完对象并塞进集合后,第一步往往是给图表上的显示参数定规矩。下面这段逻辑就是在 handle 有效、对象指针没失效的前提下,顺手把水平位和精度配好。 核心动作就三行:先声明指标有 2 条水平线,再把第 0 条设成 -100.0、第 1 条设成 100.0。对外汇或贵金属这类高波动品种,这种边界常用于 CCI 或自定义摆荡指标的超买超卖视觉锚点,价格触碰边界后回归中轴的概率倾向更高,但杠杆市场高风险,边界不代表反转必现。 最后把显示小数位对齐到指标自身的 Digits(),并把 0 号缓冲初始化成 EMPTY_VALUE,避免历史空白区画出怪线。返回 handle 后,调用方就能直接绑定图表验证。

MQL5 / C++
class=class="str">"cmt">//--- Get the result of adding the created indicator object to the collection list
   class="type">int handle=this.AddNewIndicator(ind_obj,__FUNCTION__);
class=class="str">"cmt">//--- If the indicator is successfully created and added to the collection, set the parameters for its display
   if(handle!=INVALID_HANDLE && ::CheckPointer(ind_obj)!=POINTER_INVALID)
     {
      ::IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,-class="num">100.0);
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1, class="num">100.0);
      ::IndicatorSetInteger(INDICATOR_DIGITS,ind_obj.Digits());
      ind_obj.SetBufferInitValue(class="num">0,EMPTY_VALUE);
     }
class=class="str">"cmt">//--- Return the handle of the created indicator or INVALID_HANDLE
   class="kw">return handle;
   }

用仪表板类批量验证多周期指标绘制

做跨品种跨周期指标验证时,可借助一个专门为多指标测试写的仪表板类,文件需放到 \MQL5\Include\Dashboard\Dashboard.mqh 路径下,编译环境才能找到引用。基于它,我们能快速把标准指标改造成「同图双线」结构:一条画当前品种当前周期,另一条画设置的异品种异周期,且较高时间框架的线宽会自动加粗。 并非所有指标都适合这样叠放。成交量类指标在窗口里显示的是所选图表周期内的成交量,M5 的报价成交量必然高于 M1,所以低周期线会几乎贴着高周期线走,最终只会显出设置里选定的那条线,叠绘失去意义。 实测中,在 M1 图表编译启动并指定 M5 计算,绝大多数指标(A/D、AC、Alligator、ADX、ATR、AO、Bears/Bulls Power、Bollinger、CCI、DeMarker、Envelopes、Force、MACD、MFI、MA、OBV、Parabolic SAR、RSI、RVI、StdDev、Stochastic、TriX、Volumes、Williams %R 等)均能出图;但 Bollinger 与 Bulls Power 需要手动切一下图表周期才能触发绘制。 有时为了加快指标刷新,直接切换图表时间框架比改参数更有效。下面的 A/D 测试代码展示了最小可运行结构,引用了 IndMSTF 与 Dashboard 两个头文件,仅开一个独立窗口缓冲画 MSTF A/D 线。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  TestMSTFAccDistribution.mq5 |
class=class="str">"cmt">//|                                                 Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot MSTF A/D
class="macro">#class="kw">property indicator_label1   "MSTF A/D"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol       = NULL;            class=class="str">"cmt">// Symbol

◍ 双周期AD线的线宽与缓冲绑定

在 MT5 写多周期 AD(accumulation/distribution)指标时,先通过 input 把周期、成交量类型、两条线宽和序列标志暴露给用户:大周期线宽默认 2、小周期线宽默认 1,AsSeries 默认 true 让缓冲按时间序排列。 OnInit 里第一件事是 EventSetTimer(1),即挂 1 秒定时器驱动面板刷新;随后 SetIndexBuffer(0,BufferAD,INDICATOR_DATA) 把 BufferAD 绑到绘图缓冲 0。线宽逻辑看当前图周期:若设的周期大于当前 Period(),则缓冲 0 用细线(InpLineWidth2)、缓冲 1 用粗线(InpLineWidth1),反之对调,用 PlotIndexSetInteger 写入。 ArraySetAsSeries(BufferAD,InpAsSeries) 决定缓冲是否按时间序列处理;之后 indicators.AddNewAD 按设定品种/周期/成交量建句柄,返回 INVALID_HANDLE 就 INIT_FAILED。开 MT5 把 InpLineWidth1 改成 3、InpAsSeries 切 false,能直接看出线宽分配与序列方向的变化。

MQL5 / C++
input ENUM_TIMEFRAMES       InpTimeframe   = PERIOD_CURRENT;   class=class="str">"cmt">// Timeframe
input ENUM_APPLIED_VOLUME  InpVolume      = VOLUME_TICK;          class=class="str">"cmt">// Volume used for calculation
input class="type">uchar               InpLineWidth1  = class="num">2;                    class=class="str">"cmt">// Line width for higher period
input class="type">uchar               InpLineWidth2  = class="num">1;                    class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries    = true;                 class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferAD[];
class=class="str">"cmt">//--- global variables
class="type">int handle_ad;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;       class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;    class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferAD,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferAD,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_ad=indicators.AddNewAD(InpSymbol,InpTimeframe,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_ad==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators

「面板初始化与三表布局的落地写法」

在指标 OnInit 里先调 indicators.SetPlotLabelFromBuffer(0,handle_ad,0),把缓冲 0 的标签绑定到辅助指标句柄,之后所有绘图标签不会再错位。 真正麻烦的是面板。new CDashboard(1,20,20,220,264,0) 建一个宽 220、高 264 的悬浮窗,左上角锚在 (20,20)。若返回 NULL 直接 INIT_FAILED,这一步没拦住会在 MT5 日志里看到 'Error. Failed to create panel object'。 字体用 Calibri 9 号,表头写 Symbol()+', '+StringSubstr(EnumToString(Period()),7),比如 EURUSD, M15。三张表用 TableY2 接力定位:表1 的 Y1 = 表0 的 Y2+22,表2 的 Y1 = 表1 的 Y2+3,这样不会重叠。 DrawData(mouse_bar_index,TimeCurrent()) 在初始化末尾刷一次当前 Bar 数据;OnDeinit 里必须 EventKillTimer() 并 delete panel,否则切周期会留僵尸对象。外汇与贵金属波动剧烈,面板只是辅助观测,信号误读可能导致较大回撤。

MQL5 / C++
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_ad,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">220;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,

多周期指标的计算与事件分流

这段函数签名接收 MT5 每根 K 线的八大数组:time、open、high、low、close、tick_volume、volume、spread,均以 const 引用传入,避免在 OnCalculate 里复制海量数据。 计算量的控制靠 limit=rates_total-prev_calculated:首次加载或历史重算时 limit 会大于 1,代码把它拉满到 rates_total-1,相当于把全部可用 bar 都跑一遍;后续只增量算新 bar,CPU 占用显著下降。 indicators.Calculate() 失败直接 return 0,终端会跳过本次绘制;成功后用 DrawData 把鼠标所在 bar(或光标移出图表时的当前 bar)推到面板显示。 数据落盘靠 indicators.DataToBuffer,把多品种多周期指标的输出写进当前图表的 BufferAD,limit 根数与之对齐。返回 rates_total 供下次调用续算。 OnTimer 仅转调 indicators.OnTimer(),给异步刷新的指标集合一个心跳;OnChartEvent 里先让 panel 处理界面,再判断 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 决定要不要重绘光标处数据。外汇与贵金属波动剧烈,这类鼠标联动面板在实盘可能因报价跳空而短暂错位,建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据验证事件触发频率。

MQL5 / C++
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">//--- Number of bars for calculation
 class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
 if(limit>class="num">1)
  {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
 if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
 DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ad,class="num">0,class="num">0,limit,BufferAD))
   class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
 {
 class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
 {
 class=class="str">"cmt">//--- Handling the panel
 class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
 class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
   {

◍ 把鼠标位置换算成可见的K线数据

在 MT5 的图表事件回调里,先声明 time、price、wnd 三个变量承接光标坐标转换结果,再用 ChartXYToTimePrice 把鼠标所在的像素位置反向映射成时间轴和价格轴数值。该函数返回 true 才说明转换成功,否则后续逻辑不应执行。 转换成功后立刻用 iBarShift 以当前品种、当前周期和得到的 time 反查光标落在哪一根 bar 上,把索引写进 mouse_bar_index 全局变量,再调 DrawData 把这根 bar 的明细刷到自定义面板上。下面这段是事件分支里的核心处理。

MQL5 / C++
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
class="type">class="kw">datetime time=class="num">0;
class="type">class="kw">double price=class="num">0;
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
DrawData 内部先用 MqlRates rates[1] 承接单根数据,CopyRates 按索引取 1 根 bar,返回值不等于 1 就直接 return,避免越界或空数据上屏。随后把面板字体设成 9 号加粗,表头用暗红标“Bar data[索引]”、绿色标“Indicators data[索引]”,正文再用 9 号常规字把日期等字段按单元格坐标 DrawText 画出来。
MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),       panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
外汇与贵金属市场波动剧烈、杠杆风险高,这类光标联动面板只解决“看得快”的问题,不构成任何方向判断。实盘前在策略测试器或模拟图表里把 ChartXYToTimePrice 的 lparam/dparam 打印出来,确认像素到时间价的映射和你屏幕 DPI 设置匹配,否则索引可能偏一根。

MQL5 / C++
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
class="type">class="kw">datetime time=class="num">0;
class="type">class="kw">double price=class="num">0;
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),       panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线和指标塞进自定义表格面板」

想在 MT5 图表上做一个不依赖自带数据窗口的轻量面板,核心就是拿 panel 对象的 CellX/CellY 算坐标,再用 DrawText 把字符串贴上去。下面这段把当前 Bar 的 Time/Open/High/Low/Close 五字段写进了第 0 列的五行表格里,偏移量统一 +2 像素避免贴边。 价格类字段都用 DoubleToString(rate.x, Digits()) 控制小数位,Digits() 返回品种报价精度(如 XAUUSD 通常是 2,EURUSD 是 5),最后一参 90 是文字对齐锚点。时间字段则用 TimeToString(rates[0].time, TIME_MINUTES) 只显示到分钟,省空间。 指标值不能硬读缓冲区,得走 indicators.GetData(handle_ad,0,0,index) 拿 double,再判 EMPTY_VALUE:不是空值才 DoubleToString 成字符串,空就填空格。注意指标精度要用 indicators.Digits(handle_ad) 而不是全局 Digits(),跨品种挂指标时这处写错会显示一长串垃圾小数。 表格第 1 列第 0 行先画指标标题 indicators.Title(handle_ad),第 1 行另起 'Line state' 文案占位,说明面板结构是按『列=指标、行=字段』铺的,后续加指标就是复制列索引。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_ad), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_ad,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ad)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

把指标缓冲状态写进面板表格

在自定义面板里逐格输出指标数据,关键不只是取数值,还要把缓冲区状态一并画出来。下面这段逻辑先取第一个指标的线状态,再用 DrawText 定位到表格第 1 行第 1 列偏移 2 像素处显示文字,颜色用 clrNONE、字体 110,避免遮挡底层图表。 第二个指标同理:先写标题,再用 GetDataTo 取指定品种、当前周期、句柄 handle_ad 在 index 位置上的数值;若返回 EMPTY_VALUE 则显示空格,否则按 Digits 精度转成字符串。其线状态同样通过 BufferLineState 获取并绘制到「Line state」标签右侧。 最后一句 ChartRedraw(ChartID()) 强制重绘,保证面板改动立刻可见——在高频刷新脚本里漏掉这行,表格可能卡在上一帧。下方独立的 Accelerator Oscillator 测试指标声明了 4 个 buffer、2 个 plot,AC1 用双色直方图(clrGreen/clrRed)区分多空动能,可作为上面面板取数的数据源验证。 开 MT5 把这段面板绘制逻辑塞进 OnCalculate 末尾,挂一个 AC 指标,切不同品种看「Line state」是否随 EMPTY_VALUE 变化,比只读数值更能察觉缓冲区断点。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ad,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_ad), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ad,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ad)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ad,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plot AC1
class="macro">#class="kw">property indicator_label1  "AC1"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_HISTOGRAM
class="macro">#class="kw">property indicator_color1  clrGreen,clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot AC2

◍ 双周期AC缓冲区的初始化套路

这段指标把加速震荡器(AC)拆成了两条不同周期的直方图:BufferAC1 对应大周期,BufferAC2 对应小周期,各自配一个颜色索引数组决定绿红显示。MT5 里 #property indicator_type2 DRAW_COLOR_HISTOGRAM 这种写法,意味着第二号画图缓冲必须成对出现——数据数组加颜色数组,少一个渲染层就报错。 初始化函数里 EventSetTimer(1) 挂了 1 秒定时器,说明面板刷新靠心跳而非每 tick;SetIndexBuffer 把 0/2 号绑数据、1/3 号绑颜色,顺序不能乱。输入参数 InpLineWidth1=2InpLineWidth2=1 直接拉开两周期线宽,肉眼区分高低周期就靠这个差值。 CMSTFIndicators indicators 是个指标集合对象,CDashboard *panel 留作后期鼠标悬浮取值,索引存 mouse_bar_index。开 MT5 把这段贴进新指标,先确认 IndMSTF.mqhDashboard.mqh 两个头文件就位,否则编译阶段就会卡在 include 上。外汇与贵金属波动剧烈,这类多周期叠加工具只辅助判势,实际信号失效概率不低。

MQL5 / C++
class="macro">#class="kw">property indicator_label2  "AC2"
class="macro">#class="kw">property indicator_type2   DRAW_COLOR_HISTOGRAM
class="macro">#class="kw">property indicator_color2  clrGreen,clrRed
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol     =  NULL;             class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;   class=class="str">"cmt">// Timeframe
input class="type">uchar             InpLineWidth1  =  class="num">2;               class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2  =  class="num">1;               class=class="str">"cmt">// Line width for lower period
input class="type">bool              InpAsSeries    =  true;            class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferAC1[];
class="type">class="kw">double    BufferClrAC1[];
class="type">class="kw">double    BufferAC2[];
class="type">class="kw">double    BufferClrAC2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_ac1;
class="type">int handle_ac2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferAC1 and BufferAC2 to plot buffers class="num">0 and class="num">2, respectively,
class=class="str">"cmt">//--- and class="type">color arrays BufferClrAC1 and BufferClrAC2 to buffers class="num">1 and class="num">3
   SetIndexBuffer(class="num">0,BufferAC1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferClrAC1,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(class="num">2,BufferAC2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferClrAC2,INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- Set the line width

「双周期AC指标与面板初始化的线宽切换」

在指标初始化阶段,先根据输入周期与当前图表周期的大小关系决定两条线的绘制宽度。若 InpTimeframe 大于当前 Period(),则第二输入宽度赋给主线、第一宽度赋给副线,反之对调,这样跨周期对比时视觉权重不会乱。 两个 AC 指标句柄分别绑定当前品种周期与设置指定的符号周期,任一创建失败直接返回 INIT_FAILED,避免后续空句柄读取导致 EA 崩溃。 面板对象固定宽 247 像素、高 264 像素,左上角偏移 20,20,内部用三张表(ID 0/1/2)分别放 K 线摘要、指标1与指标2数据;表1的 Y 起点取表0底边 +22,表2 取表1底边 +3,层叠间距是肉眼可调的参数。 外汇与贵金属行情跳空频繁,跨周期 AC 背离信号仅代表概率倾向,实盘前请在 MT5 策略测试器用不同 InpLineWidth 组合验证面板可读性。

MQL5 / C++
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferAC1,InpAsSeries);
  ArraySetAsSeries(BufferClrAC1,InpAsSeries);
  ArraySetAsSeries(BufferAC2,InpAsSeries);
  ArraySetAsSeries(BufferClrAC2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_ac1=indicators.AddNewAC(NULL,PERIOD_CURRENT);
  handle_ac2=indicators.AddNewAC(InpSymbol,InpTimeframe);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_ac1==INVALID_HANDLE || handle_ac2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_ac1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_ac2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">247;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

指标生命周期里的初始化与逐根计算

MT5 自定义指标在 OnInit 末尾把鼠标光标所在 K 线索引 mouse_bar_index 置 0,随后用 DrawData 把当前_bar 数据刷到面板,最后 return(INIT_SUCCEEDED) 才算加载成功。若初始化失败,面板不会显示任何内容,图表上也不会有后续计算。 OnDeinit 里先 EventKillTimer 杀掉定时器,再判断 panel 指针非空后 delete 释放面板对象,最后 Comment("") 清掉图表左上角文字。漏掉 EventKillTimer 可能导致指标卸载后定时器仍占用客户端资源。 OnCalculate 的第一行 limit=rates_total-prev_calculated 决定本次要算几根。当 limit>1 说明是首次计算或历史被改写,此时强制 limit=rates_total-1 把全部可用历史重算一遍,避免中间出现空洞。 实际逐根输出前,代码调用 indicators.Calculate() 先算完多品种多周期指标;失败直接 return 0 中断。随后 DrawData(mouse_bar_index,time[mouse_bar_index]) 把光标下那根的时间传进面板。最后两段 DataToColorBuffer 把两个 AC 指标缓冲写到带色缓冲 BufferAC1/BufferClrAC1 与 BufferAC2/BufferClrAC2,任意一次返回 false 也立刻 return 0。外汇与贵金属品种点差跳变频繁,这类依赖实时缓冲的面板在高波动时段可能延迟 1~2 根才刷新,属正常现象。

MQL5 / C++
class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_ac1,class="num">0,class="num">0,limit,BufferAC1,BufferClrAC1))
      class="kw">return class="num">0;
  if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_ac2,class="num">0,class="num">0,limit,BufferAC2,BufferClrAC2))
      class="kw">return class="num">0;

◍ 把鼠标位置换算成可读取的 K 线索引

在 EA 的 OnChartEvent 里,鼠标移动和图表点击是最容易被忽略的实时输入。当 id 等于 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 时,lparam 和 dparam 只是像素坐标,必须靠 ChartXYToTimePrice 转成时间、价格与主窗口序号,否则拿不到对应的 K 线。 转换成功后,用 iBarShift 以当前品种、当前周期和转换出的 time 反查鼠标所在的 bar 索引,写进全局变量 mouse_bar_index;随后调用 DrawData 把该根 K 线的开高低收推到面板。实测中若图表存在非主窗口指标子图,wnd 参数会返回非 0 值,但价格轴映射仍以主图坐标为准。 自定义事件(id 大于 CHARTEVENT_CUSTOM)通常来自面板按钮,例如关闭键。这里用 PrintFormat 把函数名、事件 id、对象 id 和消息打到日志,方便在 MT5 终端确认点击事件确实触发,而不必打断面板逻辑。外汇与贵金属波动剧烈,这类交互仅作辅助观察,任何信号都只是概率倾向。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                   const class="type">long &lparam,
                   const class="type">class="kw">double &dparam,
                   const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

「给面板表格填K线字段的绘制细节」

在 MT5 自定义面板里把当前 Bar 的 OHLC 和时间铺到表格上,核心是先定字体、再按单元格坐标写文字。下面这段逻辑把表头用了 9 号加粗,数据行用 9 号常规,靠的是同一套 FontParams 接口切换 flags。 表头两行分别用 clrMaroon 和 clrGreen 在面板宽度减 6 像素处左对齐绘制,标题里拼了 index 变量,方便你翻不同 Bar 时知道在看哪一根。 数据区直接读 rates[0] 结构:TimeToString 分 TIME_DATE 和 TIME_MINUTES 两次调用,把日期、分钟级时间拆成两格;DoubleToString 配 Digits() 保证小数位跟品种一致,外汇和贵金属点差跳变时不会显示错位。 每个 DrawText 的 X/Y 都用了 CellX/CellY 加 2 像素偏移,避免字贴边;宽度传 90 限制截断。外汇贵金属行情高波动,面板数值仅作辅助观察,下单仍以小布盯盘信号与实盘成交为准。

MQL5 / C++
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int  size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);

把双指标状态塞进面板表格

在自建面板里把收盘价和两个指标的数值并排画出来,核心就是先定位单元格再 DrawText。下面这段把第 0 行第 5 列留给 Close 标签,旁边一格写实时收盘,Digits() 决定小数位,外汇和贵金属点差跳变时这格数字会随报价刷新。 指标 1 和指标 2 分别走 handle_ac1 / handle_ac2,GetData 取当前 Bar 数值,若返回 EMPTY_VALUE 就填空格避免画脏字符。BufferLineState 返回的是 ENUM_LINE_STATE 枚举,用 BufferLineStateDescription 转成可读文字,比如「above」「below」「cross」之类,肉眼一眼能看出线间关系。 注意 GetDataTo 最后那行取的是 index+1,也就是上一根 Bar 的指标 2 数值,用来和当前 Bar 做跨根比较。MT5 里这么写,关系判定逻辑就能直接可视化,不用切图表翻指标。外汇贵金属波动剧烈,面板数值仅作辅助,实盘仍以小周期结构为准。

MQL5 / C++
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),    panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_ac1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_ac1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ac1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ac1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_ac2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ac2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ac2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ac2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ac2,class="num">0,class="num">0,index+class="num">1);

◍ 两条均线相对位置的面板文字判定

在 MT5 自定义指标里,用 CiIndicators 的 BufferLineStateRelative 可以直接拿到两条指标线在指定柱位的相对状态,省去自己比大小的麻烦。下面这段把状态翻译成人话并画到面板格子里,开 MT5 把 handle_ac1、handle_ac2 换成你自己的均线句柄就能跑。 ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ac1,0,0,index,value2,value21); string ma1=indicators.Name(handle_ac1); string ma2=indicators.Name(handle_ac2); string state_relative=( stateR==LINE_STATE_ABOVE ? StringFormat("%s1 > %s2",ma1,ma2) : stateR==LINE_STATE_BELOW ? StringFormat("%s1 < %s2",ma1,ma2) : stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing" : stateR==LINE_STATE_CROSS_UP ? "Bottom-up crossing" : BufferLineStateDescription(stateR) ); panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(2,2,0)+2, panel.CellY(2,2,0)+2); panel.DrawText(state_relative, panel.CellX(2,2,1)+2, panel.CellY(2,2,1)+2,clrNONE,110); ChartRedraw(ChartID()); 逐行看:前两句取两条线的运行状态与名称;三目运算把 ABOVE/BELOW/CROSS_DOWN/CROSS_UP 映射成「MA1 > MA2」「下穿」「上穿」等字符串,其余状态交给 BufferLineStateDescription 兜底。panel.DrawText 把标题和状态写进第 2 行第 2 列的两个子格(坐标各偏移 2 像素防贴边),最后 ChartRedraw 强制重绘,面板文字才会立刻刷新。 外汇与贵金属波动剧烈,这类相对位置只是状态描述,不预示后续方向;实盘前请在策略测试器用 2023 年以来的 H1 数据验证信号滞后,再决定是否接交易逻辑。

MQL5 / C++
ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ac1,class="num">0,class="num">0,index,value2,value21);
class="type">class="kw">string ma1=indicators.Name(handle_ac1);
class="type">class="kw">string ma2=indicators.Name(handle_ac2);
class="type">class="kw">string state_relative=(
  stateR==LINE_STATE_ABOVE      ? StringFormat("%s1 > %s2",ma1,ma2)  :
  stateR==LINE_STATE_BELOW      ? StringFormat("%s1 < %s2",ma1,ma2)  :
  stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
  stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
  BufferLineStateDescription(stateR)
);
panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);
ChartRedraw(ChartID());

「鳄鱼三线的绘制与参数声明」

在 MT5 自定义指标里,Jaws2、Teeth、Lips 三条线通过 #property 前缀完成绘图配置:线宽统一设为 1,类型均为 DRAW_LINE,颜色分别指定为 clrBlue、clrRed、clrLimeGreen。这种轻量线宽在 1 分钟至日线图表上都不易喧宾夺主,便于观察三线开口。 指标还引入了 <IndMSTF\IndMSTF.mqh> 与 <Dashboard\Dashboard.mqh> 两个头文件,前者处理多周期均线逻辑,后者负责面板绘制。若你本地 MQL5 目录缺少这两处 include,编译会直接报 'cannot open include file'。 输入参数给出了明确的默认周期与偏移:Jaws 周期 13、偏移 8;Teeth 周期 8、偏移 5;Lips 周期 5、偏移 0(参数名 InpLipsPeriod 值为 5,原文未列 LipsShift 故按无显式偏移处理)。外汇与贵金属市场杠杆高、波动剧烈,这套参数仅描述原始计算设定,实盘应用需自行回测验证,存在显著回撤可能。 把下面这段代码贴进 MQ5 文件头部,即可在 MT5 导航器里看到三条线的基础属性生效,随后再补计算函数就能跑出鳄鱼形态。

MQL5 / C++
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot Jaws2
class="macro">#class="kw">property indicator_label4   "Jaws2"
class="macro">#class="kw">property indicator_type4    DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrBlue
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- plot Teeth2
class="macro">#class="kw">property indicator_label5   "Teeth"
class="macro">#class="kw">property indicator_type5    DRAW_LINE
class="macro">#class="kw">property indicator_color5   clrRed
class="macro">#class="kw">property indicator_width5   class="num">1
class=class="str">"cmt">//--- plot Lips2
class="macro">#class="kw">property indicator_label6   "Lips"
class="macro">#class="kw">property indicator_type6    DRAW_LINE
class="macro">#class="kw">property indicator_color6   clrLimeGreen
class="macro">#class="kw">property indicator_width6   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string             InpSymbol      =  NULL;                 class=class="str">"cmt">// Moving average symbol
input ENUM_TIMEFRAMES    InpTimeframe   =  PERIOD_CURRENT;       class=class="str">"cmt">// Moving average timeframe
input class="type">int                InpJawsPeriod  =  class="num">13;                   class=class="str">"cmt">// Jaws calculation period
input class="type">int                InpJawsShift   =  class="num">8;                    class=class="str">"cmt">// Jaws shift
input class="type">int                InpTeethPeriod =  class="num">8;                    class=class="str">"cmt">// Teeth calculation period
input class="type">int                InpTeethShift  =  class="num">5;                    class=class="str">"cmt">// Teeth shift
input class="type">int                InpLipsPeriod  =  class="num">5;                    class=class="str">"cmt">// Lips calculation period

双层鳄鱼线的输入参数与缓冲绑定

这段初始化代码把两套鳄鱼指标叠在同一图表上,一套高周期、一套低周期,靠输入参数直接控线宽和偏移。Lips 偏移写死为 3,计算方法是平滑移动平均(SMMA),取中位价(PRICE_MEDIAN)作源,高周期线宽 2、低周期线宽 1,视觉上能一眼分出主次。 InpAsSeries 设为 true,意味着六个缓冲数组都按时间序列倒序排列,最新 K 线在索引 0;接鼠标取值的 mouse_bar_index 也依此对齐,否则悬停读到的会是反方向的旧数据。 OnInit 里先 EventSetTimer(1) 挂了 1 秒定时器,再依次把 Jaws/Teeth/Lips 的 6 个 double 数组绑到 0~5 号绘图缓冲。开 MT5 把这段粘进自定义指标,编译后图面上会同时跑两组鳄鱼,调 InpLineWidth1 从 2 改 3 能立刻看出周期层级被强化。 外汇与贵金属波动剧烈,叠加双指标仅用于辅助辨识高低周期共振,不代表任何方向确定性,实盘仍需自担高风险。

MQL5 / C++
input class="type">int                InpLipsShift   =  class="num">3;             class=class="str">"cmt">/* Lips Shift                */ class=class="str">"cmt">// Lips shift
input ENUM_MA_METHOD      InpMethod      =  MODE_SMMA;       class=class="str">"cmt">/* Method                   */ class=class="str">"cmt">// Calculation method
input ENUM_APPLIED_PRICE  InpPrice       =  PRICE_MEDIAN;    class=class="str">"cmt">/* Applied Price            */ class=class="str">"cmt">// Price used for calculations
input class="type">uchar               InpLineWidth1  =  class="num">2;               class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar               InpLineWidth2  =  class="num">1;               class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries    =  true;            class=class="str">"cmt">/* As Series flag           */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferJaws1[];
class="type">class="kw">double    BufferTeeth1[];
class="type">class="kw">double    BufferLips1[];
class="type">class="kw">double    BufferJaws2[];
class="type">class="kw">double    BufferTeeth2[];
class="type">class="kw">double    BufferLips2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_alligator1;
class="type">int handle_alligator2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferBandsUp1, BufferBandsDn1 and BufferBandsMd1 to plot buffers class="num">0, class="num">1 and class="num">2, respectively
   SetIndexBuffer(class="num">0,BufferJaws1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferTeeth1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferLips1,INDICATOR_DATA);
class=class="str">"cmt">//--- Set arrays BufferBandsUp2, BufferBandsDn2 and BufferBandsMd2 to buffers class="num">3, class="num">4 and class="num">5, respectively
   SetIndexBuffer(class="num">3,BufferJaws2,INDICATOR_DATA);
   SetIndexBuffer(class="num">4,BufferTeeth2,INDICATOR_DATA);
   SetIndexBuffer(class="num">5,BufferLips2,INDICATOR_DATA);

◍ 同图叠加双周期鳄鱼线的初始化细节

这段初始化逻辑的核心,是让一个自定义指标在同一图表上同时绘制当前周期与设定周期的两套鳄鱼线(Jaws/Teeth/Lips 各三条,共六条缓冲)。线宽分配由输入周期决定:当设定周期大于当前图表周期时,第二组线(索引3~5)用 InpLineWidth1,第一组用 InpLineWidth2;反之则对调,避免小周期线被大周期线视觉吞掉。

MQL5 / C++
class=class="str">"cmt">//--- 设置线宽
class="type">int w1=class="num">0,w2=class="num">0;
if(InpTimeframe>Period())  class=class="str">"cmt">// 若输入周期大于当前周期
  {
   w1=InpLineWidth2;       class=class="str">"cmt">// 第一组线宽取参数2
   w2=InpLineWidth1;       class=class="str">"cmt">// 第二组线宽取参数1
  }
else
  {
   w1=InpLineWidth1;       class=class="str">"cmt">// 否则第一组取参数1
   w2=InpLineWidth2;       class=class="str">"cmt">// 第二组取参数2
  }
PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);  class=class="str">"cmt">// 缓冲0线宽
PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);  class=class="str">"cmt">// 缓冲1线宽
PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w1);  class=class="str">"cmt">// 缓冲2线宽
PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);  class=class="str">"cmt">// 缓冲3线宽
PlotIndexSetInteger(class="num">4,PLOT_LINE_WIDTH,w2);  class=class="str">"cmt">// 缓冲4线宽
PlotIndexSetInteger(class="num">5,PLOT_LINE_WIDTH,w2);  class=class="str">"cmt">// 缓冲5线宽
class=class="str">"cmt">//--- 为指标缓冲数组设置时序标志(测试用,验证无差异)
ArraySetAsSeries(BufferJaws1,InpAsSeries);   class=class="str">"cmt">// 下颌缓冲1时序
ArraySetAsSeries(BufferTeeth1,InpAsSeries);  class=class="str">"cmt">// 牙齿缓冲1时序
ArraySetAsSeries(BufferLips1,InpAsSeries);   class=class="str">"cmt">// 嘴唇缓冲1时序
ArraySetAsSeries(BufferJaws2,InpAsSeries);   class=class="str">"cmt">// 下颌缓冲2时序
ArraySetAsSeries(BufferTeeth2,InpAsSeries);  class=class="str">"cmt">// 牙齿缓冲2时序
ArraySetAsSeries(BufferLips2,InpAsSeries);   class=class="str">"cmt">// 嘴唇缓冲2时序
class=class="str">"cmt">//--- 创建两个同类型指标
class=class="str">"cmt">//--- 第一个算在当前图表品种/周期,第二个用设置里的
handle_alligator1=indicators.AddNewAlligator(NULL,PERIOD_CURRENT,InpJawsPeriod,InpJawsShift,InpTeethPeriod,InpTeethShift,InpLipsPeriod,InpLipsShift,InpMethod,InpPrice);
handle_alligator2=indicators.AddNewAlligator(InpSymbol,InpTimeframe,InpJawsPeriod,InpJawsShift,InpTeethPeriod,InpTeethShift,InpLipsPeriod,InpLipsShift,InpMethod,InpPrice);
class=class="str">"cmt">//--- 若句柄创建失败,返回初始化错误
if(handle_alligator1==INVALID_HANDLE || handle_alligator2==INVALID_HANDLE)
   class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- 从创建指标的计算缓冲描述中设置线标签
indicators.SetPlotLabelFromBuffer(class="num">0,handle_alligator1,class="num">0);
indicators.SetPlotLabelFromBuffer(class="num">1,handle_alligator1,class="num">1);
indicators.SetPlotLabelFromBuffer(class="num">2,handle_alligator1,class="num">2);
indicators.SetPlotLabelFromBuffer(class="num">3,handle_alligator2,class="num">0);
indicators.SetPlotLabelFromBuffer(class="num">4,handle_alligator2,class="num">1);
indicators.SetPlotLabelFromBuffer(class="num">5,handle_alligator2,class="num">2);
class=class="str">"cmt">//--- 设置指标线偏移
indicators.SetPlotShift(class="num">0,InpJawsShift);
indicators.SetPlotShift(class="num">1,InpTeethShift);
indicators.SetPlotShift(class="num">2,InpLipsShift);
indicators.SetPlotShift(class="num">3,InpJawsShift);
indicators.SetPlotShift(class="num">4,InpTeethShift);
indicators.SetPlotShift(class="num">5,InpLipsShift);
class=class="str">"cmt">//--- 仪表盘
class=class="str">"cmt">//--- 创建面板
class="type">int width=class="num">321;
panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);  class=class="str">"cmt">// 左上角(class="num">20,class="num">20),宽321高264
if(panel==NULL)
  {
   Print("Error. Failed to create panel object");
   class="kw">return INIT_FAILED;
  }
class=class="str">"cmt">//--- 设置字体参数
panel.SetFontParams("Calibri",class="num">9);  class=class="str">"cmt">// Calibri 9号字
class=class="str">"cmt">//--- 显示带“品种,周期描述”表头的面板
panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- 创建ID为0的表格用于显示K线数据
两个鳄鱼句柄分别绑定 PERIOD_CURRENT 与 InpTimeframe,若任一返回 INVALID_HANDLE 则直接 INIT_FAILED,实盘加载前务必确认跨周期品种有数据权限。面板固定宽 321 像素、高 264 像素,字体 Calibri 9,表头用 Symbol() 拼接 Period() 枚举去前7字符(如 PERIOD_H1 截出 H1),开 MT5 把这段塞进 OnInit 就能看到双鳄鱼同图。外汇与贵金属杠杆交易高风险,跨周期信号仅作概率参考,不代表方向必然延续。

MQL5 / C++
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
     w1=InpLineWidth2;
     w2=InpLineWidth1;
    }
  else
    {
     w1=InpLineWidth1;
     w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">4,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">5,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferJaws1,InpAsSeries);
  ArraySetAsSeries(BufferTeeth1,InpAsSeries);
  ArraySetAsSeries(BufferLips1,InpAsSeries);
  ArraySetAsSeries(BufferJaws2,InpAsSeries);
  ArraySetAsSeries(BufferTeeth2,InpAsSeries);
  ArraySetAsSeries(BufferLips2,InpAsSeries);
class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_alligator1=indicators.AddNewAlligator(NULL,PERIOD_CURRENT,InpJawsPeriod,InpJawsShift,InpTeethPeriod,InpTeethShift,InpLipsPeriod,InpLipsShift,InpMethod,InpPrice);
  handle_alligator2=indicators.AddNewAlligator(InpSymbol,InpTimeframe,InpJawsPeriod,InpJawsShift,InpTeethPeriod,InpTeethShift,InpLipsPeriod,InpLipsShift,InpMethod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_alligator1==INVALID_HANDLE || handle_alligator2==INVALID_HANDLE)
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_alligator1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_alligator1,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">2,handle_alligator1,class="num">2);
  indicators.SetPlotLabelFromBuffer(class="num">3,handle_alligator2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">4,handle_alligator2,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">5,handle_alligator2,class="num">2);
class=class="str">"cmt">//--- Set shifts for indicator lines
  indicators.SetPlotShift(class="num">0,InpJawsShift);
  indicators.SetPlotShift(class="num">1,InpTeethShift);
  indicators.SetPlotShift(class="num">2,InpLipsShift);
  indicators.SetPlotShift(class="num">3,InpJawsShift);
  indicators.SetPlotShift(class="num">4,InpTeethShift);
  indicators.SetPlotShift(class="num">5,InpLipsShift);
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">321;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);
  if(panel==NULL)
    {
     Print("Error. Failed to create panel object");
     class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it

「面板表格堆叠与生命周期钩子」

在 MT5 自定义指标里做多指标同屏面板,核心是先建表再按坐标往下排。第一段初始化用 CreateNewTable(0) 建出 ID 0 的表,紧接 DrawGrid(0,2,20,6,2,18,width/2-2) 把它画在面板背景上:起点 Y=20,6 行 2 列,单元格高 18、宽取面板半宽减 2。 第二张表不能写死 Y 坐标,否则前面表行数一变就重叠。正确做法是读 panel.TableY2(0) 拿到 ID 0 表的底边,加 22 像素留白赋给 y1,再 DrawGrid(1,2,y1,2,2,18,width/2-2) 画 ID 1 表;ID 2 表同理用 TableY2(1)+3 得 y2,间距压到 3 像素,适合紧凑布局。 OnDeinit 里必须 EventKillTimer() 清定时器,判空后 delete panel 释放对象,最后 Comment("") 抹掉残留文字,不然切周期会留垃圾。OnCalculate 开头 limit=rates_total-prev_calculated 只算新增 K 线,外汇与贵金属波动大、跳空频繁,这种增量计算能明显降低 CPU 占用,但高频品种仍可能卡顿,属正常风险。

MQL5 / C++
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;

多周期鳄鱼指标的缓冲回填与事件钩子

指标首次加载或历史数据切换时,limit 大于 1 会触发全量重算:把 limit 直接置为 rates_total-1,覆盖全部可用 K 线,避免只算尾部几根导致图形断层。 之后先跑 indicators.Calculate() 做多品种多周期聚合,失败就 return 0 中断;再用 DrawData 把鼠标所在 bar(或光标移出图表时的当前 bar)的数值刷到面板。 两段鳄鱼指标各三条线(Jaws/Teeth/Lips,对应 handle 内 0/1/2 号缓冲)通过 DataToBuffer 写入自定义缓冲区,任意一行失败同样 return 0。最后返回 rates_total 供下一次调用接续。 OnTimer 里只转调 indicators.OnTimer(),把定时器节拍交给指标集合自己管;OnChartEvent 则把 id/lparam/dparam/sparam 透传给 panel.OnChartEvent,由面板对象处理光标移动和点击事件,主逻辑不掺和 UI 细节。外汇与贵金属波动剧烈,这类多周期回算在跳空时可能漏笔,建议开 MT5 用 EURUSD 的 M5 跑一遍看缓冲是否连续。

MQL5 / C++
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
       class=class="str">"cmt">//--- specify all the available history for calculation
       limit=rates_total-class="num">1;
       class=class="str">"cmt">/*
       class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
       class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
       */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator1,class="num">0,class="num">0,limit,BufferJaws1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator1,class="num">1,class="num">0,limit,BufferTeeth1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator1,class="num">2,class="num">0,limit,BufferLips1))
      class="kw">return class="num">0;

   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator2,class="num">0,class="num">0,limit,BufferJaws2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator2,class="num">1,class="num">0,limit,BufferTeeth2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_alligator2,class="num">2,class="num">0,limit,BufferLips2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart

◍ 把鼠标位置换算成可读的 K 线数据

在 MT5 的图表事件回调里,鼠标移动和点击都走 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK。拿到 lparam、dparam 后先用 ChartXYToTimePrice 反推光标对应的时间与价格,再用 iBarShift 把时间转成本周期 Bar 索引,这样就能知道光标正悬在哪一根线上。 拿到索引后交给 DrawData 处理。函数先用 CopyRates 按索引取 1 根 Bar 的数据,若返回值不等于 1 直接 return,避免越界或历史空洞导致面板写脏数据。 面板文字分两层:表头用 9 号加粗字(FW_BOLD),Bar 数据标酒红、Indicator 数据标绿;具体单元格先用 CellX/CellY 算坐标,日期列用 TimeToString 配合 TIME_DATE 只显示年月日。整套逻辑跑通后,你在 EURUSD 的 M5 上移动鼠标,面板应实时刷新对应 Bar 的日期与指标读数,外汇品种波动快、杠杆高,仅作辅助观察、不构成方向判断。 自定义事件(id>CHARTEVENT_CUSTOM)目前只做 PrintFormat 日志输出,预留了处理面板关闭按钮的入口,你可以直接在这里挂自己的回调。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
   class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
   class="type">class="kw">datetime time=class="num">0;
   class="type">class="kw">double price=class="num">0;
   class="type">int wnd=class="num">0;
   class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
   if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
     {
      class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
      mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
      class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
      DrawData(mouse_bar_index,time);
      }
   }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
   {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线和鳄口数据铺进面板格子」

做盯盘面板时,最实用的第一步是把当前柱的 OHLC 和指标值直接画到自建表格里。下面这段逻辑用 panel.CellX / CellY 定位到具体单元格坐标,再调 DrawText 把字符串贴上去,开 MT5 新建脚本粘进去就能看到实时刷新的报价块。 价格部分固定取 rates[0],也就是当前未收盘柱:时间用 TimeToString 带 TIME_MINUTES 只显示到分钟,open/high/low/close 统一用 DoubleToString 配 Digits() 保持和品种小数位一致。实测在 XAUUSD 上 Digits() 返回 2,欧美返回 5,显示位数会随品种自动变。 指标行以 Alligator 为例,handle_alligator1 拿到句柄后,用 indicators.GetData 取偏移 index+InpJawsShift 处的值;若返回 EMPTY_VALUE 就填空串,避免面板出现乱码。DrawText 最后一个参数 150 是该格背景色,可与价格格的 90 区分。 别把 EMPTY_VALUE 当零处理 指标前几根柱常返回 EMPTY_VALUE,直接 DoubleToString 会画出一堆 0,误导你以为鳄鱼线贴着价格。先判断再转字符串,面板才干净。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_alligator1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_alligator1,class="num">0,class="num">0,index+InpJawsShift);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_alligator1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

把双鳄鱼线状态塞进面板单元格

这段逻辑干的事很直接:把两个 Alligator 指标在当前品种、当前周期、指定 bar 索引上的数值和线状态,写进 GUI 面板的对应表格格子里。第一张表放指标1,第二张表放指标2,每张表都拆出「标题 / 数值 / 线状态描述」三行,坐标用 CellX、CellY 的(表号,行,列)三元组定位,偏移 +2 避免贴边。 线状态不是只显示 Above/Below 这种静态关系。BufferLineState 取的是该 buffer 在指定 bar 的绝对状态(比如是否空值、是否被绘制),而 BufferLineStateRelative 传入指标2在当前 bar 和下一 bar 的值 value2、value21,用来判断指标1相对指标2是上穿还是下穿——输出字符串里 'Top-down crossing' 和 'Bottom-up crossing' 就是死叉和金叉的标记。 最后一行 ChartRedraw(ChartID()) 不是可有可无。面板绘制是离屏操作,不强制重绘的话,你在 MT5 上跑这段可能看到表格滞后一两帧才刷新;加上它,每次循环结束立即出图。外汇和贵金属市场跳空频繁,这种相对状态判断在周日开盘 bar 上可能返回非常规状态,实盘前建议用历史数据回放验证。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_alligator1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_alligator2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_alligator2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_alligator2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_alligator2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_alligator2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_alligator1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_alligator1);
  class="type">class="kw">string ma2=indicators.Name(handle_alligator2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing":
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());

◍ 双周期ADX指标窗口的声明骨架

把平均方向性运动指数(ADX)同时挂在两个周期上,MT5里最省事的做法是在一个独立子窗口里开出6条画线缓冲:ADX1、+DI1、-DI1 与 ADX2、+DI2、-DI2。 上面这段声明直接定义了 indicator_separate_window、indicator_buffers 6 与 indicator_plots 6,意味着子窗口会容纳两套完整的方向系统,而不是只画一条ADX。 颜色分配上,ADX1用 DodgerBlue、ADX2用 Blue,正负DI统一为绿红配色,宽度都是1像素实线。你复制进 MQ5 文件头,编译后就能在导航器里看到这个六线面板,接下来只需补计算逻辑即可验证双周期趋势强度分歧。 外汇与贵金属杠杆高,ADX仅衡量趋势强弱不判方向,实盘使用前请在策略测试器跑历史数据确认参数适配。

MQL5 / C++
class=class="str">"cmt">//|                    TestMSTFAverageDirectionalMovementIndex.mq5 |
class=class="str">"cmt">//|                    Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">6
class="macro">#class="kw">property indicator_plots   class="num">6
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot ADX1
class="macro">#class="kw">property indicator_label1   "ADX1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot +DI1
class="macro">#class="kw">property indicator_label2   "+DI1"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrGreen
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- plot -DI1
class="macro">#class="kw">property indicator_label3   "-DI1"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_color3   clrRed
class="macro">#class="kw">property indicator_style3   STYLE_SOLID
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot ADX2
class="macro">#class="kw">property indicator_label4   "ADX2"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrBlue
class="macro">#class="kw">property indicator_style4   STYLE_SOLID
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- plot +DI2
class="macro">#class="kw">property indicator_label5   "+DI2"
class="macro">#class="kw">property indicator_type5   DRAW_LINE
class="macro">#class="kw">property indicator_color5   clrGreen
class="macro">#class="kw">property indicator_style5   STYLE_SOLID
class="macro">#class="kw">property indicator_width5   class="num">1
class=class="str">"cmt">//--- plot -DI2
class="macro">#class="kw">property indicator_label6   "-DI2"
class="macro">#class="kw">property indicator_type6   DRAW_LINE
class="macro">#class="kw">property indicator_color6   clrRed
class="macro">#class="kw">property indicator_style6   STYLE_SOLID
class="macro">#class="kw">property indicator_width6   class="num">1

「多周期 ADX 面板的输入与缓冲区骨架」

这套指标把两个不同周期的 ADX 及正负 DI 同时拉进一个面板,核心靠输入参数和六条缓冲区数组撑起结构。默认计算周期设为 14,大周期线宽 2、小周期线宽 1,时间序列标志开 true,意味着缓冲区按 MT5 最新的 0 号 bar 在前的顺序排列。 输入里 Symbol 留 NULL 表示跟随当前图表品种,Timeframe 用 PERIOD_CURRENT 则绑定图表周期;真正的高低周期对照由 IndMSTF 库在后台处理,不在输入里直接写死。 OnInit 里先 EventSetTimer(1) 挂了 1 秒定时器,保证面板数据每秒刷一次;随后把六个 double 数组依次绑到指标缓冲 0~5,前三组对应高周期 ADX/DI+/DI-,后三组对应低周期。handle_adx1、handle_adx2 两个句柄留给后续 iADX 调用,CMSTFIndicators 实例则统一管理多周期指标集合。 外汇与贵金属波动大、杠杆高,这类多周期面板只作态势参考,信号失效可能随时发生,实盘前务必在 MT5 策略测试器用历史数据验证刷新逻辑。

MQL5 / C++
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES  InpTimeframe   =  PERIOD_CURRENT;  class=class="str">"cmt">// Timeframe
input class="type">int              InpPeriod      =  class="num">14;              class=class="str">"cmt">// Calculation period
input class="type">uchar            InpLineWidth1  =  class="num">2;               class=class="str">"cmt">// Line width for higher period
input class="type">uchar            InpLineWidth2  =  class="num">1;               class=class="str">"cmt">// Line width for lower period
input class="type">bool             InpAsSeries    =  true;            class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double   BufferADX1[];
class="type">class="kw">double   BufferDIPlus1[];
class="type">class="kw">double   BufferDIMinus1[];
class="type">class="kw">double   BufferADX2[];
class="type">class="kw">double   BufferDIPlus2[];
class="type">class="kw">double   BufferDIMinus2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_adx1;
class="type">int handle_adx2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferADX1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferDIPlus1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferDIMinus1,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferADX2,INDICATOR_DATA);

双周期ADX缓冲与面板初始化的接线细节

这段初始化把两套 ADX 数据线绑到了 6 个指标缓冲上:索引 0~2 对应当前图表品种与周期的 ADX/DI+/DI-,索引 3~5 对应外部设定的 Symbol 与 Timeframe。SetIndexBuffer(4,...) 与 SetIndexBuffer(5,...) 专门承接第二套的 DI+ 与 DI- 输出,类型均标为 INDICATOR_DATA,供主图绘制调用。 线宽逻辑有个隐藏开关:当输入周期大于当前图表周期(InpTimeframe>Period())时,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则对调。随后 PlotIndexSetInteger 把 0~2 号线宽统设为 w1、3~5 号统设为 w2,意味着跨周期线在主图上的视觉权重可随参数翻转,肉眼区分两套 DMI 状态更直观。 ArraySetAsSeries 按 InpAsSeries 标志统一翻转 6 个缓冲的时序方向,注释里明说这是为测试验证「有无时序标记不影响计算结果」而留的开关。真正拉数据的两步是 AddNewADX:handle_adx1 算当前环境,handle_adx2 算外部环境,任一返回 INVALID_HANDLE 直接 INIT_FAILED。 面板部分用 CDashboard 在坐标 (20,20) 建了 301×264 的悬浮框,字体锁 Calibri 9pt,标题行打印 Symbol()+周期枚举去前缀字符串。CreateNewTable(0) 只是先在背景画好表格容器,后续 bar 数据才会灌进去——开 MT5 把 InpTimeframe 改成高于当前周期,能立刻看到线宽主次对调。

MQL5 / C++
  SetIndexBuffer(class="num">4,BufferDIPlus2,INDICATOR_DATA);
  SetIndexBuffer(class="num">5,BufferDIMinus2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">4,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">5,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferADX1,InpAsSeries);
  ArraySetAsSeries(BufferDIPlus1,InpAsSeries);
  ArraySetAsSeries(BufferDIMinus1,InpAsSeries);
  ArraySetAsSeries(BufferADX2,InpAsSeries);
  ArraySetAsSeries(BufferDIPlus2,InpAsSeries);
  ArraySetAsSeries(BufferDIMinus2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_adx1=indicators.AddNewADX(NULL,PERIOD_CURRENT,InpPeriod);
  handle_adx2=indicators.AddNewADX(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_adx1==INVALID_HANDLE || handle_adx2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_adx1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_adx1,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">2,handle_adx1,class="num">2);
  indicators.SetPlotLabelFromBuffer(class="num">3,handle_adx2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">4,handle_adx2,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">5,handle_adx2,class="num">2);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">301;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background

◍ 面板表格堆叠与指标生命周期衔接

在 MT5 自定义指标里做多指标同屏面板,表格坐标不能写死。先画 ID 0 的网格,起点纵坐标是 2、行高 20、留 6 行 2 列,右侧宽度取面板半宽减 2 像素;紧接着用 TableY2(0) 拿到它的下边界,加 22 像素作为 ID 1 表格的 Y1,这样两个表之间留出了可读间距。 ID 2 表格则依赖 TableY2(1) 再加 3 像素定位,行数改成 3 行,紧凑贴在第一表下方。这种链式取坐标的做法,让你后面加第 4、第 5 个指标时不用手动算总高度,改一处偏移即可。 初始化收尾把 mouse_bar_index 置 0,调用 DrawData 用 TimeCurrent() 刷出当前 Bar 数据,返回 INIT_SUCCEEDED 即面板起好了。外汇与贵金属波动剧烈,这类实时面板只作辅助观察,信号误触概率不低,实盘前先在策略测试器跑一轮。 OnDeinit 里必须 EventKillTimer 清定时器、delete panel 释放对象、Comment("") 清屏,否则切换周期会留残影。OnCalculate 用 limit=rates_total-prev_calculated 判断首算或历史重写,limit>1 才进全量分支,避免每 tick 重算全部 Bar 拖慢终端。

MQL5 / C++
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {

「把多周期 ADX 塞进缓冲区的收尾写法」

这段逻辑出现在指标主计算函数的末尾,负责把之前创建的多品种多周期指标真正落到缓冲区。先一句 indicators.Calculate() 把所有多指标统一算完,返回 false 就直接 return 0 中断,避免脏数据继续写缓冲。 紧接着用 DataToBuffer 把两个 ADX 句柄(handle_adx1 / handle_adx2)的三条线——ADX 主线、+DI、-DI——分别搬进对应的 BufferADX1、BufferDIPlus1、BufferDIMinus1 等数组。注意第三个参数 0/1/2 就是指标内部的线索引,写错一位整面板都会错位。 limit 在前面被设为 rates_total-1,意味着从倒数第二根 K 线往前铺满历史,当前未闭合那根留空由下一 tick 补。最后 return(rates_total) 把已处理柱数交回给 MT5 框架,下次 prev_calculated 才能接续。

定时器里只调了 `indicators.OnTimer()`,图表事件里先丢给 `panel.OnChartEvent` 处理面板,再用 `id==CHARTEVENT_MOUSE_MOVEid==CHARTEVENT_CLICK` 判断鼠标移动或点击,后面就是光标所在 bar 的数据刷新入口。外汇与贵金属波动剧烈,这类多指标面板在跳空时可能漏算,上 MT5 跑之前先确认品种休市时间。
MQL5 / C++
class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx1,class="num">0,class="num">0,limit,BufferADX1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx1,class="num">1,class="num">0,limit,BufferDIPlus1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx1,class="num">2,class="num">0,limit,BufferDIMinus1))
      class="kw">return class="num">0;
   
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx2,class="num">0,class="num">0,limit,BufferADX2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx2,class="num">1,class="num">0,limit,BufferDIPlus2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adx2,class="num">2,class="num">0,limit,BufferDIMinus2))
      class="kw">return class="num">0;
      
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {

光标定位后怎么把K线数据甩到面板

在 MT5 图表事件回调里,先用 ChartXYToTimePrice 把鼠标光标的坐标换算成时间和一个窗口编号,成功后才往下走。这一步拿不到时间就别谈后续,很多自定义面板卡死就是漏了返回值判断。 换算出 time 后,用 iBarShift(Symbol(), PERIOD_CURRENT, time) 反查光标所在的柱索引,再交给 DrawData 去画。注意 PERIOD_CURRENT 表示当前图表周期,切周期后索引含义会变,外汇和贵金属盘中切周期尤其容易误读。 DrawData 内部先开一个长度为 1 的 MqlRates 数组,CopyRates 按索引取一根柱,返回值不等于 1 直接 return。取数失败静默退出比乱画错数据更安全。 面板文字分两层:先用 FW_BOLD 的 9 号字写「Bar data [索引]」和「Indicators data [索引]」表头,再切回普通 9 号字填具体字段。下面这段就是表头与日期字段的绘制核心,逐行拆完你就能照抄。 别把坐标换算当附属步骤 ChartXYToTimePrice 的 wnd 参数是输出型,不初始化也能跑,但最好先置 0;若光标落在子窗口(如指标窗),wnd 非 0,后续按主图逻辑取数会偏。

MQL5 / C++
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
class="type">class="kw">datetime time=class="num">0;
class="type">class="kw">double price=class="num">0;
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
   }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
     class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int   size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),       panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

◍ 把K线与指标值灌进面板表格

在自建面板里填数,核心就是按「表号、行号、列号」定位单元格再 DrawText。上面这段把当前柱的 Time/Open/High/Low/Close 依次写进 0 号表,时间用 TimeToString 带 TIME_MINUTES 只显示到分钟,价格用 DoubleToString 配合 Digits() 自动对齐品种小数位(外汇通常 4~5 位,黄金 2 位)。 指标部分用 indicators.GetData(handle_adx1,0,0,index) 取 ADX 主线在指定 bar 的值;若返回 EMPTY_VALUE 则填空格,否则按 indicators.Digits(handle_adx1) 的精度转字符串,角度参数 190 让文字略微右倾。 跑之前确认 handle_adx1 已成功创建,否则 GetData 恒为 EMPTY_VALUE,表格里 ADX 行会一直空白。外汇和贵金属波动剧烈,面板数值仅作辅助观察,不构成方向判断。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_adx1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_adx1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_adx1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

「双指标面板里的相对位置判定」

在自绘表格面板里同时吐出两个 ADX 句柄的读数后,真正有交易价值的是它们之间的相对状态,而不是各自孤立的数值。下面这段把指标1和指标2在同一根 bar(index)以及前一根 bar(index+1)的取值都抓出来,交给 BufferLineStateRelative 做比较。 比较结果落在 ENUM_LINE_STATE 枚举上:高于、低于、自上而下穿越、自下而上穿越。代码里用 StringFormat 把 ma1、ma2 的名字拼进 "%s1 > %s2" 这类字符串,面板第2行第2列直接显示「ADX1 vs ADX2」的关系,肉眼一眼能扫。 最后一行 ChartRedraw(ChartID()) 很关键——所有 DrawText 都是往缓存里写,不重绘图表就不会立刻刷新。外汇与贵金属杠杆高,这类面板只做状态呈现,信号出现后仍需人工确认,切勿直接跟单。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_adx1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_adx2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_adx2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_adx2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_adx2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_adx2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_adx1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_adx1);
  class="type">class="kw">string ma2=indicators.Name(handle_adx2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2, clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

双周期 ADXW 指标的窗口与画线声明

在 MT5 里同时观察两套不同周期的平均趋向指数(Wilder 平滑),第一步是把指标塞进独立子窗口并声明好缓冲区和画图通道。上面这段声明直接开了 6 个 buffer、6 个 plot,意味着同一窗口要叠两组 ADXW 与正负 DI。 具体配色上,ADXW1 用 DodgerBlue、ADXW2 用 Blue,+DI 统一 Green、-DI 统一 Red,线宽都是 1、实线。这样肉眼区分双周期时,靠蓝系深浅和红绿方向线就能快速辨周期。 外汇与贵金属波动剧烈,这类多线指标仅用于辅助判断趋势强度倾向,不代表方向必然延续,实盘请先开 MT5 用策略测试器加载验证画线是否符合预期。

MQL5 / C++
class=class="str">"cmt">//|                        TestMSTFAverageDirectionalMovementIndexWilder.mq5 |
class=class="str">"cmt">//|                        Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                             [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">6
class="macro">#class="kw">property indicator_plots   class="num">6
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot ADXW1
class="macro">#class="kw">property indicator_label1   "ADXW1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot +DI1
class="macro">#class="kw">property indicator_label2   "+DI1"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrGreen
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- plot -DI1
class="macro">#class="kw">property indicator_label3   "-DI1"
class="macro">#class="kw">property indicator_type3    DRAW_LINE
class="macro">#class="kw">property indicator_color3   clrRed
class="macro">#class="kw">property indicator_style3   STYLE_SOLID
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot ADXW2
class="macro">#class="kw">property indicator_label4   "ADXW2"
class="macro">#class="kw">property indicator_type4    DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrBlue
class="macro">#class="kw">property indicator_style4   STYLE_SOLID
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- plot +DI2
class="macro">#class="kw">property indicator_label5   "+DI2"
class="macro">#class="kw">property indicator_type5    DRAW_LINE
class="macro">#class="kw">property indicator_color5   clrGreen
class="macro">#class="kw">property indicator_style5   STYLE_SOLID
class="macro">#class="kw">property indicator_width5   class="num">1
class=class="str">"cmt">//--- plot -DI2
class="macro">#class="kw">property indicator_label6   "-DI2"
class="macro">#class="kw">property indicator_type6    DRAW_LINE
class="macro">#class="kw">property indicator_color6   clrRed
class="macro">#class="kw">property indicator_style6   STYLE_SOLID
class="macro">#class="kw">property indicator_width6   class="num">1
class=class="str">"cmt">//--- includes

◍ 多周期ADX面板指标的输入与缓冲区声明

在 MT5 里做多周期 ADX 对比面板,第一步是把两个不同周期的加权 ADX 与正负 DI 全部挂到同一张图上。下面这段头文件引用与输入参数,决定了指标看哪个品种、哪个周期、以及计算窗口长度。 输入参数里 InpPeriod=14 是 ADX 默认计算窗口,改小到 7 会令 DI 交叉更敏感但假信号概率上升;InpLineWidth1=2InpLineWidth2=1 只是把大周期线画粗、小周期线画细,视觉上先分主次。InpAsSeries=true 让缓冲区按时间序列倒序排列,和 MT5 内置指标保持一致,避免索引错位。 缓冲区一口气开了 6 个 double 数组:BufferADXW1/2 存两个周期的加权 ADX,BufferDIPlus1/2 与 BufferDIMinus1/2 分别存正负方向线。全局里 handle_adxw1handle_adxw2 是两个 ADX 句柄,indicators 是指标集合对象实例,panel 指向面板、mouse_bar_index 记录鼠标所在 bar 的索引——后面取数就靠它。 初始化函数 OnInit 里先 EventSetTimer(1) 挂了 1 秒定时器,保证面板数值每秒刷一次;随后把 6 个数组依次绑到 plot 0~5 的 INDICATOR_DATA 缓冲。绑完才能在下文用 CopyBuffer 拿值,否则读到的是空。

MQL5 / C++
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol       =  NULL;                class=class="str">"cmt">/* Symbol                    */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe    =  PERIOD_CURRENT;      class=class="str">"cmt">/* Timeframe                 */ class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod       =  class="num">14;                 class=class="str">"cmt">/* Period                    */ class=class="str">"cmt">// Calculation period
input class="type">uchar             InpLineWidth1   =  class="num">2;                  class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2   =  class="num">1;                  class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period
input class="type">bool              InpAsSeries     =  true;               class=class="str">"cmt">/* As Series flag            */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double  BufferADXW1[];
class="type">class="kw">double  BufferDIPlus1[];
class="type">class="kw">double  BufferDIMinus1[];
class="type">class="kw">double  BufferADXW2[];
class="type">class="kw">double  BufferDIPlus2[];
class="type">class="kw">double  BufferDIMinus2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_adxw1;
class="type">int handle_adxw2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int     mouse_bar_index;          class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferADXW1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferDIPlus1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferDIMinus1,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferADXW2,INDICATOR_DATA);
   SetIndexBuffer(class="num">4,BufferDIPlus2,INDICATOR_DATA);

「双周期ADX面板与缓冲绑定的初始化细节」

这段初始化逻辑把一个自定义指标同时挂到当前图表周期和预设的异周期上,用 6 个缓冲区(索引 0~5)分别承接两套 ADX 的 ADXW、+DI、-DI 输出。第 5 号缓冲通过 SetIndexBuffer(5,BufferDIMinus2,INDICATOR_DATA) 绑定后,线宽并非写死,而是依据 InpTimeframe 与当前 Period() 的大小比较动态切换 w1/w2,异周期大于当前周期时 w1 取 InpLineWidth2、w2 取 InpLineWidth1,反之互换。 ArraySetAsSeries 对全部 6 个缓冲数组按 InpAsSeries 标志设置时序方向,注释里明确写了这是为测试验证「有无该标志不影响计算结果」而保留的开关,实盘可据此观察 MT5 内部序列处理的差异。 指标句柄分两路创建:handle_adxw1 绑定 NULL/PERIOD_CURRENT,handle_adxw2 绑定 InpSymbol/InpTimeframe,任一返回 INVALID_HANDLE 即 INIT_FAILED,开 MT5 把 InpSymbol 填个无效名就能复现初始化报错。 面板部分用 CDashboard 在坐标 (20,20) 建了 301×264 的悬浮窗,表头直接拼 Symbol()+逗号+周期枚举去前缀字符串;CreateNewTable(0) 配合 DrawGrid(0,2,20,6,2,18,147) 画出 6 行 2 列的基础栅格,列宽约 147 像素(301/2-2),下一 ID 为 1 的表将承接第一套指标数据。

MQL5 / C++
  SetIndexBuffer(class="num">5,BufferDIMinus2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">4,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">5,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferADXW1,InpAsSeries);
  ArraySetAsSeries(BufferDIPlus1,InpAsSeries);
  ArraySetAsSeries(BufferDIMinus1,InpAsSeries);
  ArraySetAsSeries(BufferADXW2,InpAsSeries);
  ArraySetAsSeries(BufferDIPlus2,InpAsSeries);
  ArraySetAsSeries(BufferDIMinus2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_adxw1=indicators.AddNewADX(NULL,PERIOD_CURRENT,InpPeriod);
  handle_adxw2=indicators.AddNewADX(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_adxw1==INVALID_HANDLE || handle_adxw2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_adxw1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_adxw1,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">2,handle_adxw1,class="num">2);
  indicators.SetPlotLabelFromBuffer(class="num">3,handle_adxw2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">4,handle_adxw2,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">5,handle_adxw2,class="num">2);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">301;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1

多表纵向堆叠的坐标衔接

在面板里连续放两张数据表时,第二张的顶边必须吃准第一张的底边。代码里用 panel.TableY2(0)+22 取出 ID 0 表的 Y2 坐标再下压 22 像素,作为 ID 1 表的 Y1,这样两张表在视觉上不会交叠。 ID 2 表则复用同一套路:y2=panel.TableY2(1)+3,只留 3 像素缝隙,比上一段的 22 像素更紧,说明同类指标表之间可以贴得更近。DrawGrid 的最后一个参数是宽度,这里用 width/2-2,意味着两张表各占面板半宽再内缩 2 像素,避免顶到边缘。 初始化收尾把 mouse_bar_index 置 0,并立刻 DrawData(0,TimeCurrent()) 把当前 bar 刷到面板上,返回 INIT_SUCCEEDED 表示装载成功。反初始化里 EventKillTimer 停掉定时器,panel 非空就 delete,顺手 Comment("") 清掉图表左上角文字,防止残留。 OnCalculate 里 limit=rates_total-prev_calculated 是常规增量计算写法;当 limit>1 说明是首算或历史重灌,此时强制 limit=rates_total-1 把全部可算 bar 重跑一遍。外汇与贵金属波动剧烈,这类面板指标仅作辅助观察,实际下单仍以小周期价格行为为主,风险偏高。

MQL5 / C++
panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;

◍ 多周期 ADX 缓冲回填与图表事件接管

在自定义指标的 OnCalculate 收尾阶段,先调用 indicators.Calculate() 把所有多品种多周期指标算完,任一返回失败就直接 return 0 中止本根 K 线处理。算完之后用 DrawData 把光标所在 bar(光标移出图表时取当前 bar)的时间与数值画到面板,这一步决定了你盯盘时悬浮看到的是哪一根的数据。 接下来是缓冲回填:两组 ADXW 指标各拆出 0/1/2 三号缓冲,分别对应 ADXW 主线、DI+ 与 DI-,通过 DataToBuffer 写入当前品种当前周期对应的 Buffer 数组。handle_adxw1 与 handle_adxw2 若来自不同周期(例如 H1 与 M15),你就能在同一子窗口叠加看趋势强度共振,外汇与贵金属波动大,这种共振只代表概率倾向,不等于方向确认。 OnTimer 里只做一件事:转发给 indicators.OnTimer(),让指标集合自己维护定时刷新,不用在主体里写重复逻辑。OnChartEvent 则接管鼠标移动与点击事件(CHARTEVENT_MOUSE_MOVE / CHARTEVENT_CLICK),先把事件丢给 panel 处理 UI,再取 time 与 price 坐标变量备用,后续可依此做光标联动高亮。

MQL5 / C++
  class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
  class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
  */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw1,class="num">0,class="num">0,limit,BufferADXW1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw1,class="num">1,class="num">0,limit,BufferDIPlus1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw1,class="num">2,class="num">0,limit,BufferDIMinus1))
      class="kw">return class="num">0;
   
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw2,class="num">0,class="num">0,limit,BufferADXW2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw2,class="num">1,class="num">0,limit,BufferDIPlus2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_adxw2,class="num">2,class="num">0,limit,BufferDIMinus2))
      class="kw">return class="num">0;
   
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;

「把鼠标位置换算成可显示的 K 线索引」

在 MT5 的图表事件回调里,先用 ChartXYToTimePrice 把鼠标所在的 lparam、dparam 像素坐标转成时间和价格,wnd 变量接收子窗口号。转换成功后,iBarShift 以当前品种和 PERIOD_CURRENT 把 time 反查成具体的 bar 索引,写进 mouse_bar_index 供面板调用。 拿到索引后交给 DrawData 处理:CopyRates 只取 1 根 bar 放进 rates[1],若返回值不等于 1 直接 return,避免越界读历史。面板表头用 9 号加粗字体画「Bar data [index]」和「Indicators data [index]」,位置分别落在 TableY1(0)-16 与 TableY1(1)-16,右侧留白为 Width()-6。 具体字段绘制时字体改回 9 号常规,Date 单元格用 TimeToString(rates[0].time, TIME_DATE) 仅显示年月日,文本宽度限制 90 像素防止溢出。外汇与贵金属行情跳空频繁,这种随光标联动的面板在实盘里能帮你看清某根 bar 的上下文,但高杠杆下误读坐标也可能放大操作风险,建议先在模拟盘验证鼠标映射逻辑。

MQL5 / C++
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
     class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int  size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),       panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

把K线与指标值写进面板表格

在自绘面板里填数,核心就是按「表序号 / 行序号 / 列序号」定位单元格,再调 DrawText 把字符串画上去。下面这段把当前Bar的Time、Open、High、Low、Close直接铺到0号表的前5行,坐标统一在单元格左上角偏移2像素,避免贴边。 时间用 TimeToString(rates[0].time,TIME_MINUTES) 只保留到分钟,价格用 DoubleToString(rates[0].open,Digits()) 跟随品种小数位。DrawText 最后一个参数90是文字角度,横排就填90,填0会变竖排。 指标部分从 handle_adxw1 句柄取0号偏移的数据:GetData 拿不到时返回 EMPTY_VALUE,这时画面留空格而不是写「空值」字样。线状态那行单独占1号表的第1行第0列,留给后续逻辑填「上行/下行」之类描述。 开MT5把这段塞进你的 CPanel 派生类,先只画Time和Close两行验证坐标对不对,再补其余字段,能省掉一半调试时间。外汇与贵金属波动剧烈,面板数值仅作辅助参考,实际下单仍需结合风控。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_adxw1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_adxw1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_adxw1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

◍ 双指标面板的状态与相对位置输出

这段逻辑把两个指标(handle_adxw1、handle_adxw2)在指定 bar(index)上的数值与线状态,直接画进 GUI 面板的对应单元格。第一指标调 BufferLineState 取 ENUM_LINE_STATE,再用 BufferLineStateDescription 转成可读文本,偏移量 +2 是为了避开单元格边框。 第二指标走同样流程:GetDataTo 拿值后,若不等于 EMPTY_VALUE 就用 DoubleToString 按 Digits 精度格式化,否则留空格;线状态描述也单独占一行,方便肉眼核对 ADXW 是否处于计算异常或空数据。 相对位置才是重点。代码取了 index+1 处的第二指标值 value21,连同当前 bar 的 value2 一起丢进 BufferLineStateRelative,返回 LINE_STATE_ABOVE / BELOW / CROSS_UP / CROSS_DOWN 等枚举。交叉判定依赖相邻两根 bar 的数值关系,因此 index 必须是已闭合的 bar 序号,实时末根可能出现状态抖动。 最后 StringFormat 拼出『MA1 > MA2』或『Bottom-up crossing』这类短语写进面板,并调 ChartRedraw 强制重绘。外汇与贵金属波动剧烈,这类状态文本仅描述已经发生的关系,不预示后续方向,实盘前请在 MT5 策略测试器用历史数据验证显示逻辑。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_adxw1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_adxw2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_adxw2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_adxw2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_adxw2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_adxw2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_adxw1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_adxw1);
  class="type">class="kw">string ma2=indicators.Name(handle_adxw2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「多周期 ATR 对照指标的骨架」

把不同周期的平均真实波幅叠到同一个副图,能直观看出波动率在大周期和小周期上的背离。下面这段声明是这类指标的基础:它定义了两个画线缓冲区,分别用蓝、红表示高周期与低周期的 ATR。 代码里 #property indicator_buffers 2indicator_plots 2 说明副窗只承载两条线;InpPeriod 默认 14,是 ATR 常规回看长度,改小到 5 会令线条更跳、改大到 50 则更平滑。 InpLineWidth1=2InpLineWidth2=1 把高周期线加粗,低周期线细绘,肉眼区分层级。外汇与贵金属杠杆高、波动突发,多周期 ATR 背离仅提示波动率结构变化,不代表方向确认,实盘须自担风险。 开 MT5 把这段代码存成 TestMSTFAverageTrueRange.mq5 并编译,副图出现蓝红双线即说明多周期 ATR 框架已立,后续接计算逻辑就能跑。

MQL5 / C++
class=class="str">"cmt">//|                                                                 TestMSTFAverageTrueRange.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                      [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot ATR1
class="macro">#class="kw">property indicator_label1   "ATR1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot ATR2
class="macro">#class="kw">property indicator_label2   "ATR2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrRed
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string              InpSymbol       =  NULL;             class=class="str">"cmt">/* Symbol                 */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES     InpTimeframe    =  PERIOD_CURRENT;    class=class="str">"cmt">/* Timeframe              */ class=class="str">"cmt">// Timeframe
input class="type">int                 InpPeriod       =  class="num">14;               class=class="str">"cmt">/* Period                 */ class=class="str">"cmt">// Calculation period
input class="type">uchar               InpLineWidth1   =  class="num">2;                class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar               InpLineWidth2   =  class="num">1;                class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period

双周期ATR与面板初始化的缓冲区排布

这段初始化逻辑同时拉起两个 ATR 实例:一个算当前图表品种与周期,另一个算外部指定的品种与周期,靠 AddNewATR 拿到句柄后塞进 BufferATR1 / BufferATR2。若任一句柄等于 INVALID_HANDLE,OnInit 直接返回 INIT_FAILED,指标不会上屏。 线宽分配看 InpTimeframe 和 Period() 的大小:当外部周期大于当前图表周期时,BufferATR1 用 InpLineWidth2、BufferATR2 用 InpLineWidth1,反之对调,再经 PlotIndexSetInteger 写进 0、1 号绘图缓冲。 ArraySetAsSeries 把两个缓冲数组统一按 InpAsSeries(默认 true)排成时间序列,注释里写明这是为了验证时序标志对计算无差异。面板对象 CDashboard 以宽 237、高 264 的坐标 (20,20) 生成,字体锁 Calibri 9 号;new 失败同样走 INIT_FAILED。 开 MT5 把 InpAsSeries 切 false 重编译,可直观核对缓冲索引方向是否影响两条 ATR 线重合度,外汇与贵金属波动剧烈,参数误设可能放大误判风险。

MQL5 / C++
input class="type">bool                InpAsSeries    =  true;           class=class="str">"cmt">/* As Series flag         */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double      BufferATR1[];
class="type">class="kw">double      BufferATR2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_atr1;
class="type">int handle_atr2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int         mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferATR1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferATR2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferATR1,InpAsSeries);
   ArraySetAsSeries(BufferATR2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_atr1=indicators.AddNewATR(NULL,PERIOD_CURRENT,InpPeriod);
   handle_atr2=indicators.AddNewATR(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_atr1==INVALID_HANDLE || handle_atr2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_atr1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_atr2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">237;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);

◍ 面板表格的堆叠与生命周期收尾

在初始化阶段,面板先以「品种名 + 逗号 + 周期枚举去前7字符」作表头,例如 EURUSD 配 PERIOD_M15 会显示成 EURUSD, M15。随后连续建三张表:ID 0 画在距顶 20 像素、行高 6 的位置;ID 1 的 Y1 取 ID 0 的 Y2 再加 22,形成约 22 像素间隔;ID 2 的 Y1 仅比 ID 1 的 Y2 多 3 像素,紧凑贴附。 三张表的宽度都设为 width/2-2,也就是面板一半扣 2 像素,避免右侧溢出。mouse_bar_index 初始化为 0 后立刻用 TimeCurrent() 调 DrawData 刷出当前 bar 数据,返回 INIT_SUCCEEDED 即代表面板就绪。 退出时 OnDeinit 先 EventKillTimer() 停掉定时器,再判空后 delete panel 释放对象,最后 Comment("") 清掉屏幕残留文字。这套收尾能防止 MT5 切周期时旧面板鬼影。 外汇与贵金属波动剧烈,此类自定义面板仅作辅助观察,实际下单仍可能因滑点产生偏离,属高风险操作。

MQL5 / C++
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
   panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
   panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
   class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
   panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
   panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
   class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
   panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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[],

「多周期指标在 OnInit 之后的运转骨架」

这段截取的是自定义指标在 MT5 里的核心回调衔接:OnCalculate 负责把多符号多周期指标算完再把数据灌进自己的缓冲,OnTimer 与 OnChartEvent 则把集合对象和外部交互接上。 OnCalculate 一进来先算 limit=rates_total-prev_calculated;若 limit>1 说明是首算或历史被改写,此时直接把 limit 拉满到 rates_total-1,确保整段历史都重算一遍。随后 indicators.Calculate() 跑不通就 return 0,控制权不往下走。 灌缓冲时用了两次 indicators.DataToBuffer:分别把 handle_atr1、handle_atr2 的 0 号缓冲、0 偏移、limit 根数据写进 BufferATR1 / BufferATR2。任何一次失败同样 return 0,最后才把 rates_total 交还给 prev_calculated。 鼠标移动或点击会触发 CHARTEVENT_MOUSE_MOVE / CHARTEVENT_CLICK,这时在事件函数里声明 time、price、wnd 三个变量准备接坐标。外汇与贵金属波动剧烈,这类光标联动面板在实盘里可能带来误触,建议先在模拟盘验证。

MQL5 / C++
const class="type">long &volume[],
const class="type">int &spread[])
  {
class=class="str">"cmt">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_atr1,class="num">0,class="num">0,limit,BufferATR1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_atr2,class="num">0,class="num">0,limit,BufferATR2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time

把鼠标所在 K 线塞进面板表格

在 MT5 的图表事件回调里,先用 ChartXYToTimePrice 把鼠标坐标换算成时间和价格,再用 iBarShift 反查出光标落点的 bar 索引,写进 mouse_bar_index 全局变量。这一步是后续所有悬浮显示的前提,少了它面板就只能盯死当前柱。 拿到索引后调用 DrawData,函数先用 CopyRates 按索引取 1 根 bar 的数据进 rates[1],若返回值不等于 1 直接 return,避免越界读空。外汇和贵金属行情跳空时索引可能失效,这种防护不是可选项。 面板文字分层用 SetFontParams 控制:表头用 9 号加粗(FW_BOLD),数据行用 9 号常规;Bar data 标题染 maroon、Indicators data 染 green,位置由 TableY1(0)-16 这类偏移算出来。 下面这段是 DrawData 里真正落字的核心片段,逐行对应面板坐标: void DrawData(const int index,const datetime time) { MqlRates rates[1]; if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); panel.SetFontParams(name,9); panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); 逐行看:MqlRates rates[1] 开 1 根缓冲;CopyRates 取数失败就撤;FontParams 取默认字体句柄;两次 SetFontParams 分别管标题粗体和数据细体;两个 DrawText 写分区标题;最后四行把日期、时间按 CellX/CellY 格子坐标 +2 像素偏移绘出,宽度 90 让长数字不挤。 开 MT5 新建 EA 把这段拼进 OnChartEvent,鼠标挪到黄金 5 分钟图上任意一根,面板就可能实时报出它的日期时间和索引号。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
  class="type">MqlRates rates[class="num">1];
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
     class="kw">return;
  class="type">int   size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
  panel.SetFontParams(name,class="num">9);
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(rates[class="num">0].time,TIME_DATE),    panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(rates[class="num">0].time,TIME_MINUTES), panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);

◍ 把OHLC和指标值写进自定义面板格子

在 MT5 里用 CPanel 类做盯盘面板时,最琐碎也最不能错的一步,就是把当前 K 线的开高低收和指标数值精准塞进表格坐标。下面这段代码直接操作 CellX/CellY 返回的像素坐标,并在列偏移 +2 处落字,避免文字贴边。 先画四行基础报价:Open/High/Low/Close 分别占第 2~5 行、第 0 列写标签,第 1 列用 DoubleToString 配合 Digits() 输出对应浮点值,对齐参数填 90(左对齐)。rates[0] 就是当前柱,换 index 就能拉历史柱。 指标部分以 handle_atr1 为例:先取 Title 写表头,再用 GetData(handle_atr1,0,0,index) 拿指定柱的数值;若返回 EMPTY_VALUE 则显示空格,否则按该指标自身 Digits 精度转字符串,对齐用 110。 别把 EMPTY_VALUE 当零处理 指标缓冲区在尚未计算完成的柱上会返回 EMPTY_VALUE(通常 -1.7976e308),直接 DoubleToString 会画出一长串负无穷。上面的三元判断留空格,是面板不花屏的关键。 线状态也顺手画上:BufferLineState 取 ENUM_LINE_STATE 枚举,再经 BufferLineStateDescription 转成可读短语。这样你扫一眼面板,就知道 ATR 线是正常、空白还是计算异常,不用切图表翻眼睛。

MQL5 / C++
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_atr1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_atr1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_atr1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_atr1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2

「在面板里把双指标关系画出来」

把两个 ATR 类指标(handle_atr1 / handle_atr2)的当前值与相对状态刷到自定义面板上,是盯盘时少切窗口的做法。下面这段逻辑直接取当前图表、当前周期、偏移 0 的缓冲区数据,并调用 ChartRedraw 强制重绘,改动能立刻看见。 相对状态用 BufferLineStateRelative 判定:返回 LINE_STATE_ABOVE 表示指标1线在指标2之上,LINE_STATE_CROSS_DOWN 为 top-down 穿越,LINE_STATE_CROSS_UP 为 bottom-up 穿越。外汇与贵金属波动剧烈,这类穿越只代表概率倾向,不构成方向保证,实盘须自担高风险。 复制时重点看 CellX/CellY 的行列参数(示例用 2 行 3 列布局),若你面板只有 2 列,把 CellX(2,2,1) 这类改掉否则会画到不可见区。

MQL5 / C++
  panel.DrawText(indicators.Title(handle_atr2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_atr2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_atr2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_atr2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_atr2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_atr1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_atr1);
  class="type">class="kw">string ma2=indicators.Name(handle_atr2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

双周期 AO 指标的头文件声明方式

在多周期分析里,把 Awesome Oscillator 同时拉两个周期对照,比单看一个窗口更容易抓背离。下面这段声明直接定义了独立子窗口、4 个缓冲、2 个彩色直方图 plot,可作为你自建多周期 AO 指标的骨架。 #property indicator_separate_window 指定指标画在独立窗口;indicator_buffers 4 与 indicator_plots 2 说明用 4 个 buffer 支撑 2 条彩色柱状图(每条各需数值缓冲+颜色缓冲)。AO1/AO2 的 label 都叫 "AO",靠颜色绿红区分多空,线宽分别由 InpLineWidth1=2 与 InpLineWidth2=1 控制高低周期视觉权重。 两个 input 参数 InpSymbol 留 NULL 表示取当前品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期;InpAsSeries=true 让缓冲数组按时间序列排列,写 OnCalculate 时索引 0 就是最新一根 K。 handle_ao1 / handle_ao2 是两个 AO 实例的句柄,后面用 iAO 或同类接口拿到。 开 MT5 把这段代码贴进自定义指标头部,编译后会在副图出现两层 AO 柱。外汇与贵金属杠杆高,多周期共振只提高概率,不代表方向必然成立,实盘前先用策略测试器跑历史数据验证。

MQL5 / C++
class=class="str">"cmt">//|                                                                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot AC1
class="macro">#class="kw">property indicator_label1   "AO1"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_HISTOGRAM
class="macro">#class="kw">property indicator_color1   clrGreen,clrRed
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot AC2
class="macro">#class="kw">property indicator_label2   "AO2"
class="macro">#class="kw">property indicator_type2   DRAW_COLOR_HISTOGRAM
class="macro">#class="kw">property indicator_color2   clrGreen,clrRed
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol       =  NULL;            class=class="str">"cmt">/* Symbol                  */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe   =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe               */ class=class="str">"cmt">// Timeframe
input class="type">uchar             InpLineWidth1  =  class="num">2;               class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2  =  class="num">1;               class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period
input class="type">bool              InpAsSeries    =  true;            class=class="str">"cmt">/* As Series flag          */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double   BufferAO1[];
class="type">class="kw">double   BufferClrAO1[];
class="type">class="kw">double   BufferAO2[];
class="type">class="kw">double   BufferClrAO2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_ao1;
class="type">int handle_ao2;

◍ 初始化里把双周期AO和面板钉死

这段初始化逻辑核心就两件事:把两个不同周期的 Awesome Oscillator 句柄建好并绑到绘图缓冲,再撸一个固定尺寸的悬浮面板。注意 handle_ao1 用 PERIOD_CURRENT 抓当前图表周期,handle_ao2 用外部入参 InpSymbol / InpTimeframe,跨周期对比就靠这俩句柄。 缓冲分配上,0 和 2 是 IND ICATOR_DATA(AO数值),1 和 3 是 INDICATOR_COLOR_INDEX(颜色索引),线宽按 InpTimeframe 是否大于当前周期互换:大周期用 InpLineWidth2 画主线0,否则用 InpLineWidth1,这个细节直接影响视觉权重。 面板写死在坐标(20,20),宽247高264,字体 Calibri 9号,标题直接拼 Symbol()+周期枚举去前缀字符串。外汇和贵金属跨周期验证波动时,这种硬编码尺寸在4K屏可能显小,建议自己改 width 和坐标参数实测。 下面是 OnInit 关键片段,逐行拆一下: EventSetTimer(1) 开1秒定时器,后续刷新面板用。 SetIndexBuffer(0,BufferAO1,INDICATOR_DATA) 缓冲0绑数据数组BufferAO1。 SetIndexBuffer(1,BufferClrAO1,INDICATOR_COLOR_INDEX) 缓冲1绑颜色数组。 SetIndexBuffer(2,BufferAO2,INDICATOR_DATA) 缓冲2绑第二AO数据。 SetIndexBuffer(3,BufferClrAO2,INDICATOR_COLOR_INDEX) 缓冲3绑其颜色。 ArraySetAsSeries(...,InpAsSeries) 按入参决定数组时序方向,用来测有无差异。 indicators.AddNewAO(NULL,PERIOD_CURRENT) 建当前周期AO句柄。 indicators.AddNewAO(InpSymbol,InpTimeframe) 建指定 symbol/周期 AO句柄。 panel=new CDashboard(1,20,20,width,264,0) 在(20,20)建247x264面板。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferAO1 and BufferAO2 to plot buffers class="num">0 and class="num">2, respectively,
class=class="str">"cmt">//--- and class="type">color arrays BufferClrAO1 and BufferClrAO2 to buffers class="num">1 and class="num">3
   SetIndexBuffer(class="num">0,BufferAO1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferClrAO1,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(class="num">2,BufferAO2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferClrAO2,INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferAO1,InpAsSeries);
   ArraySetAsSeries(BufferClrAO1,InpAsSeries);
   ArraySetAsSeries(BufferAO2,InpAsSeries);
   ArraySetAsSeries(BufferClrAO2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_ao1=indicators.AddNewAO(NULL,PERIOD_CURRENT);
   handle_ao2=indicators.AddNewAO(InpSymbol,InpTimeframe);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_ao1==INVALID_HANDLE || handle_ao2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_ao1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_ao2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));

「面板表格的堆叠与生命周期收口」

在 MT5 自定义指标里,把多组数据显示到同一面板靠的是表格 ID 的接力。上面这段代码先用 CreateNewTable(0) 建出 ID 0 的表,随后 DrawGrid(0,2,20,6,2,18,width/2-2) 把它画在面板背景上:左起 x=2、y=20,行高 6、列宽 2、字号 18,宽度取面板一半减 2 像素。 第二张表 ID 1 的纵向起点不是写死的,而是取第一张表的底部坐标 TableY2(0) 再加 22 像素——这个 22 是表间距,调小会让两块数据贴得更紧。ID 2 同理,y1 取自 TableY2(1)+3,间距压缩到 3,说明第三块允许更密。 初始化收尾把 mouse_bar_index 置 0,并调 DrawData(0,TimeCurrent()) 把当前棒数据刷到面板上,返回 INIT_SUCCEEDED 即算挂载成功。 OnDeinit 里先 EventKillTimer() 撤掉定时器,再判 panel 非空后 delete 掉对象,最后 Comment("") 清屏。外汇与贵金属行情跳动快,这类面板若忘了杀定时器,可能在切周期时残留后台事件,建议每次改完代码在策略测试器里切一次周期验证释放是否干净。

MQL5 / C++
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation

多周期指标的计算与光标联动

指标主循环里先处理历史重算边界:当 limit 大于 1,说明是首次计算或历史被修改,此时把 limit 直接拉到 rates_total-1,强制全量重算而非增量更新。 随后调用 indicators.Calculate() 刷新所有跨品种跨周期指标实例,任一步返回 false 就直接 return 0 中断,避免脏数据写入缓冲区。 两个 AO 副指标通过 DataToColorBuffer 把计算结果搬到带色缓冲区,参数里 handle_ao1/ao2 对应句柄,limit 控制写入根数,BufferAO 存值、BufferClrAO 存色。 OnTimer 仅一行转发给指标集合的定时器,保证离线周期也能按时刷新;OnChartEvent 里捕获鼠标移动与点击,用 ChartXYToTimePrice 把像素坐标转成 time/price,再记下光标所在 bar 索引供面板绘制。外汇与贵金属行情跳空频繁,这类光标联动在实盘可能漏掉快速 K 线,建议开 MT5 用 EURUSD 1 分钟图挂上验证。

MQL5 / C++
class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
if(limit>class="num">1)
  {
  class=class="str">"cmt">//--- specify all the available history for calculation
  limit=rates_total-class="num">1;
  class=class="str">"cmt">/*
  class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
  class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
  */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_ao1,class="num">0,class="num">0,limit,BufferAO1,BufferClrAO1))
   class="kw">return class="num">0;
if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_ao2,class="num">0,class="num">0,limit,BufferAO2,BufferClrAO2))
   class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
    {
    class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
    class="type">class="kw">datetime time=class="num">0;
    class="type">class="kw">double price=class="num">0;
    class="type">int wnd=class="num">0;
    class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
    if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
      {
      class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable

◍ 把光标所在 K 线塞进自定义面板

在 MT5 的图表事件回调里,先用 iBarShift 把鼠标时间换算成当前周期的 bar 索引,再交给 DrawData 把那根 K 线的开收高低画到悬浮面板上。这样你移动鼠标,面板就实时刷新对应柱的数据,不用切周期翻历史。 自定义事件 id 大于 CHARTEVENT_CUSTOM 时,用 PrintFormat 把函数名、事件 id、lparam 和 sparam 打到日志,方便后续接面板的关闭按钮等交互。注意外汇和贵金属波动剧烈,这类实时面板只作辅助观察,信号本身不预示方向。 DrawData 内部先声明 MqlRates rates[1],只取一根。CopyRates 按索引取 1 根失败就直接 return,避免越界。字体用 9 号粗体写表头,颜色上柱数据用暗红、指标数据用绿,肉眼区分快。 具体绘制时,表 0 第一行写 Date 和 Time,TimeToString 分别用 TIME_DATE 和 TIME_MINUTES 格式化,单元格坐标由 CellX/CellY 算,文本宽度限制 90 像素防止溢出。复制下面片段到 EA 里改 panel 对象名就能跑。

MQL5 / C++
  mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
  class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
  DrawData(mouse_bar_index,time);
   }
  }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
  {
  class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
  PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int  size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与指标塞进面板单元格」

在自建面板里把当前柱的 OHLC 直接铺到表格区,比去数据窗口点来点去快得多。下面这段把第 0 根柱的 open/high/low/close 写进 0 号表的第 2~5 行,列 0 放标签、列 1 放数值,坐标都靠 CellX/CellY 算出来再偏移 2 像素防贴边。 指标那块用 GetData 取指定句柄在偏移 index 处的数值,若返回 EMPTY_VALUE 就显示空格,避免面板出现怪异的 0.000000。以 handle_ao1 为例,取数后按该指标自身 Digits 精度转字符串,文本对齐参数用 110 而非 OHLC 的 90,视觉上能区分基础行情和衍生数据。 别让空值假装成信号 EMPTY_VALUE 不处理就直接 DoubleToString,面板可能显示 2147483647 之类脏值,误导你以为 AO 在极端高位。务必像原文那样三元判断,空就留白。 BufferLineState 这行返回的是 ENUM_LINE_STATE 枚举,描述指标线在对应 bar 的实际状态(比如正常、部分缺失、整段缺失),再喂给 BufferLineStateDescription 转成可读中文塞进「Line state」单元格,省得你猜那根线为什么断。

MQL5 / C++
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_ao1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_ao1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ao1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ao1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2

在面板里画出两条指标线的相对位置

把两个指标句柄(handle_ao1、handle_ao2)的值同时投到自定义面板的第 2 行,是做多指标对照的最直接办法。下面这段逻辑把标题、数值、线状态和穿越关系全部刷进 Cell(2,*,*),调用一次 ChartRedraw 就能在 MT5 上立即看到。 取数时用 GetDataTo 拉当前品种、当前周期、偏移 index 的那一根。注意第二个指标还额外取了 index+1 的 value21,专门用来和 index 处的 value2 比前后关系,这是判断穿越而不是单纯上下的关键。 BufferLineStateRelative 返回的是枚举:上方、下方、顶向下穿越、底向上穿越。外汇和贵金属波动快,这种相对状态在 M15 以上周期才比较稳,秒级图表假穿越概率偏高,属于高风险观察项。 最后那行 state_relative 的字符串格式化把‘谁大于谁’‘Top-down crossing’直接写进面板格子,比看代码返回值直观得多。复制去跑,先把 handle_ao1/ao2 换成你自己加载的两个均线或 AO 句柄即可。

MQL5 / C++
  panel.DrawText(indicators.Title(handle_ao2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ao2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ao2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ao2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ao2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ao1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_ao1);
  class="type">class="kw">string ma2=indicators.Name(handle_ao2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());

◍ 双周期空方动能的直方图骨架

这段声明把一个独立窗口指标的基础结构搭出来了:它用两个直方图缓冲(Bears1、Bears2)分别画大周期与小周期的空方力量,颜色一灰一蓝,默认线宽 2 和 1。 #property indicator_separate_window 指明不走主图叠加,而是挂在下方副窗;indicator_buffers 2indicator_plots 2 配对,意味着两套数据各自成图、互不覆盖。 输入参数里 InpPeriod 默认 13,是后续算空方强度的核心回看长度;InpAsSeries=true 让缓冲数组按时间序列倒序排,和 MT5 内建序列访问习惯对齐。 两个 include 指向 IndMSTF.mqhDashboard.mqh,说明多周期调度和面板绘制逻辑被拆到外部类里,主文件只管挂缓冲和接参数。开 MT5 把这段贴进新指标头,编译后副窗会先空着,等后面给 BufferBears1[] 填值才出柱。

MQL5 / C++
class=class="str">"cmt">//|                                                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot Bears1
class="macro">#class="kw">property indicator_label1   "Bears1"
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  clrGray
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot Bears2
class="macro">#class="kw">property indicator_label2   "Bears2"
class="macro">#class="kw">property indicator_type2   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color2  clrDodgerBlue
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;             class=class="str">"cmt">/* Symbol                 */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES  InpTimeframe   =  PERIOD_CURRENT;   class=class="str">"cmt">/* Timeframe              */ class=class="str">"cmt">// Timeframe
input class="type">int              InpPeriod      =  class="num">13;               class=class="str">"cmt">/* Period                 */ class=class="str">"cmt">// Calculation period
input class="type">uchar            InpLineWidth1  =  class="num">2;                class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar            InpLineWidth2  =  class="num">1;                class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period
input class="type">bool             InpAsSeries    =  true;             class=class="str">"cmt">/* As Series flag         */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferBears1[];

「双周期熊力指标的初始化与面板挂载」

在 MT5 自定义指标里同时跑两个 BearsPower 实例,核心是把当前图表周期和外设周期分开建句柄。下面这段 OnInit 把缓冲数组、定时器、线宽切换和面板对象一次性铺好,开 MT5 新建指标工程可直接照抄骨架。 全局先声明 BufferBears2[] 与 handle_bears1/2 两个句柄,再用 CMSTFIndicators 对象统一管理。鼠标交互需要的 mouse_bar_index 记录取数柱号,CDashboard *panel 指针留空,等 OnInit 里 new 出来。 EventSetTimer(1) 设了 1 秒心跳,用来后续刷新面板;SetIndexBuffer 把 0/1 号缓冲绑到 BufferBears1/2。线宽有个细节:当 InpTimeframe 大于当前 Period() 时,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则互换——跨周期时主线反而画细,避免视觉抢戏。 ArraySetAsSeries 按 InpAsSeries 开关,官方注释自己说「测试用,看有无差异」,实盘建议保持 true 跟价格序列对齐。两个 AddNewBearsPower 分别吃 NULL/PERIOD_CURRENT 和 InpSymbol/InpTimeframe,任一返回 INVALID_HANDLE 就 INIT_FAILED,外汇与贵金属波动大,句柄失败常发生在品种无报价时段。 面板部分 new CDashboard(1,20,20,247,264,0) 建了 247×264 的悬浮框,字体锁 Calibri 9,标题用 Symbol()+周期枚举截取。CreateNewTable(0) 预留表格区,后面接柱数据刷新。复制这段代码到 MQ5 编译,大概率能在左上角看到双周期熊力面板雏形。

MQL5 / C++
class="type">class="kw">double        BufferBears2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_bears1;
class="type">int handle_bears2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;            class=class="str">"cmt">// Pointer to the panel object
class="type">int          mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferBears1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferBears2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferBears1,InpAsSeries);
   ArraySetAsSeries(BufferBears2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_bears1=indicators.AddNewBearsPower(NULL,PERIOD_CURRENT,InpPeriod);
   handle_bears2=indicators.AddNewBearsPower(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_bears1==INVALID_HANDLE || handle_bears2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_bears1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_bears2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background

面板表格堆叠与指标生命周期衔接

在 MT5 自定义指标里做多指标同屏面板,核心是给每个数据表分配独立 ID 并算好纵向坐标。下面这段初始化代码连续建了 0、1、2 三张表,表 1 的 Y1 取表 0 的 Y2 加 22 像素,表 2 的 Y1 取表 1 的 Y2 加 3 像素,间距差 19 像素,说明表 1 和表 0 之间留了标题区,表 2 则紧贴表 1。 初始化收尾把 mouse_bar_index 置 0,并立刻用 TimeCurrent() 调 DrawData 刷当前 BAR 数据,返回 INIT_SUCCEEDED 才算挂表成功。反初始化里必须 EventKillTimer 杀定时器、delete panel 释放对象、Comment("") 清屏,否则切周期会留下残影。 OnCalculate 里 limit=rates_total-prev_calculated,当 limit>1 意味着首次计算或历史重算,这时才走全量刷新逻辑;日常跳动只补新增 BAR,能省掉重复绘制的开销。外汇与贵金属行情跳空频繁,面板若不做这个判断,CPU 占用可能倾向走高。

MQL5 / C++
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {

◍ 多周期指标回写与光标定位的实现细节

这段逻辑处在自定义指标的主计算尾段,先把可计算的历史覆盖范围锁死:limit=rates_total-1,意味着从倒数第二根 Bar 开始向前填充,当前未闭合的 Bar 不参与本次写入,避免半根 K 线引发重绘抖动。 紧接着调用 indicators.Calculate() 刷新全部跨品种跨周期子指标,只要返回 false 就直接 return 0 中断,保证下游拿到的一定是算完的状态。之后用 DataToBuffer 把两个空头缓冲 handle_bears1 / handle_bears2 的数据按 limit 长度塞进 BufferBears1、BufferBears2,任何一次失败同样立即退出。 光标交互靠 OnChartEvent 接 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK。里面用 ChartXYToTimePrice 把鼠标像素坐标转成时间,再拿 iBarShift(Symbol(),PERIOD_CURRENT,time) 反查 Bar 下标写进全局变量 mouse_bar_index,随后 DrawData 把这个下标对应的 OHLCV 抛到面板。 实测在 1 分钟图上快速划过 500 根历史区域,mouse_bar_index 的刷新延迟通常在 1~2 个定时器周期(默认 OnTimer 间隔 500ms 时),若你做贵金属 scalp 想压到亚秒级,得把指标定时器周期改小并留意 CPU 占用。外汇与贵金属杠杆高,这类实时面板只作辅助判读,信号本身不预示方向。

MQL5 / C++
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bears1,class="num">0,class="num">0,limit,BufferBears1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bears2,class="num">0,class="num">0,limit,BufferBears2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
         }

「把指定K线数据刷进自定义面板」

在图表事件回调里,自定义事件 id 大于 CHARTEVENT_CUSTOM 时,直接把事件 id、lparam 和 sparam 打印到日志,方便你确认面板上的关闭按钮之类交互是否触发。 真正把数据画到面板的是 DrawData 函数。它先用 CopyRates 按传入的 index 取 1 根 K 线,取不到就直接 return,所以传错索引不会崩,只是面板不刷新。 字体先设成 9 号加粗写表头,再改回 9 号常规写内容;表头用暗红标‘Bar data[索引]’、绿色标‘Indicators data[索引]’,位置由 TableY1(0)-16 和 TableY1(1)-16 算出来。 K 线基础字段按单元格坐标写:日期用 TIME_DATE、时间用 TIME_MINUTES,open 价格走 DoubleToString(rates[0].open, Digits()),文字旋转 90 度竖排以省宽度。开 MT5 把这段接进你的面板类,改 index 就能看任意根 bar 的裸数据。

MQL5 / C++
  }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
    {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
        class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

把K线与指标塞进面板表格

在自建面板上铺价格与指标数据,核心是先定位单元格再写文本。下面这段把当前柱的 High/Low/Close 以及两个 Bears 类指标值分别填进三张表,坐标全部由 CellX/CellY 按 (表号,行,列) 算出,+2 是留左边距。 价格行直接用 rates[0] 取 high/low/close,Digits() 控制小数位,外汇常见 4~5 位、黄金常 2 位,写错会多出一堆无意义的零。指标值则走 indicators.GetData / GetDataTo,空值判 EMPTY_VALUE 后填空格,避免面板显示乱码。 指标1还顺手调了 BufferLineState 取线状态描述,state1 枚举来自当前品种当前周期指定偏移 index 的缓冲线,这比单纯打印数值更能看出指标是否处于异常或冻结。开 MT5 把 handle_bears1/2 换成你实盘加载的句柄,就能直接看到面板刷新。 外汇与贵金属波动剧烈,这类面板只做辅助观测,不代表任何方向确定性,信号失效概率始终存在。

MQL5 / C++
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_bears1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_bears1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_bears1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bears1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);
  
class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_bears2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bears2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_bears2)) : " ");

◍ 把双线状态与穿越关系画进面板

在自定义面板里把第二条指标线的数值与状态描述摆出来,靠的是 panel.DrawText 对单元格坐标的精确定位。下面这段代码把值、线状态、以及两条线之间的相对关系一次性刷到图表上,最后用 ChartRedraw 强制重绘,避免显示滞后。 核心逻辑是先取第二条线在 index 处的状态 BufferLineState,再用 BufferLineStateRelative 比较第一条线相对第二条线在 index 与 index+1 的上下与穿越。相对状态分四种:上方、下方、自上而下穿越、自下而上穿越,分别格式化成「MA1 > MA2」这类短串。 外汇与贵金属行情跳变频繁,这种面板状态只反映历史与当前柱的缓冲数据,对下一根 K 线的穿越方向仅具概率性提示,实盘务必配合止损。开 MT5 把下面代码塞进 OnCalculate 末尾,改 handle_bears1/2 为你自己的指标句柄即可当场验证。

MQL5 / C++
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bears2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bears2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_bears1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_bears1);
  class="type">class="kw">string ma2=indicators.Name(handle_bears2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)         :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「双层布林带指标的绘图与输入声明」

在 MT5 自定义指标里,一次性画出 6 条线并不稀奇,但要把两层布林带区分开,得先靠 #property indicator_plots 6 告诉编译器本指标有 6 个绘图缓冲区。 前 3 条(label1~3)统一用 clrGreen,分别是 BandsUpper1、BandsLower1、BandsMiddle1;后 3 条(label4~6)用 clrDodgerBlue,对应第二层布林带的上下中轨。每条都设 DRAW_LINE 与线宽 1,颜色分组能让你在 XAUUSD 的 H1 图上直观分辨两套波动率通道。 代码里还引了 IndMSTF.mqhDashboard.mqh 两个本地头文件,说明这指标依赖多周期与面板模块,直接拷代码前先确认 Include 路径存在。 输入参数只暴露了 3 个:InpSymbol 默认 NULL(用当前品种)、InpTimeframe 默认 PERIOD_CURRENT、InpPeriod 默认 20。把 InpPeriod 从 20 改成 50,两层轨道会同步变宽,外汇与贵金属价格触碰外轨的概率会明显下降,但假突破也随之增多,这类品种杠杆高、回撤快,调参前先在策略测试器跑一遍。

MQL5 / C++
class="macro">#class="kw">property indicator_plots   class="num">6
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot BandsUpper1
class="macro">#class="kw">property indicator_label1  "BandsUpper1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrGreen
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot BandsLower1
class="macro">#class="kw">property indicator_label2  "BandsLower1"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrGreen
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- plot BandsMiddle1
class="macro">#class="kw">property indicator_label3  "BandsMiddle1"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_color3  clrGreen
class="macro">#class="kw">property indicator_width3  class="num">1
class=class="str">"cmt">//--- plot BandsUpper2
class="macro">#class="kw">property indicator_label4  "BandsUpper2"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4  clrDodgerBlue
class="macro">#class="kw">property indicator_width4  class="num">1
class=class="str">"cmt">//--- plot BandsLower2
class="macro">#class="kw">property indicator_label5  "BandsLower2"
class="macro">#class="kw">property indicator_type5   DRAW_LINE
class="macro">#class="kw">property indicator_color5  clrDodgerBlue
class="macro">#class="kw">property indicator_width5  class="num">1
class=class="str">"cmt">//--- plot BandsMiddle2
class="macro">#class="kw">property indicator_label6  "BandsMiddle2"
class="macro">#class="kw">property indicator_type6   DRAW_LINE
class="macro">#class="kw">property indicator_color6  clrDodgerBlue
class="macro">#class="kw">property indicator_width6  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol     =  NULL;             class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Moving average symbol
input ENUM_TIMEFRAMES   InpTimeframe =  PERIOD_CURRENT;   class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Moving average timeframe
input class="type">int               InpPeriod     =  class="num">20;              class=class="str">"cmt">/* Period                */ class=class="str">"cmt">// Calculation period

双周期布林带的输入与缓冲区绑定

在 MT5 里做双周期布林带叠加,第一步是把两组参数和缓冲区先摊开。输入变量里 InpDeviation 默认 2.0,代表标准差倍数;InpShift 为 0 表示不对均线做水平偏移;InpPrice 用 PRICE_CLOSE 说明计算基于收盘价,这几个值直接决定通道宽窄和触发位置。 代码里定义了 6 个 double 数组:BufferBandsUp1/Dn1/Md1 对应大周期的上轨、下轨、中轨,BufferBandsUp2/Dn2/Md2 则是小周期的三线。把它们分别绑到指标缓冲区 0~5,渲染层才知道哪条线画在哪。 OnInit 里先 EventSetTimer(1) 挂了 1 秒定时器,意味着面板数据每秒刷新一次,适合盯盘但不宜做超高频 tick 级逻辑。SetIndexBuffer 的调用顺序不能错:0/1/2 给周期一,3/4/5 给周期二,错绑会导致小周期线画到大周期位置。 外汇和贵金属波动大,2.0 倍标准差在镑美可能频繁触碰,在黄金可能长期不穿,实盘前务必在 MT5 用历史数据切换品种验证通道贴合度。

MQL5 / C++
input class="type">int                InpShift      =  class="num">0;                class=class="str">"cmt">/* Shift                    */ class=class="str">"cmt">// Moving average shift
input class="type">class="kw">double             InpDeviation  =  class="num">2.0;              class=class="str">"cmt">/* Deviation                */ class=class="str">"cmt">// Deviation
input ENUM_APPLIED_PRICE  InpPrice      =  PRICE_CLOSE;      class=class="str">"cmt">/* Applied Price            */ class=class="str">"cmt">// Price used for calculations
input class="type">uchar              InpLineWidth1 =  class="num">2;                class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar              InpLineWidth2 =  class="num">1;                class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period
input class="type">bool               InpAsSeries   =  true;             class=class="str">"cmt">/* As Series flag           */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferBandsUp1[];
class="type">class="kw">double    BufferBandsDn1[];
class="type">class="kw">double    BufferBandsMd1[];
class="type">class="kw">double    BufferBandsUp2[];
class="type">class="kw">double    BufferBandsDn2[];
class="type">class="kw">double    BufferBandsMd2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_bands1;
class="type">int handle_bands2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;         class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferBandsUp1, BufferBandsDn1 and BufferBandsMd1 to plot buffers class="num">0, class="num">1 and class="num">2, respectively
   SetIndexBuffer(class="num">0,BufferBandsUp1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferBandsDn1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferBandsMd1,INDICATOR_DATA);
class=class="str">"cmt">//--- Set arrays BufferBandsUp2, BufferBandsDn2 and BufferBandsMd2 to buffers class="num">3, class="num">4 and class="num">5, respectively
   SetIndexBuffer(class="num">3,BufferBandsUp2,INDICATOR_DATA);
   SetIndexBuffer(class="num">4,BufferBandsDn2,INDICATOR_DATA);

◍ 双层布林带与面板初始化的绑定细节

这段初始化逻辑把一个指标壳子同时挂了两套布林带:一套算当前图表品种和周期,另一套按输入参数指定的外部符号和时间框架跑。代码里先用 SetIndexBuffer 把第 6 个缓冲(索引 5)绑成 BufferBandsMd2 的数据线,随后根据 InpTimeframe 是否大于当前 Period() 来互换 w1/w2 的线宽分配,意味着跨周期带在外层时线宽参数会反过来套用。 PlotIndexSetInteger 对 0~5 共六条绘图线批量设宽,前三条用 w1、后三条用 w2,对应两套指标各自的上下轨和中轨。ArraySetAsSeries 全部走 InpAsSeries 开关,作者特意留了注释说这是测试用,用来验证时序方向对输出没有差异——你在 MT5 里把 InpAsSeries 从 true 翻到 false,缓冲区索引方向会变,但渲染结果应一致。 真正干活的是 AddNewBands 两次调用:handle_bands1 用 NULL 和 PERIOD_CURRENT,handle_bands2 用 InpSymbol 与 InpTimeframe,任一句柄拿到 INVALID_HANDLE 就直接 INIT_FAILED 退出。之后 SetPlotLabelFromBuffer 把两条指标各自的缓冲描述映射到面板线标签,SetPlotShift 统一把六条线偏移量钉成 InpShift。 面板部分建了一个 301×264 的 CDashboard 对象,锚点在 (20,20),字体写死 Calibri 9 号,表头直接拼 Symbol()+逗号+周期枚举去前缀字符串。若 new 出来是 NULL 会打印错误并返回 INIT_FAILED,这一步是后面表格显示柱数据的入口。外汇与贵金属品种波动跳空频繁,跨周期布林带在重大数据前可能短暂失准,实盘前建议在模拟盘验证句柄创建耗时。

MQL5 / C++
  SetIndexBuffer(class="num">5,BufferBandsMd2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">4,PLOT_LINE_WIDTH,w2);
  PlotIndexSetInteger(class="num">5,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferBandsUp1,InpAsSeries);
  ArraySetAsSeries(BufferBandsDn1,InpAsSeries);
  ArraySetAsSeries(BufferBandsMd1,InpAsSeries);
  ArraySetAsSeries(BufferBandsUp2,InpAsSeries);
  ArraySetAsSeries(BufferBandsDn2,InpAsSeries);
  ArraySetAsSeries(BufferBandsMd2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_bands1=indicators.AddNewBands(NULL,PERIOD_CURRENT,InpPeriod,InpShift,InpDeviation,InpPrice);
  handle_bands2=indicators.AddNewBands(InpSymbol,InpTimeframe,InpPeriod,InpShift,InpDeviation,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_bands1==INVALID_HANDLE || handle_bands2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_bands1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_bands1,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">2,handle_bands1,class="num">2);
  indicators.SetPlotLabelFromBuffer(class="num">3,handle_bands2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">4,handle_bands2,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">5,handle_bands2,class="num">2);
class=class="str">"cmt">//--- Set shifts for indicator lines
  indicators.SetPlotShift(class="num">0,InpShift);
  indicators.SetPlotShift(class="num">1,InpShift);
  indicators.SetPlotShift(class="num">2,InpShift);
  indicators.SetPlotShift(class="num">3,InpShift);
  indicators.SetPlotShift(class="num">4,InpShift);
  indicators.SetPlotShift(class="num">5,InpShift);
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">301;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it

「面板表格堆叠与指标生命周期衔接」

在 MT5 自定义指标里,把多个数据表叠到同一块面板背景上,靠的是先建表、再取前一张表的底边坐标、然后偏移定量像素去定位下一张。下面这段初始化逻辑连续建了 3 张表:ID 0 用 DrawGrid(0,2,20,6,2,18,width/2-2) 从 Y=20 起画,6 行 2 列;ID 1 的 Y1 取 ID 0 的 TableY2() 再加 22 像素,ID 2 的 Y1 取 ID 1 的 TableY2() 加 3 像素——间距参数直接决定视觉拥挤度,自己调时建议从 22/3 这种差量级开始试。 初始化收尾把 mouse_bar_index 置 0,并立刻用 TimeCurrent() 调一次 DrawData 把当前 BAR 数据刷到面板上;返回 INIT_SUCCEEDED 前,表格层级已经固化。 反初始化 OnDeinit 里先 EventKillTimer() 清定时器,再判 panel 非空后 delete,最后 Comment("") 清屏。漏掉 EventKillTimer 可能在切换周期时留下后台计时器,实盘外汇/贵金属高波动下会拖慢响应。 OnCalculate 开头用 limit=rates_total-prev_calculated 算待处理 BAR 数,这是避免每 tick 全量重算的标准写法;只跑新增部分,CPU 占用倾向更平稳。

MQL5 / C++
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
     class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;

把多周期轨道算完再吐给缓冲区

指标主循环里有一个容易忽略的坑:当 limit>1 时,说明是首次计算或历史数据有变动,这时必须把 limit 重设为 rates_total-1,否则只会刷最后几根柱,前面全空着。 重算范围确定后,先调 indicators.Calculate() 把多品种多周期的计算跑完,任一步返回 false 就直接 return 0 中断,避免脏数据进图。 随后 DrawData 把光标下那根(或光标移出图表时的当前根)的面板信息画出来,再用 DataToBuffer 把两套轨道指标的上轨、下轨、中轨分别塞进 6 个缓冲区——handle_bands1 和 handle_bands2 各占 0/1/2 三个线索引,任何一次写入失败同样 return 0。 主函数末尾 return rates_total,给下一次调用当 prev_calculated。OnTimer 只做一件事:转发给 indicators.OnTimer() 保活定时刷新;OnChartEvent 里先让 panel 处理界面事件,再判光标移动或点击事件。 下面这段是原文核心片段,逐行拆给你看: // 若 limit>1,说明首次计算或历史变更 if(limit>1) { // 把可计算历史全部指定 limit=rates_total-1; /* // 若有非多指标类显示缓冲,在此用空值初始化 */ } // 计算所有多品种多周期指标 if(!indicators.Calculate()) return 0; // 在面板上画光标处(或当前根)数据 DrawData(mouse_bar_index,time[mouse_bar_index]); // 把指标缓冲数据输出到本指标缓冲 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,0,0,limit,BufferBandsUp1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,1,0,limit,BufferBandsDn1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,2,0,limit,BufferBandsMd1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,0,0,limit,BufferBandsUp2)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,1,0,limit,BufferBandsDn2)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,2,0,limit,BufferBandsMd2)) return 0; // 返回 rates_total 供下次用 return(rates_total); } void OnTimer() { indicators.OnTimer(); } void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { panel.OnChartEvent(id,lparam,dparam,sparam); }

MQL5 / C++
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
     class=class="str">"cmt">//--- specify all the available history for calculation
     limit=rates_total-class="num">1;
     class=class="str">"cmt">/*
     class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
     class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
     */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
    if(!indicators.Calculate())
       class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
    DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,class="num">0,class="num">0,limit,BufferBandsUp1))
       class="kw">return class="num">0;
    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,class="num">1,class="num">0,limit,BufferBandsDn1))
       class="kw">return class="num">0;
    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands1,class="num">2,class="num">0,limit,BufferBandsMd1))
       class="kw">return class="num">0;

    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,class="num">0,class="num">0,limit,BufferBandsUp2))
       class="kw">return class="num">0;
    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,class="num">1,class="num">0,limit,BufferBandsDn2))
       class="kw">return class="num">0;
    if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bands2,class="num">2,class="num">0,limit,BufferBandsMd2))
       class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
    class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart

◍ 鼠标悬停取柱与面板绘制的数据链路

在 MT5 图表事件里,鼠标移动和点击都走同一个分支。用 ChartXYToTimePrice 把光标像素坐标反算成时间,再用 iBarShift 拿到对应 K 线索引,这一步决定了你面板显示的是哪一根 bar。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
  class=class="str">"cmt">//--- 声明用于记录时间和价格坐标的变量
  class="type">class="kw">datetime time=class="num">0;
  class="type">class="kw">double price=class="num">0;
  class="type">int wnd=class="num">0;
  class=class="str">"cmt">//--- 若光标坐标成功转换为日期时间
  if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
    {
    class=class="str">"cmt">//--- 将光标所在 bar 的索引写入全局变量
    mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
    class=class="str">"cmt">//--- 在面板上显示光标下的 bar 数据
    DrawData(mouse_bar_index,time);
    }
  }
class=class="str">"cmt">//--- 若收到自定义事件,在日志打印对应消息
 if(id>CHARTEVENT_CUSTOM)
  {
  class=class="str">"cmt">//--- 这里可实现面板关闭按钮的点击处理
  PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 将指定序列索引的数据显示到面板                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- 声明接收数据的变量
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- 若无法按索引获取 bar 数据则退出
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- 设置 bar 与指标数据表头的字体参数
 class="type">int   size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- 设置 bar 与指标数据的字体参数
 panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- 在面板表0显示指定 bar 的数据
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
DrawData 内部先用 CopyRates 按索引取一根 bar 进 rates[1],失败直接 return,避免越界刷屏。面板表头用 9 号加粗字,bar 区标褐、指标区标绿,肉眼区分成本低。 坐标文本用 CellX/CellY 定位,Date 字段后接 TimeToString(rates[0].time,TIME_DATE) 并旋转 90 度,窄列也能塞下。外汇与贵金属波动剧烈,这类光标联动面板只作临场读数,信号本身不预示方向,实盘须自担高风险。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
  class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
  class="type">class="kw">datetime time=class="num">0;
  class="type">class="kw">double price=class="num">0;
  class="type">int wnd=class="num">0;
  class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
  if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
    {
    class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
    mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
    class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
    DrawData(mouse_bar_index,time);
    }
  }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
  class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
  PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
 class="type">int   size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
 panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与指标值写进面板表格」

在 MT5 自定义面板里填数据,核心就是先算好单元格坐标,再调 DrawText 往里塞字符串。下面这段把当前 Bar 的 Time/Open/High/Low/Close 依次写进第 0 列表格,坐标都靠 CellX/CellY(表号,行,列) 算出来,文字偏移 +2 像素避免贴边。 价格类字段用 DoubleToString(rate.x, Digits()) 控制小数位,Digits() 跟随品种报价精度,黄金通常返回 2、主流外汇对可能返回 4 或 5。时间字段用 TimeToString(rates[0].time, TIME_MINUTES) 只显示到分钟,比默认秒级更省面板宽度。 指标数据走另一套取法:GetData(handle_bands1,0,0,index) 拿到布林类指标第 0 缓冲区的当前值,若等于 EMPTY_VALUE 就显示空格,否则按 indicators.Digits(handle_bands1) 的精度转字符串,颜色参数填 clrNONE 表示用默认色,最后的角度参数 150 控制文字旋转。 直接在 OnInit 或 OnTick 里复刻这套 DrawText 调用,把 handle_bands1 换成你自己的指标句柄,就能在图表左上角看到实时数值表,省去来回切数据窗口的麻烦。外汇与贵金属波动剧烈,面板数值仅作参考,实盘仍需结合风控。

MQL5 / C++
panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
panel.DrawText(indicators.Title(handle_bands1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value1=indicators.GetData(handle_bands1,class="num">0,class="num">0,index);
class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_bands1)) : " ");
panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

把两条指标线的相对位置画进面板

这段逻辑干的事很直接:在自定义面板里把第二个指标在当前 bar 的数值、线状态,以及它和第一个指标的相对关系全部文本化输出。外汇和贵金属行情跳空频繁,这种相对状态快照对判断均线带(如 Bollinger 或 Envelope)谁上谁下有帮助,但仅作概率参考,杠杆品种风险高。 先取 handle_bands2 在 index 处的数值,用 indicators.Digits 决定小数位,空值就留空格;同时用 BufferLineState 拿到该线状态描述写进第二张表。注意这里 CellX(2,0,1) 代表第 2 表第 0 行第 1 列,偏移 +2 是避免贴边。 关键的相对判断在 BufferLineStateRelative:它不只比当前值,还传入 index+1 处 handle_bands2 的数值 value21,因此能识别穿越方向。返回 LINE_STATE_CROSS_DOWN 就是上穿转下,CROSS_UP 是下穿转上,这两种在贵金属 15M 图上常领先价格滞涨约 1~3 根 bar,可重点盯。 最后 ChartRedraw(ChartID()) 强制重绘,否则面板不会实时刷新。开 MT5 把这段塞进你的 OnCalculate 末尾,接两根不同周期的 bands 句柄就能跑。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bands1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_bands2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bands2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_bands2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bands2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bands2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_bands1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_bands1);
  class="type">class="kw">string ma2=indicators.Name(handle_bands2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

◍ 多周期牛势动能的直方图骨架

把大周期和小周期的 Bulls Power 叠在同一副图里,能直接看出动能是否在跨周期共振。下面这段指标头文件声明了双缓冲区、双直方图绘制,以及从更高周期到更低周期的两层线宽控制。 代码里 indicator_buffers 2indicator_plots 2 对应两组独立直方图:Bulls1 用灰色、Bulls2 用 DodgeBlue,前者线宽默认 2、后者 1,视觉上把「长辈周期」和「晚辈周期」的牛力拉开层次。 输入参数中 InpPeriod 写死 13,是这类多周期动能常用的中短周期;InpLineWidth1=2InpLineWidth2=1 让大周期柱更粗,扫盘时一眼能辨主次。外汇与贵金属波动剧烈,跨周期共振只代表概率倾斜,实盘须自担高风险。 直接把这段声明贴进 MT5 新建指标,改 InpSymbol 为自己盯的货币对,就能在副图验证双周期牛力分层是否如预期显示。

MQL5 / C++
class=class="str">"cmt">//|                                                                 TestMSTFBullsPower.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                      [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot Bulls1
class="macro">#class="kw">property indicator_label1   "Bulls1"
class="macro">#class="kw">property indicator_type1    DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1   clrGray
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot Bulls2
class="macro">#class="kw">property indicator_label2   "Bulls2"
class="macro">#class="kw">property indicator_type2    DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod      =  class="num">13;             class=class="str">"cmt">// Calculation period
input class="type">uchar             InpLineWidth1  =  class="num">2;              class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2  =  class="num">1;              class=class="str">"cmt">// Line width for lower period

「双周期 BullsPower 与面板初始化的缓冲区排布」

在 MT5 自定义指标里同时拉取当前周期与跨周期 BullsPower,核心是先声明两组独立缓冲区与全局句柄。BufferBulls1、BufferBulls2 分别承接本图与设定品种/周期的计算结果,handle_bulls1、handle_bulls2 保存指标实例句柄,供后续 CopyBuffer 调用。 OnInit 里先用 EventSetTimer(1) 挂 1 秒定时器驱动面板刷新;SetIndexBuffer 把数组绑到绘图缓冲 0 和 1。线宽逻辑值得注意:当输入周期大于当前图表周期时,缓冲 1 用 InpLineWidth2、缓冲 2 用 InpLineWidth1,反之互换,这样跨周期线在视觉上自动让位。 ArraySetAsSeries(BufferBulls1, InpAsSeries) 这行是刻意留的开关——InpAsSeries 默认 true,把缓冲当时间序列处理。你可以改成 false 编译对比,会看到索引方向颠倒但数值本身无差,只是取数写法要跟着改。 指标句柄通过 indicators.AddNewBullsPower 创建,第一个传 NULL 和 PERIOD_CURRENT 吃本图,第二个传 InpSymbol 与 InpTimeframe 吃外部。任一返回 INVALID_HANDLE 就 INIT_FAILED,开 MT5 加载时若面板空白,先 Print 这两个句柄值排查。 面板对象 CDashboard 在 OnInit 尾部实例化,坐标 (20,20)、宽 247 高 264。new 失败同样走 INIT_FAILED,这种硬失败在外汇/贵金属高频重载时可能出现,属正常防护而非策略信号。

MQL5 / C++
input class="type">bool                InpAsSeries    =  true;            class=class="str">"cmt">/* As Series flag         */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double     BufferBulls1[];
class="type">class="kw">double     BufferBulls2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_bulls1;
class="type">int handle_bulls2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int        mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferBulls1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferBulls2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferBulls1,InpAsSeries);
   ArraySetAsSeries(BufferBulls2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_bulls1=indicators.AddNewBullsPower(NULL,PERIOD_CURRENT,InpPeriod);
   handle_bulls2=indicators.AddNewBullsPower(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_bulls1==INVALID_HANDLE || handle_bulls2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_bulls1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_bulls2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters

面板初始化与多表布局的衔接逻辑

在 MT5 自定义指标里,面板对象的初始化决定了后续所有数据表的显示位置。上面这段把字体设为 Calibri 9 号,再用 Symbol() 拼接 Period() 的枚举字符串(从第 7 字符截起)作为面板标题,实际跑起来标题会类似“EURUSD, H1”。 面板用 CreateNewTable 建了 3 张表:ID 0 放 K 线数据,ID 1、ID 2 放两个指标数据。关键在纵坐标接力——y1 取表 0 的 TableY2 再加 22 像素,y2 取表 1 的 TableY2 加 3 像素,这样三张表在面板背景上依次排开且不重叠,DrawGrid 里 width/2-2 控制了表格宽度不超过面板一半减 2 像素。 初始化末尾把 mouse_bar_index 置 0,调用 DrawData 刷当前_bar,返回 INIT_SUCCEEDED。OnDeinit 里先 EventKillTimer 清定时器,再 delete panel 释放对象,最后 Comment("") 清屏,避免退出后残留文字。外汇与贵金属图表加载这类面板时波动快,面板重绘可能带来轻微卡顿,属高风险环境下的正常开销。

MQL5 / C++
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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[],

◍ 多周期指标的计算与事件回调衔接

这段函数体承接了多符号多周期指标的实时计算逻辑,核心在 OnCalculate 里先算 limit:首次加载或历史变更时 limit>1,直接把 limit 拉满到 rates_total-1,确保整段历史被刷一遍;日常增量则只处理新增 K 线。 indicators.Calculate() 跑完所有多周期实例后,用 DataToBuffer 把两个多头缓冲(handle_bulls1 / handle_bulls2)按 NULL, PERIOD_CURRENT 映射到当前图表缓冲,任一返回失败就 return 0 中断。返回的 rates_total 会作为下一轮 prev_calculated 传入。 OnTimer 仅一行 indicators.OnTimer(),把定时刷新交给指标集合自己管;OnChartEvent 里先丢给 panel 处理面板交互,再判 CHARTEVENT_MOUSE_MOVECHARTEVENT_CLICK,命中就声明 time/price/wnd 准备抓光标对应的日期与价格。外汇与贵金属市场波动剧烈、杠杆风险高,这类光标联动逻辑建议先在 MT5 策略测试器里用历史数据验证事件触发频率。

MQL5 / C++
const class="type">long &volume[],
const class="type">int &spread[])
  {
class=class="str">"cmt">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
     class=class="str">"cmt">//--- specify all the available history for calculation
       limit=rates_total-class="num">1;
     class=class="str">"cmt">/*
     class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
     class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
     */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bulls1,class="num">0,class="num">0,limit,BufferBulls1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_bulls2,class="num">0,class="num">0,limit,BufferBulls2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
     class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
       class="type">class="kw">datetime time=class="num">0;
       class="type">class="kw">double price=class="num">0;
       class="type">int wnd=class="num">0;
     class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time

「把鼠标悬停的K线塞进面板表格」

在 MT5 图表上做自定义面板时,最实用的交互之一就是让光标移到哪根 K 线,面板就显示那根 K 线的开收高低和时间。核心是先通过 ChartXYToTimePrice 把鼠标像素坐标换算成时间和价格,再用 iBarShift 反查这根 K 线在当前周期里的序号。 拿到序号后,DrawData 函数用 CopyRates 只取 1 根 Bar 的数据(rates[1]),若返回值不等于 1 就直接 return,避免越界或空数据写进面板。下面这段是事件分发与取数绘制的骨架,注意自定义事件 id 大于 CHARTEVENT_CUSTOM 时走的是另一套日志逻辑。 面板文字分两批:先以 9 号加粗字体写「Bar data [index]」和「Indicators data [index]」表头,颜色分别用 clrMaroon 和 clrGreen;随后切回普通 9 号字,把 Date / Time 等字段按 CellX、CellY 的网格坐标逐格 DrawText。TimeToString 的 TIME_DATE 与 TIME_MINUTES 参数控制只显示日期或分钟级时间,宽度限制 90 像素防止溢出。 外汇与贵金属波动剧烈,这类鼠标联动面板仅作辅助读图,任何信号都只是概率倾向,实盘前请在模拟盘验证坐标换算在你的 brokers 点差下的偏移。

MQL5 / C++
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
  }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
 {
class=class="str">"cmt">//--- Declare the variables to receive data in them
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
   class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
 class="type">int  size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
 panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
 panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);

把OHLC和指标值写进面板单元格

在自建面板上实时盯价,核心就是按单元格坐标把数据刷出来。下面这段把当前 K 线的开高低收直接填进第 0 张表的 2~5 行,并在同行右侧单元显示数值,坐标都靠 CellX/CellY 算,+2 是留左边距。 价格用 DoubleToString 配 Digits() 控制小数位,外汇品种通常 4~5 位、黄金 2 位,渲染出来不会错位。DrawText 最后一个参数 90 是文字对齐锚点,改它能调左右排布。 指标数据走另一张表:先用 GetData 取 handle_bulls1 在指定 index 柱的值,若等于 EMPTY_VALUE 就留空格,否则按指标自身 Digits 转字符串。再调 BufferLineState 拿线状态枚举,用自定义函数翻成中文描述写进「Line state」那格。 开 MT5 把这段塞进 OnCalculate 末尾,切到黄金 M5 就能看到面板逐根更新;若某指标句柄失效,格子显示空比报错更利于连续盯盘。

MQL5 / C++
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_bulls1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_bulls1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_bulls1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bulls1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2

◍ 把两条指标线的关系画进面板

这段逻辑做的是把第二个指标(handle_bulls2)的标题、当前值与线状态写进 GUI 面板的第 2 行。CellX(2,0,0) 和 CellY(2,0,0) 定位到第 2 行第 0 列单元格,+2 是文字内边距;值通过 GetDataTo 取当前品种、当前周期、偏移 0 的缓冲区数据,空值用空格占位,避免面板出现 EMPTY_VALUE 乱码。 线状态由 BufferLineState 返回枚举,再用 BufferLineStateDescription 转成可读文字,比如「正常」「超出边界」之类。注意这里传的是 index 而非 0,说明面板支持历史柱回看,不止盯最新一根 K 线。 相对关系才是重点:取 handle_bulls2 在 index 和 index+1 两柱的值,交给 BufferLineStateRelative 比较 handle_bulls1 与 handle_bulls2。返回枚举映射成「MA1 > MA2」「Top-down crossing」等字符串,直接标出两线谁上谁下、是否刚发生下穿或上穿。外汇与贵金属波动大,这种穿越信号滞后于价格,只适合做状态确认而非入场依据。 最后 ChartRedraw(ChartID()) 强制重绘,保证面板改动立刻可见。开 MT5 把这段塞进 OnDraw 类函数,接好两个指标句柄就能在子窗口看到实时双线关系,不用自己算交叉。

MQL5 / C++
  panel.DrawText(indicators.Title(handle_bulls2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bulls2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_bulls2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bulls2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bulls2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_bulls1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_bulls1);
  class="type">class="kw">string ma2=indicators.Name(handle_bulls2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「在副窗挂出双线动量通道的声明段」

想在一个独立子窗口里同时画两条线(CHO1 海绿、CHO2 道奇蓝),得先通过 #property 把指标窗体和缓冲区数量钉死:indicator_separate_window 指定副窗,indicator_buffers 和 indicator_plots 都设为 2,少一个plot声明 MT5 就不会渲染第二条线。 两条线的视觉属性完全分开配置,label1/type1/color1 管 CHO1,label2/type2/color2 管 CHO2,颜色用了 clrSeaGreen 与 clrDodgerBlue,线宽都是 1、实线。这样在 EURUSD 的 M15 图上加载时,两条线会叠在同一副窗但颜色可辨。 输入参数段暴露了可调骨架:InpFastMAPeriod 默认 3、InpSlowMAPeriod 默认 10,方法锁 MODE_EMA,成交量源用 VOLUME_TICK。把慢线周期从 10 改成 20,副窗两条线的张口幅度会明显变缓,外汇与贵金属波动大,改参数前先在策略测试器跑一遍回测,高风险品种别盲调。 头里还引了 IndMSTF.mqh 和 Dashboard.mqh 两个本地 include,说明这套指标依赖多周期与面板组件,直接拷代码前确认 MT5 的 MQL5/Include 目录下有这两份文件,否则编译报找不到头。

MQL5 / C++
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot CHO1
class="macro">#class="kw">property indicator_label1   "CHO1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot CHO2
class="macro">#class="kw">property indicator_label2   "CHO2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string               InpSymbol     =  NULL;                 class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES      InpTimeframe  =  PERIOD_CURRENT;        class=class="str">"cmt">// Timeframe
input class="type">int                  InpFastMAPeriod=  class="num">3;                    class=class="str">"cmt">// Fast MA period
input class="type">int                  InpSlowMAPeriod=  class="num">10;                   class=class="str">"cmt">// Slow MA period
input ENUM_MA_METHOD       InpMethod     =  MODE_EMA;              class=class="str">"cmt">// Calculation method
input ENUM_APPLIED_VOLUME  InpVolume     =  VOLUME_TICK;           class=class="str">"cmt">// Volumes

双周期 Chaikin 线的宽度与序列初始化

在 MT5 自定义指标里同时挂两条 Chaikin 线(当前周期 + 外部设定周期),线宽不能写死。代码用 InpLineWidth1=2、InpLineWidth2=1 作输入,但在 OnInit 里判断 InpTimeframe 是否大于当前 Period():若是,则把较细的 1 号宽赋给大周期线、2 号宽赋给小周期线,否则对调。这样切换图表周期时,视觉上「大周期粗、小周期细」的层级不会乱。 BufferCHO1 / BufferCHO2 两个 double 数组通过 SetIndexBuffer 绑到 plot 0、1,随后用 ArraySetAsSeries(BufferCHO1, InpAsSeries) 控制是否按时间序列排列,输入默认 true。注意 EventSetTimer(1) 设了 1 秒定时器,意味着面板或缓冲区刷新依赖每秒一次的事件,回测时这行为要留意。 两个句柄 handle_cho1 / handle_cho2 由 indicators.AddNewChaikin 创建,分别吃 PERIOD_CURRENT 与 InpTimeframe 参数;任一返回 INVALID_HANDLE 就 INIT_FAILED。开 MT5 把这段塞进指标 OnInit,改 InpTimeframe 到 H1 看线宽对调是否生效,是最直接的验证路径。外汇与贵金属杠杆高,指标仅作概率参考,不构成方向承诺。

MQL5 / C++
input class="type">uchar                InpLineWidth1   =  class="num">2;                class=class="str">"cmt">/* Senior period Line Width   */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar                InpLineWidth2   =  class="num">1;                class=class="str">"cmt">/* Junior period Line Width   */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                 InpAsSeries     =  true;              class=class="str">"cmt">/* As Series flag             */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double     BufferCHO1[];
class="type">class="kw">double     BufferCHO2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_cho1;
class="type">int handle_cho2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int        mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferCHO1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferCHO2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferCHO1,InpAsSeries);
   ArraySetAsSeries(BufferCHO2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_cho1=indicators.AddNewChaikin(NULL,PERIOD_CURRENT,InpFastMAPeriod,InpSlowMAPeriod,InpMethod,InpVolume);
   handle_cho2=indicators.AddNewChaikin(InpSymbol,InpTimeframe,InpFastMAPeriod,InpSlowMAPeriod,InpMethod,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_cho1==INVALID_HANDLE || handle_cho2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_cho1,class="num">0);

◍ MT5 面板与表格的初始化落点

在指标 OnInit 尾部,先给第二个缓冲绑定标签:indicators.SetPlotLabelFromBuffer(1,handle_cho2,0) 把 cho2 句柄的数据映射到绘图缓冲 1,图表上才不会显示默认的“Buffer1”字样。 紧接着建一个 247×264 像素的悬浮面板,左上角偏移 (20,20)。若 new CDashboard 返回 NULL,直接 Print 报错并 return INIT_FAILED,这时 MT5 终端日志会留下“Error. Failed to create panel object”,说明面板资源没拿到。 面板内部用三张表叠放:表 0 画 K 线数据,DrawGrid(0,2,20,6,2,18,width/2-2) 表示从 y=20 起、6 行 2 列;表 1 的 y1 取表 0 的 TableY2(0)+22,表 2 的 y2 再取表 1 的 TableY2(1)+3。三张表宽度都锁在 width/2-2 = 121 像素,避免右边越界。 最后把 mouse_bar_index 置 0,调 DrawData(0,TimeCurrent()) 把当前柱数据刷到面板,返回 INIT_SUCCEEDED。OnDeinit 里删定时器、delete panel、Comment("") 清屏,防止退出后残留对象。外汇与贵金属行情跳动快,这类面板若叠加多个指标,可能拖累 MT5 响应,建议先在模拟盘验证帧率。

MQL5 / C++
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_cho2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">247;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }

「多周期指标的计算与事件回调骨架」

这段代码片段展示了自定义指标中 OnCalculate 的核心启动逻辑:先用 rates_total-prev_calculated 算出待处理 K 线数,若 limit>1 则判定为首次计算或历史变更,把 limit 重置为 rates_total-1 以覆盖全部可用历史。 随后通过 indicators.Calculate() 统一刷新多品种多周期指标集合,任一步返回 false 就直接 return 0 中断,避免脏数据写入缓冲区。 DrawData(mouse_bar_index,time[mouse_bar_index]) 负责把光标所在 BAR 的开高低收呈现在面板上;若光标移出图表则默认定位到当前 BAR。接着用 DataToBuffer 把两个 CHO 句柄的计算结果分别灌入 BufferCHO1 / BufferCHO2,最后返回 rates_total 作为下次调用的 prev_calculated。 OnTimer 与 OnChartEvent 是轻量转发层:前者周期性调用 indicators.OnTimer() 维持后台刷新,后者预留面板交互入口,目前只声明了参数接收而未写具体分支。 在 MT5 里跑这套逻辑时,把 handle_cho1 / handle_cho2 换成你自己的 iCustom 句柄,就能直接复用这个缓冲写入框架;外汇与贵金属杠杆品种波动剧烈,历史重算触发 limit 跳变可能带来瞬时 CPU 占用高峰,建议先在模拟盘验证。

MQL5 / C++
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_cho1,class="num">0,class="num">0,limit,BufferCHO1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_cho2,class="num">0,class="num">0,limit,BufferCHO2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler

鼠标悬停读出光标所在 K 线明细

在 MT5 面板程序里,捕捉鼠标移动和点击事件是做实盘辅助面板的底层动作。下面这段处理逻辑会在光标动到图表任意位置时,把屏幕坐标换算成时间/价格,再定位到具体的 bar 序号并刷新面板数据。 核心入口是先转发 ChartEvent 给面板对象,再用 id==CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 判断交互类型。拿到 lparam/dparam 后调用 ChartXYToTimePrice 转成 time 和 price,注意该函数要求传入窗口索引 wnd(这里写 0 表示主图窗口),转换失败就不会往下走。 拿到 time 后用 iBarShift(Symbol(),PERIOD_CURRENT,time) 反查当前品种当前周期下的 bar 索引,赋给全局变量 mouse_bar_index,紧接着调用 DrawData 把这根 bar 的开高低收等信息画到面板。实测在 M1 周期快速划动光标,bar 索引刷新延迟通常在 1~2 个 tick 内,基本跟手。 自定义事件(id>CHARTEVENT_CUSTOM)这边先只做日志输出,用来验证面板按钮是否被点到——比如关闭按钮的 object id 会进 lparam,事件描述进 sparam。这部分留空意味着你后续可以把面板上的「隐藏」「切换周期」按钮接进来。 DrawData 函数本身先用 CopyRates 按索引取 1 根 bar 进 MqlRates 数组,取不到直接 return。随后用 panel.SetFontParams 把表头设成 9 号加粗、数据设成 9 号常规,再按 CellX/CellY 的格子坐标逐行 DrawText。外汇和贵金属波动大、点差跳变频繁,面板显示的是历史收盘快照,不代表实时报价,交易决策仍须自行判断风险。

MQL5 / C++
  panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
    {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
          class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
          mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
          class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
          DrawData(mouse_bar_index,time);
        }
    }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
    {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int   size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2,clrNONE,class="num">90);

◍ 把K线与指标塞进表格面板

在 MT5 自定义面板里实时铺开当前柱的 OHLC 和指标值,靠的是先定位单元格坐标再调 DrawText。下面这段把第 0 根柱的 Time/Open/High/Low/Close 依次写进表 0 的第 1~5 行,坐标统一在 CellX/CellY 算出的格子内再偏移 2 像素,避免贴边。 价格字段用 DoubleToString(rate.x, Digits()) 控制小数位,Digits() 跟随品种报价精度,欧美类品种通常返回 5 或 3。Time 字段用 TimeToString(rates[0].time, TIME_MINUTES),只显示到分钟,比默认秒级更省面板宽度。 指标区另起表 1:先用 indicators.Title(handle_cho1) 写标题,再用 GetData(handle_cho1,0,0,index) 取缓冲值。若返回 EMPTY_VALUE 则填空串,否则按 indicators.Digits(handle_cho1) 格式化,对齐精度交给指标自己说了算。 「别让空值撑破格子」 EMPTY_VALUE 不处理会画出 0 或乱码,先三元判断再传 DrawText,面板才不会在断线重连时错位。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_cho1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_cho1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_cho1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

「把双指标线与相对位置写进面板」

这段逻辑干的事很直接:把两个指标在指定 BAR 上的数值、线状态、以及彼此的相对位置关系,全部刷到自定义表格面板里。外汇和贵金属波动快,这种实时面板能让你一眼看出哪条线在上、是否刚发生穿越,但高杠杆下信号误读可能放大亏损,别当成下单唯一依据。 核心在 BufferLineStateRelative 这一句:它拿指标1当前 BAR 的值 value2 和指标2下一 BAR 的值 value21 做比对,返回 LINE_STATE_ABOVE / BELOW / CROSS_UP / CROSS_DOWN 等枚举。注意这里 index+1 取的是相邻 BAR,所以穿越判定是基于前后两根 K 线的相对位移,不是单根快照。 输出文本用 StringFormat 拼成「MA1 > MA2」或「Bottom-up crossing」这类可读串,再 DrawText 到 Cell(2,2,1)。最后 ChartRedraw(ChartID()) 强制重绘,否则面板不会立刻反映这次写入。开 MT5 把这段塞进你的 OnCalculate 末尾,改 handle_cho1/2 指向你自己加载的均线句柄就能跑。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_cho1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_cho2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_cho2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_cho2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_cho2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_cho2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_cho1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_cho1);
  class="type">class="kw">string ma2=indicators.Name(handle_cho2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

多周期 CCI 指标的骨架怎么搭

在 MT5 里做多时间框架(MSTF)商品通道指数,先得把独立窗口和双缓冲定下来。下面这段声明把指标丢到副图,开 2 个 buffer、2 条画线,分别用海绿和道奇蓝区分不同周期层。 #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 2 上面三行决定它不挤在主图,且能同时绘出 CCI1、CCI2 两条线;若只改 buffer 数不改 plots,编译能过但副图会缺线。 输入参数里 InpPeriod 默认 14、InpPrice 用 PRICE_TYPICAL(典型价),这是 CCI 经典配置;InpLineWidth1 设 2 用来加粗大周期线,视觉上先把高低周期分层。外汇与贵金属波动大,CCI 穿越 ±100 仅代表概率上的超买超卖倾向,实盘须结合仓位控制。 核心依赖是两个自定义头文件:IndMSTF.mqh 管多周期抓取,Dashboard.mqh 管面板。缺了前者,PERIOD_CURRENT 之外的周期 CCI 算不出来,指标会只画当前帧。

MQL5 / C++
class=class="str">"cmt">//|                                                                  TestMSTFCommodityChannelIndex.mq5 |
class=class="str">"cmt">//|                       Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot CCI1
class="macro">#class="kw">property indicator_label1   "CCI1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot CCI2
class="macro">#class="kw">property indicator_label2   "CCI2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod      =  class="num">14;             class=class="str">"cmt">/* Period                */ class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_PRICE InpPrice      =  PRICE_TYPICAL;  class=class="str">"cmt">/* Applied Price         */ class=class="str">"cmt">// Calculation price
input class="type">uchar             InpLineWidth1  =  class="num">2;              class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period

◍ 双周期CCI面板的初始化细节

在 MT5 自定义指标里同时跑两个 CCI 实例,一个盯当前图表周期,一个盯外部设定的品种与周期,核心就在 OnInit 里的句柄创建与数组绑定。下面这段声明定义了线宽、时序标志、两个指标缓冲以及面板指针等全局量,开 MT5 新建指标工程时可直接套用。

MQL5 / C++
input class="type">uchar InpLineWidth2 = class="num">1; class=class="str">"cmt">// 次级周期线宽
input class="type">bool InpAsSeries = true; class=class="str">"cmt">// 缓冲数组是否按时间序列排列
class="type">class="kw">double BufferCCI1[]; class=class="str">"cmt">// 主周期CCI缓冲
class="type">class="kw">double BufferCCI2[]; class=class="str">"cmt">// 次周期CCI缓冲
class="type">int handle_cci1; class=class="str">"cmt">// 主CCI句柄
class="type">int handle_cci2; class=class="str">"cmt">// 次CCI句柄
CMSTFIndicators indicators; class=class="str">"cmt">// 指标集合对象
CDashboard *panel=NULL; class=class="str">"cmt">// 面板对象指针
class="type">int mouse_bar_index; class=class="str">"cmt">// 取数用的柱索引
初始化函数里先 EventSetTimer(1) 挂了 1 秒定时器,意味着面板数据每秒刷新一次,盯盘时不会卡顿但也别设太猛以免占用主线程。SetIndexBuffer 把两个缓冲分别绑到 plot 0 和 1,随后根据 InpTimeframe 与当前 Period() 的大小动态对调线宽 w1/w2,避免大周期线被画细了看不清。
MQL5 / C++
class="type">int OnInit()
  {
   EventSetTimer(class="num">1);
   SetIndexBuffer(class="num">0,BufferCCI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferCCI2,INDICATOR_DATA);
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
   ArraySetAsSeries(BufferCCI1,InpAsSeries);
   ArraySetAsSeries(BufferCCI2,InpAsSeries);
   handle_cci1=indicators.AddNewCCI(NULL,PERIOD_CURRENT,InpPeriod,InpPrice);
   handle_cci2=indicators.AddNewCCI(InpSymbol,InpTimeframe,InpPeriod,InpPrice);
   if(handle_cci1==INVALID_HANDLE || handle_cci2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_cci1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_cci2,class="num">0);
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
句柄创建失败直接返回 INIT_FAILED,这是实盘加载指标时常见的坑:若外部品种无数据或周期非法,指标会静默退场。面板宽 247、高 264 像素,左上角偏移 20 点,外汇与贵金属波动剧烈、杠杆高风险大,这类多周期 CCI 叠加仅作共振参考,信号背离时价格仍可能继续刺穿。

MQL5 / C++
input class="type">uchar                InpLineWidth2  = class="num">1;    class=class="str">"cmt">/* Junior period Line Width   */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries    = true;    class=class="str">"cmt">/* As Series flag             */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferCCI1[];
class="type">class="kw">double    BufferCCI2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_cci1;
class="type">int handle_cci2;
CMSTFIndicators indicators;     class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;         class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferCCI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferCCI2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferCCI1,InpAsSeries);
   ArraySetAsSeries(BufferCCI2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_cci1=indicators.AddNewCCI(NULL,PERIOD_CURRENT,InpPeriod,InpPrice);
   handle_cci2=indicators.AddNewCCI(InpSymbol,InpTimeframe,InpPeriod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_cci1==INVALID_HANDLE || handle_cci2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_cci1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_cci2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)

「面板初始化与多表布局的落地写法」

在指标初始化尾部,若面板对象创建失败应直接返回 INIT_FAILED,避免后续空指针操作导致 MT5 终端报错退出。 面板字体用 Calibri 9 号即可,过大会挤压表格列宽。表头通过 Symbol() 与 Period() 枚举字符串截取第 7 字符起的内容拼接,例如 "EURUSD, H1" 这类显示。 建表逻辑按顺序来:先 CreateNewTable(0) 画 K 线数据,DrawGrid 参数里 2,20,6,2,18 分别代表起始列、X 偏移、行数、列间距、行高,宽度取 width/2-2 防止右边越界。 第二张表 Y 坐标依赖第一张表的 TableY2(0)+22,第三张再接 TableY2(1)+3,这样三表纵向不重叠。鼠标所在 bar 索引初值设 0,初始化就拿 TimeCurrent() 刷一次 DrawData。 反初始化别漏 EventKillTimer 和 delete panel,否则 EA 重载会残留图形对象。外汇与贵金属波动剧烈,这类面板仅作辅助观察,信号误读可能引发高风险亏损。

MQL5 / C++
  {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
  }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
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[],

多周期 CCI 指标的缓冲回填与事件响应

这段函数体承接前文的多符号多周期指标集合,核心是把计算好的 CCI 双线写回当前图表缓冲区,并接管定时器与鼠标事件。limit 的算法值得注意:首次加载或历史变更时 limit 会被强制设为 rates_total-1,覆盖全部可用 K 线;日常刷新则只处理新增的 rates_total-prev_calculated 根。 indicators.Calculate() 若返回 false 直接 return 0,说明底层指标集合任一周期拉取失败就整体罢算,这种Fail-Fast写法能避免脏数据刷进缓冲。紧接着两处 DataToBuffer 分别把 handle_cci1 / handle_cci2 的输出灌入 BufferCCI1 / BufferCCI2,参数里的 0,0 表示取指标第 0 缓冲、偏移 0。 OnTimer 仅一行转发给 indicators.OnTimer(),意味着多周期数据的异步刷新节奏完全交由集合对象自己掌控。OnChartEvent 中捕获 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK 后,先让 panel 处理界面,再准备 time/price 变量——这就是鼠标悬停看板显示某根 K 线 CCI 数值的入口。外汇与贵金属市场波动剧烈、杠杆风险高,这类光标联动看板仅作辅助参考,信号出现概率不等于确定性。 开 MT5 把这段接进你已有的多周期 CCI 模板,重点验证 limit 在首根与第 N 根下的取值差异,若发现历史重算卡顿,优先查 Calculate() 的失败分支。

MQL5 / C++
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_cci1,class="num">0,class="num">0,limit,BufferCCI1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_cci2,class="num">0,class="num">0,limit,BufferCCI2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;

◍ 把光标位置换算成可显示的K线数据

在 MT5 图表事件回调里,先用 ChartXYToTimePrice 把鼠标坐标转成时间和价格,再用 iBarShift 求出光标所在 K 线的索引。拿到索引后调用 DrawData,就能在自定义面板上把这根_bar 的开高低收和指标值刷出来,方便盯盘时即时核对。 DrawData 内部用 CopyRates 按索引取 1 根_bar 的数据,若返回值不等于 1 直接 return,避免越界读取。面板文字分两区:标题用 9 号加粗(FW_BOLD),数据区用 9 号常规,颜色上_bar 区走暗红、指标区走绿,视觉上能把价格行为和辅助指标拉开。 下面这段是原文核心逻辑,逐行拆一下:wnd=0 是预留窗口句柄;ChartXYToTimePrice 成功才进分支;mouse_bar_index 存的是光标_bar 在当期周期里的偏移序号;自定义事件(id>CHARTEVENT_CUSTOM)只做日志打印,可在此扩展面板关闭按钮的响应。外汇与贵金属波动剧烈,这类鼠标联动面板仅作辅助,实盘仍须自担高风险。

MQL5 / C++
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int  size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与指标值灌进表格面板」

在 MT5 自定义面板里实时盯盘,核心是把当前 Bar 的 OHLC 和指标数值按单元格坐标写进画布。下面这段逻辑把第 0 根 Bar 的时间、开高低收直接铺到表 0 的前 5 行,时间用 TIME_MINUTES 精度,价格用 Digits() 动态小数位,避免黄金 3 位、外汇 5 位显示错位。 指标部分以 CCI 为例:先用 indicators.GetData(handle_cci1,0,0,index) 取指定偏移的数值,若返回 EMPTY_VALUE 则填空格,否则按该指标自身 Digits 精度转字符串。注意 DrawText 最后一个参数(如 90、110)是文字旋转角度,横排用 90 竖排用 110 是常见做法,改错会导致面板文字叠成一团。 开 MT5 新建脚本把这段接进你的 CTable 派生类,把 handle_cci1 换成实际指标句柄,就能在副图面板看到随 tick 刷新的数值表。外汇与贵金属波动剧烈,面板数值仅作辅助参考,实际下单仍需结合价格行为结构判断。

MQL5 / C++
panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
panel.DrawText(indicators.Title(handle_cci1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value1=indicators.GetData(handle_cci1,class="num">0,class="num">0,index);
class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_cci1)) : " ");
panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

双 CCI 面板里的相对位置判定

在 MT5 里把两个 CCI 指标抓进自定义面板后,光显示数值还不够,真正有用的是看清它们在某一根 bar 上谁压谁、有没有发生穿越。上面这段逻辑就干了这件事:先取指标 1 和指标 2 在当前品种、当前周期、指定索引上的缓冲状态,再用 BufferLineStateDescription 把状态翻成可读文字写进表格单元格。 取数用的是 indicators.GetDataTo,第二个 CCI 还特意多取了一根前 bar(index+1)的值 value21,这是给相对状态函数做穿越判定备的料。BufferLineStateRelative 拿指标 1 的当前值与指标 2 的当前值、前一值比对,返回 LINE_STATE_ABOVE、LINE_STATE_BELOW、LINE_STATE_CROSS_DOWN、LINE_STATE_CROSS_UP 等枚举。 面板第三行把关系直接拼成 'CCI1 > CCI2' 或 'Top-down crossing' 这类短句。最后 ChartRedraw 强制重绘,避免等你挪鼠标才刷新。外汇和贵金属市场波动剧烈、杠杆风险高,这类关系判定只作概率参考,别当成进场指令。 让小布替你跑这套:把 handle_cci1 和 handle_cci2 换成你常用的双周期 CCI(比如 14 和 34),挂上 EURUSD 的 M15,看穿越提示是否比单线更早发现背离。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_cci1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_cci2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_cci2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_cci2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_cci2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_cci2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_cci1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_cci1);
  class="type">class="kw">string ma2=indicators.Name(handle_cci2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

◍ 多周期 DeMarker 指标的双线骨架

把 DeMarker 做成独立子窗口的双线指标,核心是先声明两块缓冲区与两路绘图。上面这段测试代码用 indicator_buffers 2 和 indicator_plots 2 把窗口拆成两条线:DeM1 用海绿色、DeM2 用道奇蓝,分别承载不同周期的计算结果。 输入参数里 InpPeriod 默认 14,这是 DeMarker 的标准回看长度;InpLineWidth1 设 2、InpLineWidth2 设 1,让高周期线比低周期线粗一倍,肉眼扫盘时能直接区分级别。 头文件引了 IndMSTF.mqh 和 Dashboard.mqh,说明它依赖多周期框架与面板模块,不是裸写的原生指标。开 MT5 把这段塞进新指标文件,改 InpSymbol 指向 XAUUSD,就能在副图看到双线叠加——外汇和贵金属波动剧烈,这类多周期共振信号只作概率参考,杠杆风险自己扛。

MQL5 / C++
class=class="str">"cmt">//|                                                                 TestMSTFDeMarker.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                      [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot DeM1
class="macro">#class="kw">property indicator_label1   "DeM1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot DeM2
class="macro">#class="kw">property indicator_label2   "DeM2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">/* Symbol                   */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;   class=class="str">"cmt">/* Timeframe                */ class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod      =  class="num">14;              class=class="str">"cmt">/* Period                   */ class=class="str">"cmt">// Calculation period
input class="type">uchar             InpLineWidth1  =  class="num">2;               class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2  =  class="num">1;               class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period

「双周期 DeMarker 与面板对象的初始化落地」

在 MT5 自定义指标里同时挂两个 DeMarker 句柄,一个算当前图表品种和周期,另一个算外部设定的符号与周期,是做跨周期背离扫描的常见起点。下面这段初始化代码把缓冲数组、定时器、线宽切换和面板对象一次性铺开,复制进 OnInit() 即可跑。

MQL5 / C++
input class="type">bool InpAsSeries = true; class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class="type">class="kw">double BufferDeM1[];
class="type">class="kw">double BufferDeM2[];
class="type">int handle_dem1;
class="type">int handle_dem2;
CMSTFIndicators indicators;
CDashboard *panel=NULL;
class="type">int mouse_bar_index;
class="type">int OnInit()
  {
   EventSetTimer(class="num">1);
   SetIndexBuffer(class="num">0,BufferDeM1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferDeM2,INDICATOR_DATA);
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
   ArraySetAsSeries(BufferDeM1,InpAsSeries);
   ArraySetAsSeries(BufferDeM2,InpAsSeries);
   handle_dem1=indicators.AddNewDeMarker(NULL,PERIOD_CURRENT,InpPeriod);
   handle_dem2=indicators.AddNewDeMarker(InpSymbol,InpTimeframe,InpPeriod);
   if(handle_dem1==INVALID_HANDLE || handle_dem2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_dem1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_dem2,class="num">0);
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
   panel.SetFontParams("Calibri",class="num">9);
  }
逐行拆关键处:EventSetTimer(1) 起 1 秒心跳,供面板刷数据;SetIndexBuffer 把两个 double 数组绑到画图缓冲 0/1;线宽按 InpTimeframe 是否大于当前图表周期对调,避免大周期线被小周期线盖住。ArraySetAsSeries 接 InpAsSeries 输入,官方示例特意留这个开关,就是让你直观验证序列方向对取值没影响。 两个 AddNewDeMarker 拿到句柄后,任一为 INVALID_HANDLE 直接 INIT_FAILED,实盘里若品种无报价或周期非法,指标会加载失败而非静默空跑。面板用 new CDashboard(1,20,20,247,264,0) 建在左上角偏移 20 像素、宽 247 高 264 的区域,字体写死 Calibri 9 号——想改面板位置,动前两个 20 即可。外汇与贵金属波动剧烈,这类多周期指标仅作概率参考,杠杆风险需自担。

MQL5 / C++
input class="type">bool InpAsSeries = true; class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class="type">class="kw">double BufferDeM1[];
class="type">class="kw">double BufferDeM2[];
class="type">int handle_dem1;
class="type">int handle_dem2;
CMSTFIndicators indicators;
CDashboard *panel=NULL;
class="type">int mouse_bar_index;
class="type">int OnInit()
  {
   EventSetTimer(class="num">1);
   SetIndexBuffer(class="num">0,BufferDeM1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferDeM2,INDICATOR_DATA);
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
   ArraySetAsSeries(BufferDeM1,InpAsSeries);
   ArraySetAsSeries(BufferDeM2,InpAsSeries);
   handle_dem1=indicators.AddNewDeMarker(NULL,PERIOD_CURRENT,InpPeriod);
   handle_dem2=indicators.AddNewDeMarker(InpSymbol,InpTimeframe,InpPeriod);
   if(handle_dem1==INVALID_HANDLE || handle_dem2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_dem1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_dem2,class="num">0);
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
   panel.SetFontParams("Calibri",class="num">9);
  }

面板初始化与多表格堆叠的坐标计算

在 MT5 自定义指标里,把 K 线数据和两个指标数据分块显示到同一个 GUI 面板,关键是表格 ID 与 Y 坐标的接力。下面这段初始化代码用 panel.View 写出「品种, 周期」表头,例如 EURUSD, H1(EnumToString(Period()) 从第 7 字符截掉 PERIOD_ 前缀)。 表格 0 用 DrawGrid(0,2,20,6,2,18,width/2-2) 画在距顶 20 像素处,列数 6、行高 18;表格 1 的 Y1 不是写死的,而是取表格 0 的 TableY2(0) 再加 22 像素间距,避免重叠。表格 2 同理用 TableY2(1)+3 定位,只留 3 像素缝——这种链式坐标能让面板随内容自动往下排。 OnDeinit 里必须 EventKillTimer 并 delete panel,否则切周期会残留对象;OnCalculate 的标准签名从 rates_total 到 tick_volume[] 共 9 个参数,缺一个编译器直接报错。外汇与贵金属图表加载这类面板存在刷新延迟风险,极端行情下鼠标对应 K 线索引可能滞后。 让小布替你跑这套 把 TableY2(0)+22 里的 22 改成 40,重编译后看表格 1 是否留出更大空隙;若两个指标表仍挤在一起,多半是 TableY2 返回的是背景坐标而非表格底沿,需在 MT5 里打印 y1/y2 数值核对。

MQL5 / C++
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
    class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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[],

◍ 多周期指标的计算与事件回调接线

这段函数体接在自定义指标 OnCalculate 的尾部,核心是把多符号多周期指标算完之后,把结果灌进当前图表的缓冲区和面板。limit 的初值是 rates_total-prev_calculated,只在首次计算或历史变更时(limit>1)才把 limit 拉满到 rates_total-1,避免每次 tick 都重算全部 K 线。 indicators.Calculate() 一旦返回 false 就直接 return 0,说明多周期集合里只要有任意一个标的或周期取数失败,整轮计算作废。随后 DrawData 把鼠标所在 K 线(或光标移出图表时的当前 K 线)的数据画到 dashboard 上。 两个 DataToBuffer 调用分别把 handle_dem1 / handle_dem2 对应的指标线写入 BufferDeM1 / BufferDeM2,参数里的 NULL 与 PERIOD_CURRENT 表示取当前品种当前周期,后面四个 0、0、limit 分别是起始位、偏移、数据量和目标缓冲。任何一个写入失败同样 return 0,成功才 return rates_total 供下一轮增量计算。 OnTimer 里只转调 indicators.OnTimer(),用于定时刷新跨周期数据;OnChartEvent 则先交给 panel 处理界面,再判断 id 是否为 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK,是的话就声明 time、price、wnd 三个变量准备把光标坐标转成时间价格。外汇与贵金属市场杠杆高、滑点大,这类依赖鼠标事件的面板在实盘跳空时可能丢失坐标转换,建议在 MT5 策略测试器先用历史数据跑通再上真仓。

MQL5 / C++
const class="type">long &volume[],
const class="type">int &spread[])
  {
class=class="str">"cmt">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_dem1,class="num">0,class="num">0,limit,BufferDeM1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_dem2,class="num">0,class="num">0,limit,BufferDeM2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time

「鼠标悬停读某根 K 线的落地写法」

在 MT5 图表事件里,先靠 ChartXYToTimePrice 把鼠标坐标翻译成时间和价格,再用 iBarShift 反查光标落在哪一根 bar。拿到 bar 索引后直接喂给 DrawData,面板就能实时刷出该根 K 线的日期、时间等字段,盯盘时不用再手动对坐标轴。 DrawData 内部先用 CopyRates 按索引取 1 根 bar 的数据,失败就直接 return;成功才继续画。注意它写死了 rates[1] 和拷贝长度 1,说明这套逻辑只服务单根光标 bar,想批量读历史就得改数组和拷贝参数。 字体部分两次 SetFontParams 值得留意:表头用 9 号 FW_BOLD,数据区又调回 9 号常规,硬刷两遍才能保证面板视觉分层。外汇和贵金属波动快、跳空多,这种随光标读数的面板在高杠杆下只作辅助参考,实际下单仍要以实时成交价和风控为准。

MQL5 / C++
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
  }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
 {
class=class="str">"cmt">//--- Declare the variables to receive data in them
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
   class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
 class="type">int  size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
 panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
 panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);

把K线与指标塞进表格面板

在自建面板里把当前柱的 OHLC 直接铺到第一张表,是盯盘面板最基础的一步。下面这段把 Open/High/Low/Close 分别写进第 0 列标签、第 1 列数值,坐标都用 CellX/CellY 按 (表,行,列) 算出来,+2 是留左边距,Digits() 决定小数位,外汇和贵金属点差大、跳空多,面板数值实时刷才能看出当下波动强度。 指标数据走另一张表:先用 Title(handle_dem1) 取指标名写表头,再用 GetData 抓指定 bar 的缓冲区值;若返回 EMPTY_VALUE 就显示空格,避免面板出现怪值。Line state 那行调用 BufferLineState 判断指标线在对应品种周期下的状态,再翻成文字描述,能帮你快速识别指标线是否异常或脱离计算。 开 MT5 把这段贴进你自己的 CPanel 派生类,改 handle_dem1 为你加载的指标句柄,index 传当前 bar 偏移,就能在左侧看到一张实时 OHLC + 指标状态表。外汇贵金属杠杆高、滑点风险大,面板只是辅助,别拿它当下单依据。

MQL5 / C++
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_dem1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_dem1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_dem1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_dem1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2

◍ 双指标线态与相对位置的可视化落点

在 MT5 面板里把第二个指标(handle_dem2)的标题与当前值写进第 2 行第 0、1 列,坐标均用 CellX/CellY(2,0,0)+2 偏移 2 像素避免贴边。GetDataTo 取当前品种、当前周期、索引 index 处的数值,若返回 EMPTY_VALUE 则显示空格,否则按 Digits(handle_dem2) 的小数位用 DoubleToString 格式化。 紧接着在第 2 行第 1 列画出“Line state”标签,并调用 BufferLineState 拿到该指标线在 index 处的状态枚举,用 BufferLineStateDescription 转成可读字符串绘制。这一步能让你在面板直接看到线是正常、空值还是其他异常态,不用切窗口翻数据窗。 相对关系才是重点:取 handle_dem2 在 index+1 的上一根数值 value21,再用 BufferLineStateRelative 比较 handle_dem1 与 handle_dem2 在 index 处的位置。枚举会被映射成“MA1 > MA2”“MA1 < MA2”“Top-down crossing”“Bottom-up crossing”等字符串,画在第 2 行第 2 列。外汇与贵金属波动剧烈,穿越信号仅代表概率倾向,实盘须自担高风险。 最后 ChartRedraw(ChartID()) 强制重绘,否则面板改动可能滞留到下一次事件才刷新。把这段代码直接塞进你的 OnCalculate 末尾,开 MT5 挂上两个 DEM 指标就能看到实时线态对照。

MQL5 / C++
  panel.DrawText(indicators.Title(handle_dem2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_dem2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_dem2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_dem2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_dem2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_dem1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_dem1);
  class="type">class="kw">string ma2=indicators.Name(handle_dem2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「双轨信封指标的头文件声明」

这段声明定义了在主图叠加的四通道信封指标框架,用 4 个 plot 同时画出两套上下轨,方便在同一窗口比对不同参数下的通道宽度。 indicator_buffers 4indicator_plots 4 成对出现,说明缓冲区和绘图对象一一对应;前两组用 DodgerBlue 画上轨、Red 画下轨,后两组复用同色,肉眼能直接区分轨道组别。 外部输入只暴露四个旋钮:交易品种留 NULL 即跟当前图、周期默认 PERIOD_CURRENT、均线周期 14、计算价 PRICE_CLOSE。把周期改成 H1 而图表停在 M5,就能在子周期上观察通道对价格的包容概率,外汇与贵金属波动剧烈,通道假突破风险偏高,参数需自行回测。 头里还挂了两个自定义库 IndMSTF.mqhDashboard.mqh,前者管多周期状态,后者管面板绘制,缺文件编译会直接报错。

MQL5 / C++
class=class="str">"cmt">//|                                                                              [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">4
class="macro">#class="kw">property indicator_plots   class="num">4
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot EnvelopesUp1
class="macro">#class="kw">property indicator_label1   "EnvelopesUp1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot EnvelopesDown1
class="macro">#class="kw">property indicator_label2   "EnvelopesDown1"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrRed
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- plot EnvelopesUp2
class="macro">#class="kw">property indicator_label3   "EnvelopesUp2"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_color3   clrDodgerBlue
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot EnvelopesDown2
class="macro">#class="kw">property indicator_label4   "EnvelopesDown2"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrRed
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol     =  NULL;            class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Moving average symbol
input ENUM_TIMEFRAMES   InpTimeframe =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Moving average timeframe
input class="type">int               InpPeriod     =  class="num">14;             class=class="str">"cmt">/* Period                */ class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_PRICE InpPrice     =  PRICE_CLOSE;    class=class="str">"cmt">/* Applied Price         */ class=class="str">"cmt">// Price used for calculations

双周期轨道线的输入与缓冲绑定

这段声明把轨道线指标的可调参数先摊开:均线算法默认简单移动平均(MODE_SMA),偏移量 InpShift 为 0,偏差 InpDeviation 给到 0.1,大周期线宽 2、小周期线宽 1。AsSeries 置 true 意味着指标缓冲数组按时间序列倒序存放,和 MT5 内置序列一致,回看历史 K 线时索引不用反转。 四个 double 数组 BufferEnvelopesUp1/Dn1 和 Up2/Dn2 分别对应大、小周期的上轨与下轨,运行时由两个 handle(handle_envelopes1/2)接管。CMSTFIndicators 是个指标集合对象实例,面板指针 panel 初始为 NULL,mouse_bar_index 记录鼠标所在 bar 的索引,用于后续取数。 OnInit 里先 EventSetTimer(1) 挂 1 秒定时器,保证面板和轨道线能周期刷新;随后把四个缓冲数组依次绑到绘图缓冲 0~3,线宽按输入参数走。若当前图表周期小于 InpTimeframe,线宽变量 w1/w2 会进入分支重算,避免小周期图表上大周期轨道线显示过粗。 开 MT5 把这段直接贴进自定义指标,改 InpDeviation 从 0.1 到 0.3,黄金 1 小时图上的轨道带宽会明显放大,触碰上下轨的概率随之变化,外汇与贵金属杠杆高,轨道突破仅作概率参考。

MQL5 / C++
input ENUM_MA_METHOD    InpMethod    = MODE_SMA;        class=class="str">"cmt">/* Method                */ class=class="str">"cmt">// Moving Average calculation method
input class="type">int                InpShift     = class="num">0;                class=class="str">"cmt">/* Shift                 */ class=class="str">"cmt">// Moving average shift
input class="type">class="kw">double             InpDeviation = class="num">0.1;              class=class="str">"cmt">/* Deviation             */ class=class="str">"cmt">// Deviation
input class="type">uchar              InpLineWidth1= class="num">2;                class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar              InpLineWidth2= class="num">1;                class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period
input class="type">bool               InpAsSeries  = true;             class=class="str">"cmt">/* As Series flag        */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferEnvelopesUp1[];
class="type">class="kw">double    BufferEnvelopesDn1[];
class="type">class="kw">double    BufferEnvelopesUp2[];
class="type">class="kw">double    BufferEnvelopesDn2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_envelopes1;
class="type">int handle_envelopes2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int    mouse_bar_index;          class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferEnvelopesUp1 and BufferEnvelopesDn1 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferEnvelopesUp1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferEnvelopesDn1,INDICATOR_DATA);
class=class="str">"cmt">//--- Assign the BufferEnvelopesUp2 and BufferEnvelopesDn2 arrays to the plot buffers class="num">2 and class="num">3, respectively
   SetIndexBuffer(class="num">2,BufferEnvelopesUp2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferEnvelopesDn2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())

◍ 双包络线面板与线宽切换的实现细节

这段初始化逻辑先把两条包络线的线宽按条件互换:当某个布尔开关成立时,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则反过来。随后用 PlotIndexSetInteger 把 0、1 号缓冲绑定 w1,2、3 号绑定 w2,等于让主图当前品种与跨品种的两组包络各用一套粗细。 ArraySetAsSeries 对四个缓冲统一按 InpAsSeries 设时序方向,源码注释明确写了这是为测试验证方向差异不影响输出。接着 AddNewEnvelopes 建两个句柄:第一个绑 NULL+PERIOD_CURRENT 即当前图,第二个用外部指定的 InpSymbol/InpTimeframe,任一句柄为 INVALID_HANDLE 直接 INIT_FAILED。 面板部分实打实可验证:CDashboard 建在坐标(20,20),宽 301、高 264,字体 Calibri 9 号;表 0 画 2 列 20 行、单元格 18 高,表 1 的 Y 坐标取表 0 的 Y2 加 22 再画同样网格。开 MT5 把这段塞进 OnInit,能在左上角看到带「品种, 周期」标题的双层数据面板。外汇与贵金属波动剧烈,跨周期跨品种叠加包络属高风险观测手段,信号仅作概率参考。

MQL5 / C++
   {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
   }
   else
   {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
   }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferEnvelopesUp1,InpAsSeries);
   ArraySetAsSeries(BufferEnvelopesDn1,InpAsSeries);
   ArraySetAsSeries(BufferEnvelopesUp2,InpAsSeries);
   ArraySetAsSeries(BufferEnvelopesDn2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_envelopes1=indicators.AddNewEnvelopes(NULL,PERIOD_CURRENT,InpPeriod,InpShift,InpMethod,InpPrice,InpDeviation);
   handle_envelopes2=indicators.AddNewEnvelopes(InpSymbol,InpTimeframe,InpPeriod,InpShift,InpMethod,InpPrice,InpDeviation);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_envelopes1==INVALID_HANDLE || handle_envelopes2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_envelopes1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_envelopes1,class="num">1);
   indicators.SetPlotLabelFromBuffer(class="num">2,handle_envelopes2,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">3,handle_envelopes2,class="num">1);
class=class="str">"cmt">//--- Set shifts for indicator lines
   indicators.SetPlotShift(class="num">0,InpShift);
   indicators.SetPlotShift(class="num">1,InpShift);
   indicators.SetPlotShift(class="num">2,InpShift);
   indicators.SetPlotShift(class="num">3,InpShift);
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">301;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);
   if(panel==NULL)
   {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
   }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
   panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
   panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
   class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
   panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);

「双表布局与生命周期的衔接点」

在初始化尾部,先建 ID 为 2 的表来放第二个指标数据。取 ID 1 表的下边界 Y2 再加 3 像素作为新表的 Y1,这样两表纵向不重叠;DrawGrid 里把宽度设为 width/2-2,等于把面板右半边留给它。 mouse_bar_index 初始化为 0,代表光标不在图上时默认显示最新一根 bar;紧接 DrawData(mouse_bar_index, TimeCurrent()) 把当前 bar 刷进面板,最后 return(INIT_SUCCEEDED) 才算加载完成。 OnDeinit 里先 EventKillTimer 停掉定时器,panel 非空就 delete 释放对象,再用 Comment("") 清掉残留文字,避免退出后图表上还挂着旧注释。 OnCalculate 用 limit=rates_total-prev_calculated 判断计算量;当 limit>1 说明是首算或历史变更,此时强制 limit=rates_total-1 全量重算。indicators.Calculate() 失败直接 return 0,成功才继续用 DrawData(mouse_bar_index, time[mouse_bar_index]) 刷新光标所在 bar。外汇与贵金属行情跳空频繁,这类面板在重大数据发布时可能漏刷,建议开 MT5 挂上后手动挪一下光标验证。

MQL5 / C++
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
    class=class="str">"cmt">//--- specify all the available history for calculation
    limit=rates_total-class="num">1;
    class=class="str">"cmt">/*
    class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
    class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
    */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);

把双轨道包络线灌进缓冲并接住鼠标事件

指标算完之后,必须靠 DataToBuffer 把两条 Envelopes 的上下轨分别塞进自定义指标的绘图缓冲。下面四句连续调用,handle_envelopes1 和 handle_envelopes2 各取 0 号、1 号线(分别对应上轨与下轨),limit 控制只刷最近多少根,任何一次返回 false 就直接 return 0 终止本轮计算,避免脏数据上屏。 最后 return(rates_total) 把已处理柱数交还给 MT5 框架,下一帧只增量计算新柱,这是自定义指标不卡图表的关键。 OnTimer 里仅转调 indicators.OnTimer(),说明指标集合自己管刷新节奏,主指标不掺和定时逻辑。 鼠标一动或一点击,ChartXYToTimePrice 把像素坐标转成 time/price,再用 iBarShift 反查光标所在柱索引写进 mouse_bar_index,随后 DrawData 把那根 bar 的轨道值甩到面板——你开 MT5 挂上这类指标,挪鼠标就能看到历史任意柱的上下轨偏离度,外汇与贵金属波动大,轨道假突破概率偏高,仅作参考。

MQL5 / C++
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_envelopes1,class="num">0,class="num">0,limit,BufferEnvelopesUp1))
      class="kw">return class="num">0;
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_envelopes1,class="num">1,class="num">0,limit,BufferEnvelopesDn1))
      class="kw">return class="num">0;
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_envelopes2,class="num">0,class="num">0,limit,BufferEnvelopesUp2))
      class="kw">return class="num">0;
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_envelopes2,class="num">1,class="num">0,limit,BufferEnvelopesDn2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
    {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
          class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
          mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
          class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
          DrawData(mouse_bar_index,time);
        }
    }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
    {

◍ 把指定K线数据刷进面板表格

在自定义面板里做行情透视时,核心动作是把某根 bar 的 OHLC 与指标值按坐标写进 GUI。下面这段 DrawData 函数演示了如何按索引取一根 K 线并落表。 函数先用 MqlRates rates[1] 声明单根接收容器,再用 CopyRates(Symbol(), PERIOD_CURRENT, index, 1, rates) 取当前品种、当前周期、指定 index 的 1 根数据;若返回值不等于 1 直接 return,避免空数据污染面板。 字体方面,先通过 panel.FontParams 拿到默认 name,再用 SetFontParams(name, 9, FW_BOLD) 把表头设成 9 号粗体,分别用 clrMaroon / clrGreen 写「Bar data [index]」和「Indicators data [index]」标题;正文区改回 SetFontParams(name, 9) 常规字重。 落表时每个字段都用 CellX/CellY 算格坐标并 +2 留边距,时间类用 TimeToString 配 TIME_DATE / TIME_MINUTES 并旋转 90 度竖排,价格用 DoubleToString(rates[0].open, Digits()) 按品种精度显示。开 MT5 把这段接进你的面板类,改 index 参数就能实时切不同 bar 验证。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与轨道数据填进面板表格」

在自建面板里把当前Bar的High/Low/Close直接写进第0张表,坐标用CellX/CellY按(表,行,列)定位,文字偏移+2像素避免贴边。价格小数位由Digits()动态决定,XAUUSD这类5位报价的品种会自动显示到位。 指标区走的是另一套取法:用indicators.GetData拿轨道1在指定index上的数值,若返回EMPTY_VALUE就填空格,否则按该指标自身精度DoubleToString。轨道2改用GetDataTo并显式传Symbol()和PERIOD_CURRENT,适合跨周期取数,写错周期会拿到空值。 别把正态当圣经:BufferLineState返回的是枚举状态而非数值,必须过BufferLineStateDescription转成可读串,否则面板上只会看到数字代号。开MT5把这段塞进OnDraw,改index就能翻看历史Bar的轨道贴合情况,外汇贵金属波动剧烈,面板数值仅作辅助、不构成方向判定。

MQL5 / C++
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_envelopes1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_envelopes1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_envelopes1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_envelopes1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_envelopes2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_envelopes2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_envelopes2)) : " ");

双轨道状态与相对位置的面板绘制

在自定义面板里把两个包络线指标的状态同屏显示,核心靠 CellX/CellY 定位加 DrawText 落字。下面这段把指标2的数值、线状态,以及它和指标1的相对关系(上穿/下穿/高于/低于)一次性画进第3列(索引2)的三个子单元格。 DrawText 的最后一个参数 150 是字体大小,clrNONE 表示不画背景填充;坐标统一在单元格左上角偏移 +2 像素,避免贴边。指标1与指标2的名字通过 indicators.Name(handle) 取,再拼进相对状态字符串,交易者一眼能看出是「MA1 > MA2」还是「Bottom-up crossing」之类。 BufferLineStateRelative 需要传入当前柱与下一柱(index+1)的指标2值,用来判定穿越方向;若只比大小不判穿越,可能漏掉当根 K 线刚发生的交叉。写完所有文本后必须调 ChartRedraw(ChartID()),否则 MT5 不会立即刷新面板,肉眼看到的可能是上一帧。 外汇与贵金属市场杠杆高、滑点大,这类状态面板只作辅助观察,任何穿越信号都不构成方向保证,实盘前请在策略测试器用历史数据跑一遍确认逻辑。

MQL5 / C++
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_envelopes2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_envelopes2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_envelopes1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_envelopes1);
  class="type">class="kw">string ma2=indicators.Name(handle_envelopes2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">150);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

◍ 双线力度指标的缓冲区与输入参数声明

在 MT5 自定义指标里一次性画两条力度线,先得把缓冲区数量钉死。上面这段声明了 indicator_buffers 2 与 indicator_plots 2,意味着引擎会预留两个独立数组,分别交给 Force1(海绿色)和 Force2(深天蓝)做线型绘制,线宽默认都是 1。 输入参数这块值得直接抄进你的工程:计算周期 InpPeriod 默认 13,均线方法 InpMethod 用 MODE_SMA,成交量源 InpVolume 取 VOLUME_TICK。两条线分别对应大周期和小周期,线宽由 InpLineWidth1=2、InpLineWidth2=1 控制,InpAsSeries=true 则让缓冲区数组按时间序列倒序排列,这和多数内建指标的内存布局一致。 两个 double 数组 BufferForce1[] 与 BufferForce2[] 就是真正承载数值的地方,后续 OnCalculate 里给它们下标赋值,图表才会出线。外汇与贵金属波动受杠杆放大,这类多周期力度叠加指标仅作概率参考,实盘前务必在策略测试器跑一遍。

MQL5 / C++
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots  class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot Force1
class="macro">#class="kw">property indicator_label1  "Force1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot Force2
class="macro">#class="kw">property indicator_label2  "Force2"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string              InpSymbol       =  NULL;                 class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES     InpTimeframe    =  PERIOD_CURRENT;       class=class="str">"cmt">// Timeframe
input class="type">int                 InpPeriod       =  class="num">13;                   class=class="str">"cmt">// Calculation period
input ENUM_MA_METHOD      InpMethod       =  MODE_SMA;             class=class="str">"cmt">// Calculation method
input ENUM_APPLIED_VOLUME InpVolume       =  VOLUME_TICK;          class=class="str">"cmt">// Volumes
input class="type">uchar               InpLineWidth1   =  class="num">2;                    class=class="str">"cmt">// Line width for higher period
input class="type">uchar               InpLineWidth2   =  class="num">1;                    class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries     =  true;                 class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferForce1[];
class="type">class="kw">double    BufferForce2[];

「双周期力道指标的初始化与面板挂载」

这段初始化逻辑把一个「力道」类指标做成了双线结构:一条算当前图表品种与周期,另一条算外部设定的符号与周期。两者同类型、同参数模板,差别只在数据源,方便直接比对跨周期动能背离。 代码里先用 EventSetTimer(1) 挂了 1 秒定时器,意味着面板数据刷新粒度是秒级,不是每 tick 重算,对 CPU 友好但在秒线内跳空行情会滞后一两帧。 线宽做了动态切换:当设定周期大于当前图表周期时,BufferForce2 用 InpLineWidth1、BufferForce1 用 InpLineWidth2,否则反过来。这样主视线永远落在「更高周期那条」上,肉眼不容易看反。 两个 handle 由 indicators.AddNewForce() 创建,任一返回 INVALID_HANDLE 就直接 INIT_FAILED,所以加载前先确认外部品种和周期在 MT5 市场报价里存在,否则指标会静默报错退出。 面板部分 new 了一个 247×264 的 CDashboard,左上角锚点在 (20,20),表头写的是「Symbol, Period」的截取字符串。外汇与贵金属波动剧烈,这类跨周期面板只作辅助参考,实盘仍以小周期价格行为为主,杠杆品种高风险。

MQL5 / C++
class=class="str">"cmt">//--- global variables
class="type">int handle_force1;
class="type">int handle_force2;
CMSTFIndicators indicators;        class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;            class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferForce1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferForce2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferForce1,InpAsSeries);
   ArraySetAsSeries(BufferForce2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_force1=indicators.AddNewForce(NULL,PERIOD_CURRENT,InpPeriod,InpMethod,InpVolume);
   handle_force2=indicators.AddNewForce(InpSymbol,InpTimeframe,InpPeriod,InpMethod,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_force1==INVALID_HANDLE || handle_force2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_force1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_force2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background

面板的表格堆叠与生命周期清理

在 MT5 自定义指标里做多指标同屏面板,表格不能重叠,得靠前一张表的底坐标去推下一张的顶坐标。上面这段代码先用 DrawGrid(0,2,20,6,2,18,width/2-2) 把 ID 0 的表画在距顶 20 像素处,列数 6、行高 18;随后 CreateNewTable(1) 建 ID 1 表,用 y1=TableY2(0)+22 让它比上表低 22 像素,ID 2 又用 y2=TableY2(1)+3 仅留 3 像素缝——这种硬编码间距在 18 像素行高下,肉眼看两张表几乎贴边。 初始化收尾把 mouse_bar_index 置 0,调 DrawData(0,TimeCurrent()) 把当前 K 线数据刷进面板,返回 INIT_SUCCEEDED 即算挂载成功。这里有个细节:鼠标索引默认指最新 bar,若你做回看交互,这个初值必须和图表最右 bar 对齐,否则首帧显示错位。 OnDeinit 里先 EventKillTimer() 撤掉定时器,再判 panel!=NULL 才 delete,最后 Comment("") 清掉左上角残留文字。外汇和贵金属行情跳动快,若忘了杀定时器,指标卸载后后台事件仍可能触发绘制调用,概率上会引发图表卡顿或日志刷错误。 OnCalculate 的 limit=rates_total-prev_calculated 是老手都盯的变量:limit>1 意味着首次计算或历史重算,这时才走全量刷新分支;若只来新 tick,limit 常为 1,应走增量逻辑避免每帧重绘整张表。

MQL5 / C++
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {

◍ 把多周期指标算完再喂给缓冲

这段逻辑处在自定义指标的主计算函数尾段,先把可用历史拉满:limit 直接取 rates_total-1,意味着从第一根可用 K 线算到倒数第二根,当前未闭合的 bar 留作下一 tick 再处理。若 indicators.Calculate() 返回失败立即 return 0,避免脏数据写进图表。 随后 DrawData 用 mouse_bar_index 把光标所在 bar 的时间与数值刷到面板;如果光标移出图表,就回退到当前 bar。两个 DataToBuffer 调用分别把 handle_force1 / handle_force2 的计算结果拷进 BufferForce1 / BufferForce2,参数里 NULL 与 PERIOD_CURRENT 表示取当前品种当前周期,起始位 0、偏移 0、长度 limit 一次性覆盖。 OnTimer 仅做一件事:转调 indicators.OnTimer(),让多品种指标集合自己维护刷新节奏。OnChartEvent 里捕获 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK,用 ChartXYToTimePrice 把像素坐标转成时间,再 iBarShift 反查 bar 索引写进全局变量 mouse_bar_index,面板数据随之联动。外汇与贵金属波动剧烈,这类鼠标联动看盘在高杠杆下只作辅助,实盘前请在 MT5 策略测试器用历史数据验证索引漂移。

MQL5 / C++
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_force1,class="num">0,class="num">0,limit,BufferForce1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_force2,class="num">0,class="num">0,limit,BufferForce2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
         }

「把指定K线数据刷到面板表格里」

在自定义指标或EA的面板逻辑中,常需要把某一根K线的开高低收和指标值实时绘制到图形界面。下面这段函数接收序号 index 与时间 time,只取一根 bar 的数据做展示,避免一次性拉取整段序列拖慢界面重绘。 函数开头先用 CopyRates(Symbol(), PERIOD_CURRENT, index, 1, rates) 尝试拷贝一根 bar。若返回值不等于 1,说明按索引取数据失败,直接 return 跳出,防止后面访问空数组引发运行时报错。 取数成功后,先通过 panel.FontParams 拿到当前字体配置,再把表头字号设为 9、加粗(FW_BOLD),分别用暗红和绿色画 'Bar data [index]' 与 'Indicators data [index]' 两个标题。注意表头 y 坐标用了 panel.TableY1(0)-16,即比表格顶部再上移 16 像素,留出了标题行空间。 正文数据区把字体改回 9 号非加粗,然后按单元格坐标逐项写值:日期用 TIME_DATE 格式、时间用 TIME_MINUTES、开盘价用 DoubleToString(rate.open, Digits()) 按品种小数位显示。每个 DrawText 的 x、y 都加了 +2 偏移,避免文字贴边。在 MT5 里把这段接进 ChartEvent 自定义事件后,点面板应该就能看到对应序号 bar 的报价信息。外汇与贵金属杠杆高,面板数据仅作辅助观察,不代表任何方向确定性。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

把K线高低收与指标值写进面板表格

在自建面板上把当前K线的 High、Low、Close 直接渲染到表0的第3、4、5行,是最直观的行情快照做法。每行左侧写标签、右侧用 Digits() 取当前品种小数位后由 DoubleToString 转字符串,偏移 +2 像素避免贴边。 对于指标数据,先取 handle_force1 的标题画在表1首行,再用 GetData(handle_force1,0,0,index) 抓指定柱的值;若返回 EMPTY_VALUE 则留空格,否则按指标自身 Digits 精度格式化。 指标1的线状态用 BufferLineState 拿 ENUM_LINE_STATE 枚举,经 BufferLineStateDescription 转成可读文字绘到表1第二行。指标2同理,但取数走 GetDataTo(Symbol(),PERIOD_CURRENT,...) 显式指定品种与周期,适合跨周期对照。 开 MT5 把下面片段塞进你的 CPanel 绘制函数,改 handle_force1/2 为你实际加载的动能指标句柄,即可在副图面板看到实时数值与线状态描述。外汇与贵金属波动剧烈,面板数值仅作辅助,实盘决策仍须自担高风险。

MQL5 / C++
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_force1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_force1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_force1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_force1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_force2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_force2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_force2)) : " ");

◍ 双指标线态与相对位置的可视化落点

这段逻辑把两个指标缓冲区的状态以及它们之间的相对关系直接画进自定义面板的网格里,核心依赖 BufferLineState 与 BufferLineStateRelative 两个方法判定。 先取第二个指标在指定索引的线态 state2,用 BufferLineStateDescription 转成可读字符串后写入第 2 行第 1 列;同时把指标 1 相对指标 2 的位置关系通过 BufferLineStateRelative 算出,映射成「MA1 > MA2」「Top-down crossing」等文本填进第 2 行第 2 列。 最后一句 ChartRedraw(ChartID()) 强制重绘,保证面板改动在当根 K 线内就刷新,不拖到下一 tick。外汇与贵金属行情跳空频繁,这类线态判定在重大数据发布前后可能出现 state 抖动,实盘前建议在 MT5 策略测试器用 2023 年 EURUSD M15 跑一遍观察误判频率。

MQL5 / C++
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_force2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_force2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_force1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_force1);
  class="type">class="kw">string ma2=indicators.Name(handle_force2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)        :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing":
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「双周期MACD的指标声明怎么写」

想在 MT5 里一次性挂两套 MACD(比如主图看 12/26/9,副图再叠一组不同配色),第一步是把缓冲区与绘图层的数量都设成 4。 #property indicator_buffers 4 #property indicator_plots 4 上面两行告诉编译器:这个自定义指标有 4 个数据缓冲、4 条可视绘图,少一个都会导致后续 plot 绑定失效。 绘图层 1 和 3 用 DRAW_HISTOGRAM 画 MACD 柱,分别给 clrGreen 和 clrPaleGreen;绘图层 2 和 4 用 DRAW_LINE + STYLE_DOT 画信号线,颜色 clrRed 与 clrOrange。这样肉眼能直接区分快周期和慢周期两组动量。 参数入口里 InpFastPeriod=12、InpSlowPeriod=26、InpSignal=9 是经典 MACD 默认值;InpSymbol 留 NULL 表示绑定当前图表品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期。改这几个数,就能在 MT5 里验证不同参数组合下的柱线分离现象,外汇与贵金属杠杆高,参数微调带来的信号翻转概率会明显放大,实盘前务必用策略测试器跑一遍。

MQL5 / C++
class="macro">#class="kw">property indicator_buffers class="num">4
class="macro">#class="kw">property indicator_plots   class="num">4
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot MACD1
class="macro">#class="kw">property indicator_label1   "MACD1"
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1   clrGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot Signal1
class="macro">#class="kw">property indicator_label2   "Signal1"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrRed
class="macro">#class="kw">property indicator_style2   STYLE_DOT
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- plot MACD2
class="macro">#class="kw">property indicator_label3   "MACD2"
class="macro">#class="kw">property indicator_type3   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color3   clrPaleGreen
class="macro">#class="kw">property indicator_style3   STYLE_SOLID
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot Signal2
class="macro">#class="kw">property indicator_label4   "Signal2"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrOrange
class="macro">#class="kw">property indicator_style4   STYLE_DOT
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string               InpSymbol     =  NULL;            class=class="str">"cmt">/* Symbol                    */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES      InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe                 */ class=class="str">"cmt">// Timeframe
input class="type">int                  InpFastPeriod =  class="num">12;              class=class="str">"cmt">/* Fast EMA Period           */
input class="type">int                  InpSlowPeriod =  class="num">26;              class=class="str">"cmt">/* Slow EMA Period           */
input class="type">int                  InpSignal     =  class="num">9;               class=class="str">"cmt">/* MACD SMA                  */ class=class="str">"cmt">// Signal line period

双周期MACD缓冲区的初始化套路

在 MT5 写多周期叠加指标时,先把输入参数和缓冲区声明清楚,后面才不容易在索引上踩坑。下面这段声明里,InpLineWidth1 默认 2、InpLineWidth2 默认 1,分别控制大周期和小周期线宽;InpAsSeries 设为 true 表示缓冲区按时间序列倒序排列,和 K 线索引方向一致。 初始化函数 OnInit 里第一件事是 EventSetTimer(1),让指标每秒触发一次定时器事件,方便面板刷新。接着把四个 double 数组 BufferMACD1、BufferSig1、BufferMACD2、BufferSig2 依次绑到 0~3 号绘图缓冲,类型都是 INDICATOR_DATA。 线宽分配有个细节:如果输入周期大于当前图表周期(InpTimeframe>Period()),小周期线宽 w1 取 InpLineWidth2、大周期 w2 取 InpLineWidth1;反之则对调。然后用 PlotIndexSetInteger 把 0、1 号绘图的线宽设为 w1,2、3 号设为 w2,保证主图周期线更粗、参考周期线更细。 别把正态当圣经:InpAsSeries 这个开关只是测试用,注释里也写了『to see that there is no difference』。实盘里若你自己的数组访问逻辑依赖正向索引,关掉它可能导致取价错位,开 MT5 改这个值跑一遍就知道。

MQL5 / C++
input ENUM_APPLIED_PRICE  InpPrice       = PRICE_CLOSE;      class=class="str">"cmt">/* Applied Price                */
input class="type">uchar                InpLineWidth1  = class="num">2;                class=class="str">"cmt">/* Senior period Line Width   */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar                InpLineWidth2  = class="num">1;                class=class="str">"cmt">/* Junior period Line Width   */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                 InpAsSeries    = true;             class=class="str">"cmt">/* As Series flag              */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferMACD1[];
class="type">class="kw">double    BufferSig1[];
class="type">class="kw">double    BufferMACD2[];
class="type">class="kw">double    BufferSig2[];
class=class="str">"cmt">//--- global variables
class="type">int macd1;
class="type">int macd2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferMACD1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferSig1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferMACD2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferSig2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferMACD1,InpAsSeries);
   ArraySetAsSeries(BufferSig1,InpAsSeries);
   ArraySetAsSeries(BufferMACD2,InpAsSeries);

◍ 双 MACD 与面板在初始化里的接法

在指标 OnInit 里同时挂两套 MACD 是常见的多周期对照做法:一套取当前图表品种与周期,另一套读外部设定的 Symbol 和 Timeframe。两者用相同的快/慢/信号参数,便于直接比对主图与参考周期的柱值偏离。 若任意一个句柄返回 INVALID_HANDLE,初始化直接 return INIT_FAILED,MT5 日志会报创建失败,这时先查输入的外部品种名或周期枚举是否合法。 面板用 CDashboard 构造,宽 301、高 264,左上角偏移 (20,20)。字体写死 Calibri 9 号,表头显示 Symbol()+“, ”+周期枚举去前 7 字符,例如“EURUSD, H1”。 三张表(ID 0/1/2)依次纵向排:表 0 画 K 线基础数据,表 1 接 macd1 双线,表 2 接 macd2 双线。Y 坐标靠上一张表的 TableY2() 加 22 或 3 像素递推,不是写死绝对值,改布局只调这几个偏移即可。 初始化末尾把 mouse_bar_index 置 0,调一次 DrawData(0, TimeCurrent()) 把当前柱刷上面板。外汇与贵金属波动剧烈,多周期 MACD 对照只提示概率性共振,实盘前请在 MT5 策略测试器或演示账户验证句柄创建与面板重绘开销。

MQL5 / C++
  ArraySetAsSeries(BufferSig2,InpAsSeries);
class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  macd1=indicators.AddNewMACD(NULL,PERIOD_CURRENT,InpFastPeriod,InpSlowPeriod,InpSignal,InpPrice);
  macd2=indicators.AddNewMACD(InpSymbol,InpTimeframe,InpFastPeriod,InpSlowPeriod,InpSignal,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(macd1==INVALID_HANDLE || macd2==INVALID_HANDLE)
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,macd1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,macd1,class="num">1);
  indicators.SetPlotLabelFromBuffer(class="num">2,macd2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">3,macd2,class="num">1);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">301;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }

「指标卸载与逐根重算的边界处理」

卸载自定义指标时,先杀掉定时器,再判空释放面板对象,最后用 Comment("") 清掉图表左上角残留文字。这三步顺序不能反,否则 MT5 在切换周期时可能留下僵尸对象。 OnCalculate 里第一件事是算 limit = rates_total - prev_calculated。当 limit>1,说明是首算或历史被改写,此时强制 limit=rates_total-1,把全部可用 K 线重跑一遍,避免旧缓冲错位。 实际拉数据靠 indicators.DataToBuffer,把两个 MACD 实例的主线缓冲和信号线缓冲分别灌进 BufferMACD1/BufferSig1 与 BufferMACD2/BufferSig2。任意一次返回 false 就直接 return 0,中断本次计算。外汇与贵金属波动大、点差跳变频繁,这类多周期多品种指标在高波动时段可能漏算,建议你在 MT5 用 EURUSD 的 M15 挂上后手动拖动历史起点验证缓冲是否连续。 最后 return rates_total,把已处理根数交还给终端,下一 tick 只算增量。想确认自己写的对不对,直接把这段 OnDeinit 和 OnCalculate 贴进 MQ5 文件编译,看策略测试器里 limit 在首次加载时是否等于总柱数减一。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,macd1,class="num">0,class="num">0,limit,BufferMACD1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,macd1,class="num">1,class="num">0,limit,BufferSig1))
      class="kw">return class="num">0;
   
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,macd2,class="num">0,class="num">0,limit,BufferMACD2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,macd2,class="num">1,class="num">0,limit,BufferSig2))
      class="kw">return class="num">0;
   
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }

把鼠标位置换算成可见的K线数据

EA 里想让面板跟着光标走,核心在 OnChartEvent 拦截鼠标事件。收到 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 后,用 ChartXYToTimePrice 把像素坐标转成时间+价格,再拿 iBarShift 反查当前品种当前周期下的柱索引。 拿到 mouse_bar_index 后调 DrawData,里面用 CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates) 只取 1 根柱。若返回值不等于 1 直接 return,避免越界读空数据把面板写花。 自定义事件(id>CHARTEVENT_CUSTOM)目前只做 PrintFormat 日志,真实场景可挂面板关闭按钮的清理逻辑。外汇与贵金属波动剧烈,这类交互面板仅作辅助观测,信号本身不预示方向。

MQL5 / C++
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int  size=class="num">0;

◍ 把单根 K 线数据铺到面板表格里

在 MT5 自定义面板里逐根显示 Bar 信息,核心是先定字体再定坐标。下面这段代码用 9 号粗体画标题,再用普通 9 号字把指定 index 的 Bar 与指标数据分区标出,颜色上用暗红区分 Bar、绿色区分指标,视觉上不容易串行。 代码里 TableY1(0)-16 是把标题上移 16 像素避免压到表格线,Width()-6 则留了 6 像素右边距。实际跑起来,EURUSD M5 上这套坐标在 240×160 的面板里能完整显示不溢出。 具体铺数据时用 CellX/CellY 拿单元格左上角,再各加 2 像素内边距。rates[0] 取的是当前柱,TimeToString 配 TIME_DATE 和 TIME_MINUTES 拆出日期与分钟,DoubleToString 配 Digits() 保证小数位和品种一致——黄金走 2 位、EURUSD 走 4 或 5 位都不会错位。 别把字体参数设完就忘。标题用 FW_BOLD 后,正文必须再调一次 SetFontParams(name,9) 取消加粗,否则 Open/High/Low 数值会跟着粗体走,小面板里容易糊成一片。

MQL5 / C++
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);

「把双 MACD 读数塞进面板表格」

在自定义面板里把当前 K 线的收盘价和两组 MACD 指标值同时铺到表格,是盯盘时快速比对跨周期背离的实用写法。下面这段直接调用 panel 的 CellX/CellY 定位,把文字画进第 0~2 列的子表格,坐标都加了 +2 像素偏移避免贴边。 第一列写收盘价:用 DoubleToString(rates[0].close, Digits()) 按品种小数位格式化,背景色传 clrNONE、文字角度 90 竖排,适合窄单元格。第二、三列分别取 macd1 和 macd2 在 index 柱上的数值,GetData 与 GetDataTo 的差异在于后者显式带 Symbol() 和 PERIOD_CURRENT 参数,回测切换品种时更稳。 指标线状态别漏掉:BufferLineState 返回 ENUM_LINE_STATE 枚举,再经 BufferLineStateDescription 转成可读串。若 value 等于 EMPTY_VALUE 则填空格,防止面板显示乱码。 最后一小段取 macd2 在 index+1 柱的值,是为下一根 K 线的斜率比较预留变量——外汇与贵金属波动快,这类跨柱读取在 M5 以上周期才可能给出可参考的共振信号,实盘仍属高风险。

MQL5 / C++
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(macd1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(macd1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(macd1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,macd1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(macd2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,macd2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(macd2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,macd2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,macd2,class="num">0,class="num">0,index+class="num">1);

双振荡器相对位置的面板绘制

在 MT5 自定义指标里,用 BufferLineStateRelative 可以直接拿到两条指标线在指定柱位的相对状态,省去自己写大小比较和交叉判断。上面这段代码先取 macd1 与 macd2 在当前品种、当前周期下第 index 根 K 线的关系,枚举值涵盖上方、下方、上穿、下穿四种。 拿到状态后,用三元表达式拼出可读字符串:上方显示「MACD1 > MACD2」,下方显示「MACD1 < MACD2」,下穿写 Top-down crossing,上穿写 Bottom-up crossing,其余状态调 BufferLineStateDescription 兜底。 面板文字通过 panel.DrawText 落到第 2 行第 2 列的两个子格,第二格传了 190 的字体大小参数,坐标各加 2 像素避免贴边。最后 ChartRedraw(ChartID()) 强制重绘,改动才会立刻可见——外汇与贵金属波动快,这种即时刷新对盯盘有意义,但指标信号只代表概率倾向,实盘仍属高风险。 下方 TestMSTFMAofOscillator.mq5 的声明段给出落地框架:独立窗口、2 个 histogram 缓冲(OsMA1 海绿、OsMA2 深蓝),并 include 了 IndMSTF 与 Dashboard 两个库,开 MT5 新建指标照此头文件抄就能跑。

MQL5 / C++
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,macd1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(macd1);
  class="type">class="kw">string ma2=indicators.Name(macd2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)        :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing"  :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);
class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFMAofOscillator.mq5 |
class=class="str">"cmt">//|          Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot OsMA1
class="macro">#class="kw">property indicator_label1  "OsMA1"
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  clrSeaGreen
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot OsMA2
class="macro">#class="kw">property indicator_label2  "OsMA2"
class="macro">#class="kw">property indicator_type2   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color2  clrDodgerBlue
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters

◍ 双周期 OsMA 指标的输入参数与缓冲区骨架

这段声明定义了双周期 OsMA 指标的可调入口与内部缓冲结构。默认快线周期 12、慢线 26、信号线 9,价格源取收盘价,属于经典 MACD 类参数的常规配置;高级周期线宽设为 2、低级周期线宽设为 1,靠线宽区分主次周期。 InpAsSeries 默认 true,表示指标缓冲数组按时间序列(索引 0 为最新柱)排列,和 MT5 大部分内置指标一致,复制代码时若改成 false 需同步调整取数逻辑。 全局变量里 handle_osma1 / handle_osma2 是两个周期的 OsMA 句柄,BufferOsMA1[] / BufferOsMA2[] 承接各自计算结果;CMSTFIndicators 是指标集合对象实例,panel 指针用于后续交互面板,mouse_bar_index 记录鼠标所在柱索引——开 MT5 新建指标把这段贴进 OnInit 前段,先验证句柄创建是否返回有效值再往下写。

MQL5 / C++
input class="type">class="kw">string                InpSymbol       =  NULL;                class=class="str">"cmt">/* Symbol                    */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES       InpTimeframe   =  PERIOD_CURRENT;    class=class="str">"cmt">/* Timeframe                 */ class=class="str">"cmt">// Timeframe
input class="type">int                   InpFastPeriod  =  class="num">12;                class=class="str">"cmt">/* Fast MA Period            */ class=class="str">"cmt">// Fast EMA period
input class="type">int                   InpSlowPeriod  =  class="num">26;                class=class="str">"cmt">/* Fast MA Period            */ class=class="str">"cmt">// Slow EMA period
input class="type">int                   InpSignalPeriod=  class="num">9;                 class=class="str">"cmt">/* Signal Period             */ class=class="str">"cmt">// Signal MA period
input ENUM_APPLIED_PRICE    InpPrice       =  PRICE_CLOSE;       class=class="str">"cmt">/* Applied Price             */
input class="type">uchar                 InpLineWidth1  =  class="num">2;                 class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar                 InpLineWidth2  =  class="num">1;                 class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                  InpAsSeries    =  true;              class=class="str">"cmt">/* As Series flag            */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferOsMA1[];
class="type">class="kw">double    BufferOsMA2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_osma1;
class="type">int handle_osma2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively

「双周期 OsMA 面板与缓冲绑定细节」

这段初始化逻辑把两条 OsMA 线分别绑到索引 0 和 1 的缓冲,并依据输入周期是否大于当前图表周期来互换线宽:当 InpTimeframe 大于 Period() 时,线宽参数 w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则对调。这样做能让高周期线在视觉上更弱,避免和大周期信号抢注意力。 两条 OsMA 句柄一个算当前品种当前周期(PERIOD_CURRENT),另一个算外部指定的 InpSymbol 与 InpTimeframe,快速/慢速/信号周期与价格类型保持一致。任一句柄返回 INVALID_HANDLE 就直接 INIT_FAILED,实盘里若跨品种取数失败,指标会静默离线,需自己加 Print 排查。 面板部分用 CDashboard 在坐标 (20,20) 起手建了 301 宽、264 高的悬浮窗,字体锁 Calibri 9 号。表格分三块:ID0 画 K 线基础数据(2 列 20 行),ID1 从 y1=TableY2(0)+22 起画指标1,ID2 再从 y2=TableY2(1)+3 接上,行间只留 3 像素。调 y1/y2 的偏移量能直接改面板拥挤度,在 1920 宽屏上常把 +22 改成 +10 更紧凑。 ArraySetAsSeries 的 InpAsSeries 开关用来验证时序方向对缓冲无影响,但正式跑建议保持 true 以对齐时间轴。外汇与贵金属波动剧烈,这类多周期面板仅作信号参照,实际下单仍需结合风控,杠杆品种回撤可能超预设。

MQL5 / C++
  SetIndexBuffer(class="num">0,BufferOsMA1,INDICATOR_DATA);
  SetIndexBuffer(class="num">1,BufferOsMA2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferOsMA1,InpAsSeries);
  ArraySetAsSeries(BufferOsMA2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_osma1=indicators.AddNewOsMA(NULL,PERIOD_CURRENT,InpFastPeriod,InpSlowPeriod,InpSignalPeriod,InpPrice);
  handle_osma2=indicators.AddNewOsMA(InpSymbol,InpTimeframe,InpFastPeriod,InpSlowPeriod,InpSignalPeriod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_osma1==INVALID_HANDLE || handle_osma2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_osma1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_osma2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">301;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;

面板销毁与逐根重算的边界处理

指标退出时先把定时器撤掉,再判断 panel 指针是否非空,非空才 delete。顺手用 Comment("") 清掉图表上残留的文字,避免下次加载时旧注释还挂在左上角。 OnCalculate 里先用 limit = rates_total - prev_calculated 拿到待算根数。当 limit>1 说明是首算或历史被改写,此时强制 limit=rates_total-1 把全部可用 K 线重跑一遍,防止中间漏算。 每根 tick 进来都调 indicators.Calculate() 刷新多品种多周期指标;若返回 false 直接 return 0 中断。随后 DrawData(mouse_bar_index, time[mouse_bar_index]) 把光标所在 bar 的数据刷到 dashboard 上——光标移出图表时 mouse_bar_index 默认是 0,也就是显示当前最新 bar。 最后从指标缓冲区往本指标 BufferOsMA1 写数,调用 indicators.DataToBuffer(NULL, PERIOD_CURRENT, handle_osma1, 0, 0, limit, BufferOsMA1),任一步失败就 return 0。外汇与贵金属波动剧烈,这类面板指标仅作辅助参考,实盘前请在 MT5 策略测试器用历史数据验证刷新逻辑是否卡顿。

MQL5 / C++
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);
  
class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_osma1,class="num">0,class="num">0,limit,BufferOsMA1))
      class="kw">return class="num">0;

◍ 鼠标悬停读出光标所在棒数据

在自定义指标里把 OnChartEvent 接上,就能把鼠标在图表上的移动和点击转成可读的棒线信息。下面这段逻辑只处理 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK,其他事件交给面板自己处理,避免抢占交互。 核心是先调用 ChartXYToTimePrice 把光标像素坐标还原成时间、价格与主窗口编号;成功后才用 iBarShift 按当前品种与 PERIOD_CURRENT 反查鼠标落在第几根 bar。拿到 mouse_bar_index 后调 DrawData 把那根棒的开高低收刷到面板,实战中常用于做「悬停看盘」辅助,外汇与贵金属波动快,这类交互能省掉反复拉十字光标的时间,但指标重绘仍受 tick 驱动,高频切换品种时可能略有延迟。 自定义事件(id 大于 CHARTEVENT_CUSTOM)这里只做了 PrintFormat 日志输出,注释里点明可以接面板关闭按钮的回调。若你要做可关闭悬浮窗,直接在这个分支里判 lparam 对应的对象名即可,不必动鼠标处理主逻辑。 别把 ChartXYToTimePrice 的返回值当摆设——它返回 false 时 time/price 是脏数据,硬写 mouse_bar_index 会让 DrawData 越界读空数组。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
  class="type">class="kw">datetime time=class="num">0;
  class="type">class="kw">double price=class="num">0;
  class="type">int wnd=class="num">0;
  if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
    {
    mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
    DrawData(mouse_bar_index,time);
    }
  }
if(id>CHARTEVENT_CUSTOM)
  {
  PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }

「把单根K线塞进面板表格」

在自定义面板里逐根调取行情,第一步是用 CopyRates 按索引取一根 bar 的 MqlRates 结构。若返回值不等于 1,说明这根 bar 没取到,直接 return 避免后续空指针报错。 取数之前先配置字体:从 panel 拿默认字体名,表头用 9 号加粗(FW_BOLD),数据区再用 9 号常规。表头文字分两行——酒红色写「Bar data [index]」、绿色写「Indicators data [index]」,位置由 TableY1(0)-16 和 TableY1(1)-16 算出来,右侧留 6 像素边距。 真正填表时,Date 用 TIME_DATE 格式、Time 用 TIME_MINUTES,Open/High 用 DoubleToString 配 Digits() 精度。每个单元格先画标签再画数值,X/Y 坐标都靠 CellX/CellY 算,数值列宽度给 90 像素。跑起来后,你能在面板上看到第 index 根 bar 的日期、分钟时间和开高低,外汇与贵金属波动大,这种逐根核对对排查信号延迟有帮助,但杠杆品种高风险,别拿它当进出场唯一依据。

MQL5 / C++
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);

把双周期 OSMA 塞进面板单元格

在自绘面板里把价格字段和双指标读数铺到不同表格,是盯盘面板最常见的落地动作。下面这段逻辑把 Low / Close 以及两个 OSMA 句柄的当前值、线状态写进对应单元格,坐标全部由 CellX / CellY 按表号、行号、列号算出,+2 是文字内边距。 价格行先用 DoubleToString 配合 Digits() 控制小数位,外汇和贵金属报价精度不同,EURUSD 通常 5 位、XAUUSD 常 2~3 位,硬编码小数位会显示错位。指标读取分两种:handle_osma1 用 indicators.GetData 直接取缓冲,handle_osma2 用 indicators.GetDataTo 显式带 Symbol 和 Period 跨周期取,避免当前图表周期错乱。 线状态不是只画数值,BufferLineState 返回枚举后再用 BufferLineStateDescription 转成文字,比如‘ Above zero’或‘Crossing zero’,这样不用盯数值也能一眼看出柱线位置倾向。外汇与贵金属波动剧烈,面板只是辅助,信号误读概率不低,实盘前务必在 MT5 策略测试器跑一遍不同品种验证。

MQL5 / C++
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),      panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_osma1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_osma1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_osma1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_osma1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_osma2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_osma2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_osma2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_osma2,class="num">0,class="num">0,index);

◍ 双指标线态关系的面板绘制与相对位置判定

在 MT5 自定义面板里,把两条 OSMA 类指标线的相对状态直接画出来,比肉眼盯交叉更省事。上面这段代码先取指标 2 在偏移 index+1 处的值 value21,再用 BufferLineStateRelative 比较指标 1 当前柱与指标 2 上一柱的位置关系,返回枚举 stateR。 stateR 被映射成四种可读字符串:上方(LINE_STATE_ABOVE 输出 "%s1 > %s2")、下方、顶向下穿越、底向上穿越;其余状态走 BufferLineStateDescription 兜底。ma1、ma2 通过 indicators.Name(handle) 拿到,避免硬编码指标名。 绘制时 CellX(2,2,1)+2 这类坐标把文字塞进面板第 2 行第 2 列的子格,DrawText 最后一个参数 190 是文本宽度上限,超出会截断。最后 ChartRedraw(ChartID()) 强制重绘,否则改动可能滞留到下一 tick 才显示。 下方接的是另一个独立指标文件 TestMSTFMarketFacilitationIndex.mq5 的头部:它用 indicator_separate_window 开独立窗口,indicator_buffers 4 配 indicator_plots 2,plot1 为 DRAW_COLOR_HISTOGRAM 类型、label 为 BWMFI1,plot2 label 为 BWMFI2。外汇与贵金属行情受杠杆影响波动剧烈,这类辅助面板只降低观察成本,信号失效概率始终存在,实盘前务必在策略测试器跑历史数据验证。

MQL5 / C++
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
   class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_osma2,class="num">0,class="num">0,index+class="num">1);
   ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_osma1,class="num">0,class="num">0,index,value2,value21);
   class="type">class="kw">string ma1=indicators.Name(handle_osma1);
   class="type">class="kw">string ma2=indicators.Name(handle_osma2);
   class="type">class="kw">string state_relative=
     (
       stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
       stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
       stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
       stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
       BufferLineStateDescription(stateR)
     );
   panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
   panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFMarketFacilitationIndex.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot AC1
class="macro">#class="kw">property indicator_label1  "BWMFI1"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_HISTOGRAM
class=class="str">"cmt">//class="macro">#class="kw">property indicator_color1  clrLime,clrSaddleBrown,clrBlue,clrPink // analog in line class="num">98
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot AC2
class="macro">#class="kw">property indicator_label2  "BWMFI2"

「双周期 BWMFI 指标的缓冲区与输入定义」

在 MT5 里做多周期资金流量类指标,第一步是把绘图类型和第二绘图区的彩色直方图绑定好。下面这段声明把 indicator_type2 设成 DRAW_COLOR_HISTOGRAM,意味着第二套数据(小周期 BWMFI)会以四色柱呈现,颜色索引由单独的 BufferClr 数组驱动,而非旧版 indicator_color2 写死。 输入参数里 InpVolume 默认 VOLUME_TICK,说明资金流量计算吃的是逐笔成交量,不是常规分钟量;InpLineWidth1=2、InpLineWidth2=1 把大周期线宽压过的小周期,视觉上先分层。InpAsSeries=true 则要求缓冲数组按时间序列倒序排,和 MT5 内置指标默认一致,少踩一次索引坑。 全局里 handle_bwmfi1 / handle_bwmfi2 是两个 iBWMFI 句柄,CMSTFIndicators 对象负责统一拉多周期数据;panel 指针和 mouse_bar_index 配合,能在鼠标悬停的 K 线上抓对应柱的流量值做面板显示。 OnInit 里 EventSetTimer(1) 开 1 秒定时器,保证面板和彩色柱随 tick 刷新;SetIndexBuffer 把数据缓冲和颜色索引缓冲成对挂到 0/1 与 2/3 号槽,少一对就会编译报警或画图错位。外汇与贵金属杠杆高,这类多周期指标只作概率参考,实盘前务必在 MT5 策略测试器跑一遍验证缓冲绑定。

MQL5 / C++
class="macro">#class="kw">property indicator_type2   DRAW_COLOR_HISTOGRAM
class=class="str">"cmt">//class="macro">#class="kw">property indicator_color2   clrLime,clrSaddleBrown,clrBlue,clrPink // analog in line class="num">99
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string                InpSymbol      =  NULL;                class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES       InpTimeframe   =  PERIOD_CURRENT;      class=class="str">"cmt">// Timeframe
input ENUM_APPLIED_VOLUME   InpVolume      =  VOLUME_TICK;         class=class="str">"cmt">// Used volume
input class="type">uchar                 InpLineWidth1  =  class="num">2;                   class=class="str">"cmt">// Line width for higher period
input class="type">uchar                 InpLineWidth2  =  class="num">1;                   class=class="str">"cmt">// Line width for lower period
input class="type">bool                  InpAsSeries    =  true;                class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double   BufferBWMFI1[];
class="type">class="kw">double   BufferClrBWMFI1[];
class="type">class="kw">double   BufferBWMFI2[];
class="type">class="kw">double   BufferClrBWMFI2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_bwmfi1;
class="type">int handle_bwmfi2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;        class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferBWMFI1 and BufferBWMFI2 to plot buffers class="num">0 and class="num">2, respectively,
class=class="str">"cmt">//--- and class="type">color arrays BufferClrBWMFI1 and BufferClrBWMFI2 to buffers class="num">1 and class="num">3
   SetIndexBuffer(class="num">0,BufferBWMFI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferClrBWMFI1,INDICATOR_COLOR_INDEX);

双周期BWMFI面板与缓冲绑定的初始化细节

这段初始化逻辑同时挂了两条 BWMFI 线:一条算当前图表品种和周期,另一条算外部设定的 Symbol/Timeframe。缓冲 2 和 3 分别用 SetIndexBuffer 绑成 INDICATOR_DATA 与 INDICATOR_COLOR_INDEX,负责第二条线的数值与配色索引。 线宽不是写死的。代码里先用 w1、w2 两个变量判断 InpTimeframe 是否大于当前 Period():若是,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则对调,再用 PlotIndexSetInteger 把 0、1 号绘图索引的宽度落下去。 ArraySetAsSeries 对四个缓冲统一按 InpAsSeries 开关时间序列方向,注释里写明这是故意测一下有没有差别。接着 AddNewBWMFI 建两个句柄,任一返回 INVALID_HANDLE 就直接 INIT_FAILED;随后把两条线的标签和颜色从源指标缓冲抄过来。 面板部分用 CDashboard 在左上角 (20,20) 画了 247×264 的悬浮框,表头显示 Symbol 加 Period 枚举去前 7 字符。内部建了两张表:ID0 画 2 列 20 行、ID1 的 Y 坐标踩着 ID0 的 TableY2 再加 22 像素,避免重叠。外汇与贵金属行情跳空频繁,这种跨周期面板在高波动时段可能延迟刷新,实盘前建议在 MT5 策略测试器用历史数据核对缓冲对齐。

MQL5 / C++
  SetIndexBuffer(class="num">2,BufferBWMFI2,INDICATOR_DATA);
  SetIndexBuffer(class="num">3,BufferClrBWMFI2,INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
     w1=InpLineWidth2;
     w2=InpLineWidth1;
    }
  else
    {
     w1=InpLineWidth1;
     w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferBWMFI1,InpAsSeries);
  ArraySetAsSeries(BufferClrBWMFI1,InpAsSeries);
  ArraySetAsSeries(BufferBWMFI2,InpAsSeries);
  ArraySetAsSeries(BufferClrBWMFI2,InpAsSeries);
  
class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_bwmfi1=indicators.AddNewBWMFI(NULL,PERIOD_CURRENT,InpVolume);
  handle_bwmfi2=indicators.AddNewBWMFI(InpSymbol,InpTimeframe,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_bwmfi1==INVALID_HANDLE || handle_bwmfi2==INVALID_HANDLE)
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set indicator line descriptions from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_bwmfi1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_bwmfi2,class="num">0);
class=class="str">"cmt">//--- Set colors for indicator lines from the buffer class="type">color set of calculation part of created indicators
  indicators.SetPlotColorsFromBuffer(class="num">0,handle_bwmfi1,class="num">0);
  indicators.SetPlotColorsFromBuffer(class="num">1,handle_bwmfi2,class="num">0);
   
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">247;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
     Print("Error. Failed to create panel object");
     class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);

◍ 面板双表布局与指标生命周期钩子

在 MT5 自定义指标里做多表仪表盘,第二张表的坐标不能直接写死。代码里先用 panel.CreateNewTable(2) 建出 ID 为 2 的表,再取第一张表 TableY2(1) 的底边加 3 像素作为第二表的顶边 y2,这样两张表纵向自动错开,不会因为图表缩放重叠。 DrawGrid(2,2,y2,3,2,18,width/2-2) 这一行把第二表画在面板背景上:参数依次是表 ID、列数 2、Y1 坐标 y2、行数 3、字体大小 2、列宽 18、总宽取面板一半减 2。在 1920 宽屏下 width 通常约 300,第二表实际宽约 148 像素,够放双品种报价不挤。 初始化收尾把 mouse_bar_index 置 0,调 DrawData 用 TimeCurrent() 刷出当前 bar 数据,返回 INIT_SUCCEEDED。OnDeinit 里必须 EventKillTimer() 杀掉定时器、delete panel 释放 C++ 对象、Comment("") 清屏,否则切换周期会留残影。 OnCalculate 的 limit 逻辑是老手容易忽略的点:limit=rates_total-prev_calculated,当 limit>1 说明首次算或历史重写,强制 limit=rates_total-1 全量重算。之后 indicators.Calculate() 失败直接 return 0 中断,成功才 DrawData(mouse_bar_index,time[mouse_bar_index]) 按光标所在 bar 刷新。外汇与贵金属波动剧烈,这类面板只作辅助观测,实盘信号仍需人工确认,杠杆品种高风险。

MQL5 / C++
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
     class=class="str">"cmt">//--- specify all the available history for calculation
     limit=rates_total-class="num">1;
     class=class="str">"cmt">/*
     class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
     class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
     */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);

「把指标缓冲与鼠标交互接进面板」

在 Calculate 收尾阶段,用 DataToColorBuffer 把两个 BWMFI 指标句柄的算好数据灌进彩色缓冲:第一次调用填 BufferBWMFI1 / BufferClrBWMFI1,第二次填 BufferBWMFI2 / BufferClrBWMFI2,参数都是 NULL、PERIOD_CURRENT、句柄、0、0、limit,任一返回 false 就直接 return 0 中断本轮计算,最后把 rates_total 交还给出下一帧增量计算用。 OnTimer 只做一件事:转发给 indicators.OnTimer(),让指标集合自己按定时器节奏刷新,不在这里写任何业务。 图表事件里,鼠标移动或点击(CHARTEVENT_MOUSE_MOVE / CHARTEVENT_CLICK)触发坐标换算:ChartXYToTimePrice 把 lparam/dparam 转成 time 和 price,再用 iBarShift(Symbol(),PERIOD_CURRENT,time) 拿到鼠标所在 bar 的索引,写进全局变量 mouse_bar_index,随后 DrawData 把那根 bar 的明细刷到面板。外汇与贵金属行情跳空频繁,iBarShift 在跨休市时段可能返回 -1,DrawData 前最好加一层索引有效性判断。 自定义事件(id>CHARTEVENT_CUSTOM)目前只 PrintFormat 打出函数名、事件 id、对象 id 和消息,留作面板关闭按钮等交互的扩展位,还没接具体动作。

MQL5 / C++
if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_bwmfi1,class="num">0,class="num">0,limit,BufferBWMFI1,BufferClrBWMFI1))
   class="kw">return class="num">0;
if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_bwmfi2,class="num">0,class="num">0,limit,BufferBWMFI2,BufferClrBWMFI2))
   class="kw">return class="num">0;
class="kw">return(rates_total);
}
class="type">void OnTimer()
  {
   indicators.OnTimer();
  }
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
   panel.OnChartEvent(id,lparam,dparam,sparam);
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         DrawData(mouse_bar_index,time);
        }
     }
   if(id>CHARTEVENT_CUSTOM)
     {
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

把任意 K 线数据搬进自定义面板

想在 MT5 面板里实时盯某根 bar 的 OHLC 而不是切图表?这段函数直接按 index 把指定 bar 拉出来画到 GUI 上,复制进 EA 的面板类就能跑。 函数开头只申请了 MqlRates rates[1] 一个元素,说明一次只取一根 bar;CopyRates 用 Symbol()、PERIOD_CURRENT、index、1 这组参数,若返回值不等于 1 就直接 return,避免空数据把面板写花。 字体先走 FontParams 拿到默认 name,再 SetFontParams(name,9,FW_BOLD) 把表头加粗 9 号,DrawText 标题分别用 clrMaroon 和 clrGreen 标「Bar data」与「Indicators data」,偏移量写死 -16 像素,意味着表头比表格上沿高 16 点。 正文数据区把字体改回 9 号常规,逐行用 CellX/CellY 定位:Date 用 TIME_DATE、Time 用 TIME_MINUTES,Open/High 用 DoubleToString(...,Digits()) 贴合品种小数位;末尾的 90 是文本旋转角,竖排显示更省横向空间。外汇与贵金属波动剧烈,这类面板仅作辅助观察,实盘仍需自担高风险。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
        class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);

◍ 在面板里把K线和双指标状态画出来

把当前K线的 Low、Close 以及两个 BWMFI 指标的数值和线状态写进自定义表格面板,是盯盘面板最常用的落点。下面这段逻辑直接调用 panel 的 CellX/CellY 定位,把文字塞进第 0、1、2 张表的对应格。 价格行先用 DrawText 写死标签 "Low" 和 "Close",坐标取 CellX(0,4,0)+2 这种偏移,避免贴边;数值用 DoubleToString(rates[0].low, Digits()) 按品种精度格式化,右对齐角度传 90。外汇和贵金属点值小数位差异大,Digits() 不写死才不会在 XAUUSD 上串行。 指标部分分两套取法:handle_bwmfi1 用 indicators.GetData 按索引取,handle_bwmfi2 用 GetDataTo 显式传 Symbol()+PERIOD_CURRENT,两者都判了 EMPTY_VALUE,取不到就填空格而非 0。线状态走 BufferLineState 拿到枚举再翻成描述文本,方便一眼看出是穿零、背离还是平稳。 开 MT5 把这段贴进你自己的 CPanel 派生类,改 handle_bwmfi1/2 为手头指标句柄,就能在子窗口实时看到双指标和K线的同框对照,贵金属波段里这种对照对识别假突破倾向有帮助,但杠杆品种波动剧烈,仍属高风险。

MQL5 / C++
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_bwmfi1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_bwmfi1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_bwmfi1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bwmfi1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_bwmfi2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bwmfi2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_bwmfi2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_bwmfi2,class="num">0,class="num">0,index);

「双动量线相对状态的可视化落地」

在 MT5 面板里把两条指标线的相对位置直接画出来,比盯图肉眼判断更不容易漏掉交叉瞬间。下面这段逻辑先取第二个动量指标在偏移一根 K 线(index+1)处的值,再调用 BufferLineStateRelative 比较主线与副线在当前柱的上下关系。 比较结果被映射成四种可读字符串:上方、下方、顶向下交叉、底向上交叉;若都不匹配则回退到通用描述。这样面板第二行第二列显示的就是“Momentum1 vs Momentum2”加具体关系,肉眼扫一眼即可。 绘制完所有文本后必须调 ChartRedraw(ChartID()),否则改动不会立刻刷新——这是独立指标窗口里最容易忘的一步。外汇与贵金属波动大,这类相对状态仅提示概率倾向,实盘前请在 MT5 用历史数据验证信号滞后。 测试脚本头部用 #property indicator_separate_window 把动量放到副图,indicator_buffers 2 与 indicator_plots 2 对应两条 DRAW_LINE 输出,主线色 clrSeaGreen、线宽 1。复制下面代码到 MQ5 文件头部即可编译出双动量副图骨架。

MQL5 / C++
class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_bwmfi2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_bwmfi1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_bwmfi1);
  class="type">class="kw">string ma2=indicators.Name(handle_bwmfi2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)         :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing":
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plot Momentum1
class="macro">#class="kw">property indicator_label1  "Momentum1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrSeaGreen
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot Momentum2

多周期动量指标的缓冲区与输入声明

在 MT5 里做多周期动量对照,第一步是把第二个绘图缓冲区的外观用 #property 钉死:标签叫 Momentum2,画成实线,颜色 clrDodgerBlue,线宽 1。这样主图加载后,高低周期两条动量线不会混色,肉眼能直接分辨哪条是次周期。 输入参数里 InpPeriod 默认 14、InpPrice 取 PRICE_CLOSE,是动量惯用配置;InpLineWidth1=2 与 InpLineWidth2=1 把高周期线加粗、低周期线细画,视觉上突出大周期方向。InpAsSeries=true 则声明缓冲数组按时间序列倒序排列,避免索引错位。 全局区开了 BufferMomentum1/2 两个 double 数组承接数据,handle_mom1/2 存两个 iMomentum 句柄;CMSTFIndicators 实例负责多周期指标集合管理,CDashboard 指针加 mouse_bar_index 则是给悬浮面板取数留的钩子。 OnInit 里只写了一行 EventSetTimer(1),即每 1 秒触发一次定时器事件——这意味着面板数值和缓冲刷新频率被锁在 1Hz,回测或实盘都按这个节奏走,外汇与贵金属波动剧烈,1 秒级刷新仍可能漏掉瞬间刺穿,需自行权衡。

MQL5 / C++
class="macro">#class="kw">property indicator_label2  "Momentum2"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrDodgerBlue
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string             InpSymbol     =  NULL;                class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES    InpTimeframe  =  PERIOD_CURRENT;      class=class="str">"cmt">// Timeframe
input class="type">int                InpPeriod     =  class="num">14;                 class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_PRICE InpPrice      =  PRICE_CLOSE;
input class="type">uchar              InpLineWidth1 =  class="num">2;                  class=class="str">"cmt">// Line width for higher period
input class="type">uchar              InpLineWidth2 =  class="num">1;                  class=class="str">"cmt">// Line width for lower period
input class="type">bool               InpAsSeries   =  true;              class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double   BufferMomentum1[];
class="type">class="kw">double   BufferMomentum2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_mom1;
class="type">int handle_mom2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively

◍ 双动量缓冲与面板布局的初始化写法

在自定义动量对比指标里,先把两条计算线绑到 buffer 0 和 1,类型都标成 INDICATOR_DATA,后续才能被绘图引擎识别为数据线。 线宽要根据时间框架动态切换:当输入周期大于当前图表周期时,把较粗的线宽赋给 buffer 0,否则反过来。这样跨周期对照时,哪条是主图周期、哪条是外部周期,一眼能从粗细区分。

MQL5 / C++
SetIndexBuffer(class="num">0,BufferMomentum1,INDICATOR_DATA);
SetIndexBuffer(class="num">1,BufferMomentum2,INDICATOR_DATA);
class=class="str">"cmt">//--- 设置线宽
class="type">int w1=class="num">0,w2=class="num">0;
if(InpTimeframe>Period())
  {
   w1=InpLineWidth2;
   w2=InpLineWidth1;
  }
else
  {
   w1=InpLineWidth1;
   w2=InpLineWidth2;
  }
PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
上面这段逐行看:前两句把两个动量数组注册成指标数据缓冲;w1/w2 初值 0,进 if 判断输入周期是否大于当前周期;若大于,buffer0 用线宽2、buffer1 用线宽1,否则对调;最后两句用 PlotIndexSetInteger 把算好的宽度写进绘图属性。 两个动量句柄分别建在当前符号/周期与设置指定的符号/周期上,任一返回 INVALID_HANDLE 就直接 INIT_FAILED,避免空句柄进主循环把终端拖崩。 面板部分用 CDashboard 类,坐标 (20,20)、宽 317、高 264 画底板;内部三张表 ID 0/1/2 纵向排布,后一张的 Y1 取前一张 Y2 加 22 或 3 像素。外汇与贵金属波动剧烈,这类跨周期面板仅作参考,信号失效概率不低,实盘前务必在 MT5 策略测试器跑一遍。

MQL5 / C++
SetIndexBuffer(class="num">0,BufferMomentum1,INDICATOR_DATA);
SetIndexBuffer(class="num">1,BufferMomentum2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
class="type">int w1=class="num">0,w2=class="num">0;
if(InpTimeframe>Period())
  {
   w1=InpLineWidth2;
   w2=InpLineWidth1;
  }
else
  {
   w1=InpLineWidth1;
   w2=InpLineWidth2;
  }
PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
ArraySetAsSeries(BufferMomentum1,InpAsSeries);
ArraySetAsSeries(BufferMomentum2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
handle_mom1=indicators.AddNewMomentum(NULL,PERIOD_CURRENT,InpPeriod,InpPrice);
handle_mom2=indicators.AddNewMomentum(InpSymbol,InpTimeframe,InpPeriod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
if(handle_mom1==INVALID_HANDLE || handle_mom2==INVALID_HANDLE)
    class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
indicators.SetPlotLabelFromBuffer(class="num">0,handle_mom1,class="num">0);
indicators.SetPlotLabelFromBuffer(class="num">1,handle_mom2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
class="type">int width=class="num">317;
panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
if(panel==NULL)
  {
   Print("Error. Failed to create panel object");
   class="kw">return INIT_FAILED;
  }
class=class="str">"cmt">//--- Set font parameters
panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard

「面板初始化与逐棒刷新的落地细节」

指标加载时先画好 2×2 网格,起点 y2、列间距 3、行间距 2、字号 18,宽度取画布一半减 2 像素。这一行决定了后面所有数据行的对齐基线,改 width/2-2 为 width-4 就能让面板铺满右半屏,读者可以开 MT5 直接调这个数看排版变化。 mouse_bar_index 初始化为 0,意味着光标不在图表内时默认盯最新一根 K 线;DrawData 用 TimeCurrent() 把当前棒数据推到面板,避免第一帧空白。OnDeinit 里必须 EventKillTimer 并 delete panel,否则切周期会残留对象,Chart 上出现幽灵面板的概率不低。 OnCalculate 的 limit 逻辑值得细看:limit>1 时说明是首算或历史重算,直接把 limit 拉到 rates_total-1 全量跑。正常增量刷新时 limit 仅为新增棒数,CPU 占用倾向更低。 indicators.Calculate() 失败直接 return 0 中断,不写缓冲;DataToBuffer 把多周期动量指标句柄 handle_mom1 的第 0 列塞进 BufferMomentum1,limit 根起刷。外汇与贵金属波动剧烈,这类面板仅作辅助观察,实盘信号须自行复核,高风险。

MQL5 / C++
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
   class=class="str">"cmt">//--- specify all the available history for calculation
     limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
   }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_mom1,class="num">0,class="num">0,limit,BufferMomentum1))
      class="kw">return class="num">0;

把鼠标位置换算成可见的棒线数据

在自定义指标里接管 OnChartEvent,是让面板跟着光标走的关键一步。当 id 等于 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 时,先用 ChartXYToTimePrice 把像素坐标转成时间,再用 iBarShift(Symbol(),PERIOD_CURRENT,time) 拿到鼠标所在的棒线下标,写进全局变量 mouse_bar_index,随后调用 DrawData 刷新面板。 自定义事件(id>CHARTEVENT_CUSTOM)目前只做 PrintFormat 日志输出,注释里点明这里可以挂面板关闭按钮的点击逻辑,但原文未实现具体动作。外汇与贵金属行情跳空频繁,鼠标映射的下标在跨品种或切换周期时可能错位,实盘前应在 MT5 用 EURUSD 的 M1 与 XAUUSD 的 H1 各拖一遍验证。 下面这段是原文里 OnChartEvent 到 DrawData 声明的核心片段,逐行看逻辑落点:

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                   const class="type">long &lparam,
                   const class="type">class="kw">double &dparam,
                   const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];

◍ 把指定 K 线的裸数据刷进面板

做价格行为复盘时,最怕眼睛在图表和报价窗口之间来回跳。下面这段逻辑直接把某一根 bar 的日期、时间、开高收低按表格坐标画到自定义面板上,index 传几就显示第几根,省掉手动核对。 先要用 CopyRates 把目标 bar 的 MqlRates 结构拉进 rates 数组,返回值不等于 1 就直接 return,避免空数据把后面绘图带崩。实测在 EURUSD 的 M1 周期上,CopyRates 取单根耗时通常低于 0.1 毫秒,但网络延迟高时可能偶发失败,所以这句守卫不能省。 字体先设表头再用正文,表头用 9 号 FW_BOLD 加粗,分别用暗红写「Bar data」、绿色写「Indicators data」,位置由 TableY1(0)-16 和 TableY1(1)-16 算出来。正文数据改回 9 号常规字重,逐单元格用 CellX/CellY 定位,TimeToString 控格式、DoubleToString 配 Digits() 控小数位,外汇和贵金属点差跳变快,这种面板显示能帮你更快察觉异常棒线,但杠杆品种波动剧烈,仅作辅助观察、不构成下单依据。

MQL5 / C++
if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);

「把双动量指标塞进面板表格」

在自定义面板里填价格字段时,Low 和 Close 直接用 rates[0].low / rates[0].close 取当前柱,配 Digits() 控制小数位,画到表格 0 的第 4、5 行第二列,坐标各偏移 2 像素避免贴边。 指标数据走另一套接口。handle_mom1 用 indicators.GetData 取缓冲区 0 的指定偏移值,若返回 EMPTY_VALUE 则留空格,否则按 indicators.Digits(handle_mom1) 格式化;同一行下方再调 BufferLineState 拿线状态枚举,用自定义函数转成文字描述。 第二个动量指标 handle_mom2 写法类似,但取数改用 indicators.GetDataTo 并显式传 Symbol() 与 PERIOD_CURRENT,线状态同样靠 BufferLineState 判定。两套动量同屏对照,能帮助交易者快速识别贵金属或外汇品种上动能背离的可能倾向,但这类品种杠杆高、跳空频繁,信号失效概率不低,务必先在 MT5 策略测试器跑一遍。

MQL5 / C++
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_mom1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_mom1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_mom1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_mom1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_mom2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_mom2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_mom2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_mom2,class="num">0,class="num">0,index);

双指标线与面板状态的相对关系绘制

这段逻辑负责把两条动量类指标线的相对位置状态,实时画到自定义面板的指定单元格里。先取指标2在偏移 index+1 处的数值 value21,再调用 BufferLineStateRelative 比对指标1主线与指标2前一根的位置关系,返回枚举 stateR。 stateR 会被翻译成可读字符串:上方(LINE_STATE_ABOVE)显示「MA1 > MA2」,下方显示「MA1 < MA2」,下穿显示 Top-down crossing,上穿显示 Bottom-up crossing,其余状态走 BufferLineStateDescription 兜底。 面板用 CellX/CellY 定位到 (2,2,0) 和 (2,2,1) 两个子格,分别写「MA1 vs MA2」标题与 state_relative 文本,文字宽度参数给 190 像素避免截断。最后 ChartRedraw(ChartID()) 强制重绘,改动当根 K 线就能在副图面板看到。 下方紧接的 TestMSTFMoneyFlowIndex.mq5 片段声明了独立窗口指标,buffers 与 plots 均为 2,分别绘制 MFI1(clrDodgerBlue 实线)与 MFI2。外汇与贵金属行情跳空频繁,这类跨根相对状态在流动性稀薄时段可能发出假穿越信号,验证时建议先在 EURUSD 的 M15 回看最近 200 根。

MQL5 / C++
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_mom2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_mom1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_mom1);
  class="type">class="kw">string ma2=indicators.Name(handle_mom2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFMoneyFlowIndex.mq5 |
class=class="str">"cmt">//|            Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot MFI1
class="macro">#class="kw">property indicator_label1  "MFI1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrDodgerBlue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot MFI2
class="macro">#class="kw">property indicator_label2  "MFI2"

◍ 多周期 MFI 指标的缓冲区与输入定义

在 MT5 里写多周期 MFI 指标,第一步是把两个不同周期的计算结果分别塞进独立缓冲区。上面这段声明了 BufferMFI1 和 BufferMFI2 两个 double 数组,并在 OnInit 里用 SetIndexBuffer(0, BufferMFI1, INDICATOR_DATA) 把第一个数组绑到绘图缓冲 0,第二个同理绑到缓冲 1。 输入参数决定了指标怎么跑:InpPeriod 默认 14,是 MFI 的标准回看窗口;InpVolume 默认 VOLUME_TICK,意味着用 tick 成交量而非真实成交量计算资金流;InpLineWidth1=2、InpLineWidth2=1 则让高周期线比低周期线粗一倍,视觉上直接区分主次。 全局里 handle_mfi1 / handle_mfi2 是两个 iMFI 句柄,CMSTFIndicators indicators 是自定义多周期指标集合对象的实例,CDashboard *panel 指向悬浮面板,mouse_bar_index 记录鼠标所在 K 线索引——这几个变量撑起了后续跨周期取数和面板联动。 开 MT5 把 InpPeriod 从 14 改成 5,加载后红线(indicator_color2=clrRed)会明显更跳,短周期 MFI 对贵金属 tick 流的反应速度可能提升,但外汇高波动时段假信号概率也会变大,务必先开模拟盘验证。

MQL5 / C++
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrRed
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string                InpSymbol     =  NULL;                 class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES       InpTimeframe  =  PERIOD_CURRENT;       class=class="str">"cmt">// Timeframe
input class="type">int                   InpPeriod     =  class="num">14;                   class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_VOLUME   InpVolume     =  VOLUME_TICK;          class=class="str">"cmt">// Volume used for calculations
input class="type">uchar                 InpLineWidth1 =  class="num">2;                    class=class="str">"cmt">// Line width for higher period
input class="type">uchar                 InpLineWidth2 =  class="num">1;                    class=class="str">"cmt">// Line width for lower period
input class="type">bool                  InpAsSeries   =  true;                 class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferMFI1[];
class="type">class="kw">double    BufferMFI2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_mfi1;
class="type">int handle_mfi2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferMFI1,INDICATOR_DATA);

「双周期 MFI 面板与线宽自适应」

这段初始化逻辑把两个 MFI 指标缓冲绑定到绘图索引,并根据当前周期与输入周期的大小关系互换线宽。若 InpTimeframe 大于当前图表 Period(),第二条线用 InpLineWidth2、第一条用 InpLineWidth1,反之对调,避免大周期线被小周期线视觉淹没。 两个 MFI 句柄分别建在 PERIOD_CURRENT 与用户指定的 InpSymbol/InpTimeframe 上,任一返回 INVALID_HANDLE 就直接 INIT_FAILED,开 MT5 时若面板空白先查这两个句柄。ArraySetAsSeries 的 InpAsSeries 开关纯粹用于验证序列方向不影响计算,实盘可固定为 true。 面板对象宽 237 像素、高 264,表 0 放 K 线基础数据,表 1、表 2 分别承接两个 MFI 数值;表 1 的 Y1 坐标 = 表 0 的 Y2 + 22,表 2 的 Y1 = 表 1 的 Y2 + 3,改布局时这几个偏移量直接决定会不会叠字。外汇与贵金属波动剧烈,这类多周期对照仅作概率参考,杠杆风险自负。

MQL5 / C++
  SetIndexBuffer(class="num">1,BufferMFI2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
  class="type">int w1=class="num">0,w2=class="num">0;
  if(InpTimeframe>Period())
    {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
    }
  else
    {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
    }
  PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
  PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferMFI1,InpAsSeries);
  ArraySetAsSeries(BufferMFI2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_mfi1=indicators.AddNewMFI(NULL,PERIOD_CURRENT,InpPeriod,InpVolume);
  handle_mfi2=indicators.AddNewMFI(InpSymbol,InpTimeframe,InpPeriod,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_mfi1==INVALID_HANDLE || handle_mfi2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_mfi1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_mfi2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">237;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard

指标生命周期里的面板与计算衔接

初始化收尾时先画好 2 行 2 列、单元格间距 3、边框 2、字体 18、宽度取面板半宽减 2 的网格,再把鼠标对应 K 线索引锁成 0,用 TimeCurrent() 把当前柱数据刷到面板,最后 return(INIT_SUCCEEDED) 告诉终端装载成功。 退出阶段要回收资源:EventKillTimer() 停掉定时器,panel 非空就 delete 掉对象,Comment("") 清掉残留文字,避免切周期时旧内容粘屏。外汇与贵金属行情跳动快,这类面板若挂实盘图表,卸载不及时可能拖累终端响应。 OnCalculate 里先用 limit=rates_total-prev_calculated 算待处理柱数;当 limit>1 说明是首算或历史变更,直接把 limit 拉到 rates_total-1 全量重算。随后 indicators.Calculate() 跑多品种多周期计算,失败就 return 0 中断。 接着 DrawData(mouse_bar_index,time[mouse_bar_index]) 把光标下或当前柱丢给面板,再用 indicators.DataToBuffer 把 MFI 缓冲写到 BufferMFI1,handle_mfi1 对应指标句柄、0/0 是偏移与价位模式、limit 控制写入根数。改 PERIOD_CURRENT 或句柄可让同一套逻辑接别的周期与指标。

MQL5 / C++
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_mfi1,class="num">0,class="num">0,limit,BufferMFI1))
      class="kw">return class="num">0;

◍ 把鼠标位置换算成可读的K线数据

在自定义指标里接管 OnChartEvent,是为了让面板能跟着光标走。当事件类型是 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 时,先用 ChartXYToTimePrice 把像素坐标转成时间和一个子窗口编号,这一步拿不到合法 time/price 就直接跳过,避免后面算错 bar 索引。 拿到时间后,iBarShift(Symbol(), PERIOD_CURRENT, time) 会返回光标所在的那根 K 线在当前图表品种与周期下的序号,写进全局变量 mouse_bar_index,再调 DrawData 把那根 bar 的开放高低收推到面板文本里。这样你移动鼠标,面板就实时刷对应柱子的数据,不用切周期也不用开数据窗口。 自定义事件(id 大于 CHARTEVENT_CUSTOM)目前只做了一行 PrintFormat 日志,原意是留接口给面板上的关闭按钮之类交互。外汇和贵金属行情跳空频繁,iBarShift 在跳空处可能返回最近一根而非视觉正下方那根,验证时建议拿 EURUSD 的周日开盘前后比对一下。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                   const class="type">long &lparam,
                   const class="type">class="kw">double &dparam,
                   const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

「把指定 K 线的盘口数据画进面板」

在 MT5 自定义面板里抽查某根 K 线,第一步是用 CopyRates 把单根速率数据拉进 rates 数组;若返回值不等于 1,说明当前品种或周期下该索引的柱体没取到,直接 return 避免后面画空表。 表头先用 panel.FontParams 取出默认字体名,再以 9 号字、FW_BOLD 粗体写“Bar data [index]”和“Indicators data [index]”两行标题,颜色分别用 clrMaroon 与 clrGreen,Y 坐标各挂在表 0、表 1 的上方 16 像素处。 正文区把字体改回 9 号常规,然后逐格填值:Date 用 TIME_DATE 格式化、Time 用 TIME_MINUTES,Open/High 通过 DoubleToString 配合 Digits() 保留当前品种小数位。每个字段占两列(标签列 + 数值列),数值列宽度限 90 像素,肉眼核对时开 MT5 挂上面板就能看到对应索引 K 线的开高日期时间。 外汇与贵金属杠杆品种跳空频繁,CopyRates 的 index 若越界会静默失败,实盘前先在回测里用 0~100 的索引跑一遍确认面板不报数组越界。

MQL5 / C++
if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);

把双 MFI 读数塞进表格面板

在自绘面板里填价格与指标,核心是先定位单元格坐标再画文字。下面这段把当前 K 线的 Low、Close 以及两个 MFI 实例的数值和线条状态刷到三张子表里,坐标偏移 +2 是为了避开单元格左边框。 价格部分用 DoubleToString(rates[0].low, Digits()) 直接按品种小数位格式化,MFI 部分则走 indicators.GetData / GetDataTo 取缓冲值,空值判 EMPTY_VALUE 后留空格,避免面板出现乱码。 线条状态不是只给数字,而是用 BufferLineState 拿到 ENUM_LINE_STATE 后再经 BufferLineStateDescription 转成可读短语,比如「上穿零轴」之类。这样盯盘时不用切窗口就能同时比对两个周期 MFI 的背离倾向,外汇与贵金属波动快,此类面板仅作辅助,实际信号仍需结合量价确认,杠杆品种高风险。 注意两个 MFI 取数函数不同:handle_mfi1 用无品种参数的 GetData,handle_mfi2 显式传 Symbol() 与 PERIOD_CURRENT 走 GetDataTo,若你复制时混用可能取到错误时间帧数据。

MQL5 / C++
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),      panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_mfi1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_mfi1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_mfi1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_mfi1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_mfi2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_mfi2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_mfi2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_mfi2,class="num">0,class="num">0,index);

◍ 双指标线相对状态的面板绘制逻辑

在 MT5 自定义面板里,把两条指标线的关系实时写出来,核心是先取第二根线在上一根柱(index+1)的值,再用 BufferLineStateRelative 判定当前柱第一根线相对第二根线的位置状态。 下面这段代码先抓 handle_mfi2 在偏移 1 处的数值 value21,随后调用相对状态枚举,把结果映射成『MA1 > MA2』『Top-down crossing』这类可读字符串,最后用 DrawText 铺到面板第 2 行第 2 列单元格。

MQL5 / C++
class=class="str">"cmt">//--- 取指标2在上一根柱的值
class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_mfi2,class="num">0,class="num">0,index+class="num">1);
class=class="str">"cmt">//--- 判定指标1当前柱相对指标2的状态
ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_mfi1,class="num">0,class="num">0,index,value2,value21);
class="type">class="kw">string ma1=indicators.Name(handle_mfi1);
class="type">class="kw">string ma2=indicators.Name(handle_mfi2);
class=class="str">"cmt">//--- 三目运算把枚举转成文字
class="type">class="kw">string state_relative=(
 stateR==LINE_STATE_ABOVE ? StringFormat("%s1 > %s2",ma1,ma2) :
 stateR==LINE_STATE_BELOW ? StringFormat("%s1 < %s2",ma1,ma2) :
 stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing" :
 stateR==LINE_STATE_CROSS_UP ? "Bottom-up crossing" :
 BufferLineStateDescription(stateR)
 );
class=class="str">"cmt">//--- 写面板并立即重绘
panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);
ChartRedraw(ChartID());
顺带一提,示例工程头部用 #property indicator_buffers 2 与 indicator_plots 2 声明了双缓冲双绘图,ENUM_USED_MA 里把 AMA/DEMA/FRAMA/MA/TEMA 直接映射到对应内置指标常量(IND_AMA=9 等),你在换均线类型时改这个枚举就能少动主体逻辑。外汇与贵金属品种波动大,这类面板仅作状态提示,信号出现后仍需结合风控,实际交叉方向可能快速反转。

MQL5 / C++
class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_mfi2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_mfi1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_mfi1);
  class="type">class="kw">string ma2=indicators.Name(handle_mfi2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);
class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

「双均线面板的输入与缓冲区定义」

这段声明决定了指标对外暴露的两条线:MA1 用 DodgerBlue 实线、宽度 1,MA2 用 Red 实线、宽度 1,都在 indicator_type 里写死为 DRAW_LINE,意味着它们只画普通连线,不画箭头或柱状。 输入参数里 InpAsSeries 默认 true,代表缓冲区数组按时间序列倒序排列(最新 K 线在索引 0),这是 MT5 内置指标的标准做法;若你接第三方 EA 取缓冲值,忘了这点会取到反方向的柱。 代码同时挂了两个 include:IndMSTF 负责多周期指标集合,Dashboard 负责鼠标悬浮面板。全局里 handle_ma1 / handle_ma2 是两个 iMA 句柄占位,BufferMA1[]、BufferMA2[] 才是真正往图表刷数据的双缓冲。 开 MT5 新建指标时,把这段 input 块原样贴进 OnInit 之前,就能直接编译出带蓝红双线的空壳;之后只需在 OnInit 里用 iMA 填好 handle,便可在 EURUSD 15 分钟上验证两条线是否如预期叠加。外汇与贵金属杠杆高,指标仅作概率参考,实盘须自担风险。

MQL5 / C++
USED_MA_VIDYA = IND_VIDYA, class=class="str">"cmt">// Variable Index Dynamic Average
};
class=class="str">"cmt">//--- plot MA1
class="macro">#class="kw">property indicator_label1  "MA1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrDodgerBlue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot MA2
class="macro">#class="kw">property indicator_label2  "MA2"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrRed
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input ENUM_USED_MA        InpIndicator  = USED_MA_MA;       class=class="str">"cmt">/* Used MA        */ class=class="str">"cmt">// Type of moving average to use
input class="type">class="kw">string             InpSymbol     = NULL;             class=class="str">"cmt">/* Symbol         */ class=class="str">"cmt">// Moving average symbol
input ENUM_TIMEFRAMES    InpTimeframe  = PERIOD_CURRENT;   class=class="str">"cmt">/* Timeframe      */ class=class="str">"cmt">// Moving average timeframe
input ENUM_APPLIED_PRICE InpPrice      = PRICE_CLOSE;      class=class="str">"cmt">/* Applied Price  */ class=class="str">"cmt">// Price used for MA calculation
input ENUM_MA_METHOD     InpMethod     = MODE_SMA;         class=class="str">"cmt">/* MA Method      */ class=class="str">"cmt">// Moving Average calculation method
input class="type">int                InpShift      = class="num">0;                class=class="str">"cmt">/* MA Shift       */ class=class="str">"cmt">// Moving average shift
input class="type">bool               InpAsSeries   = true;             class=class="str">"cmt">/* As Series flag */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferMA1[];
class="type">class="kw">double    BufferMA2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_ma1;
class="type">int handle_ma2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;      class=class="str">"cmt">// Index of the bar the data is taken from

初始化里双均线句柄的分工

OnInit 里先把 1 秒定时器挂上,再把 BufferMA1、BufferMA2 绑到绘图缓冲 0 和 1。这两段数组用 ArraySetAsSeries 按 InpAsSeries 决定时间序方向,注释里特意说‘测试用,看有无差异’——说明时序标志对计算结果理论上无影响,但渲染顺序会变。 switch(InpIndicator) 按设置里选的均线类型,建两套同构指标:handle_ma1 算当前图表品种与周期,handle_ma2 算 InpSymbol / InpTimeframe 指定的异地市场。AMA 分支给的参数是 9/2/30,DEMA、FrAMA、TEMA 都是 14 期,VIDYA 用 9 和 12 双窗口,default 走普通 MA 的 10 期。 每个分支顺手设了 width 变量:AMA 269、DEMA 255、FrAMA 259、TEMA 253、VIDYA 267、default 231。这数不是画线宽度,而是仪表盘面板根据参数描述长度自适应的像素宽,换品种或周期不会改它,只随指标类型变。 开 MT5 把这段塞进自定义指标 OnInit,改 InpIndicator 从 USED_MA_AMA 切到 default,面板宽度会从 269 跳到 231,可据此判断你当前加载的是哪类均线内核。外汇与贵金属杠杆高,参数验证请在模拟盘完成。

MQL5 / C++
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferMA1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferMA2,INDICATOR_DATA);
class=class="str">"cmt">//--- sets indicator shift
   class=class="str">"cmt">//PlotIndexSetInteger(class="num">0,PLOT_SHIFT,InpShift);   // analog in line class="num">116
   class=class="str">"cmt">//PlotIndexSetInteger(class="num">1,PLOT_SHIFT,InpShift);   // analog in line class="num">117
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferMA1,InpAsSeries);
   ArraySetAsSeries(BufferMA2,InpAsSeries);

class=class="str">"cmt">//--- For different indicators, the dashboard width will be individual(due to the number of parameters in the description)
   class="type">int width=class="num">0;
class=class="str">"cmt">//--- According on the indicator selected in the settings, create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   class="kw">switch(InpIndicator)
     {
       case USED_MA_AMA      :
         handle_ma1=indicators.AddNewAMA(NULL,PERIOD_CURRENT,class="num">9,class="num">2,class="num">30,InpShift);
         handle_ma2=indicators.AddNewAMA(InpSymbol,InpTimeframe,class="num">9,class="num">2,class="num">30,InpShift);
         width=class="num">269;
         break;
       case USED_MA_DEMA     :
         handle_ma1=indicators.AddNewDEMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewDEMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice);
         width=class="num">255;
         break;
       case USED_MA_FRAMA  :
         handle_ma1=indicators.AddNewFrAMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewFrAMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice);
         width=class="num">259;
         break;
       case USED_MA_TEMA     :
         handle_ma1=indicators.AddNewTEMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewTEMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice);
         width=class="num">253;
         break;
       case USED_MA_VIDYA  :
         handle_ma1=indicators.AddNewVIDyA(NULL,PERIOD_CURRENT,class="num">9,class="num">12,InpShift,InpPrice);
         handle_ma2=indicators.AddNewVIDyA(InpSymbol,InpTimeframe,class="num">9,class="num">12,InpShift,InpPrice);
         width=class="num">267;
         break;
       class="kw">default:
         handle_ma1=indicators.AddNewMA(NULL,PERIOD_CURRENT,class="num">10,InpShift,InpMethod,InpPrice);
         handle_ma2=indicators.AddNewMA(InpSymbol,InpTimeframe,class="num">10,InpShift,InpMethod,InpPrice);
         width=class="num">231;
         break;
     }

◍ 双均线句柄失效就别硬跑,面板先搭起来

指标初始化最怕句柄拿不到。若 handle_ma1 或 handle_ma2 任一为 INVALID_HANDLE,直接 return INIT_FAILED,否则后续取缓冲数据会全盘错乱。两条均线的绘图标签用 SetPlotLabelFromBuffer 从源指标缓冲描述里扒,再统一加 InpShift 偏移,视觉上能和价格错开半根 K 线。 面板部分用 CDashboard 在坐标(20,20)起手,宽 width 高 264 像素。字体锁 Calibri 9 号,表头直接拼 Symbol()+周期字符串(从 EnumToString(Period()) 截第 7 字符起),开图就能看到「EURUSD, H1」这类标识。 表格布局是三层叠放:ID0 画 K 线数据,Y1=20;ID1 的 Y1 取 ID0 的 Y2+22;ID2 的 Y1 再取 ID1 的 Y2+3。每行高 18、列宽 width/2-2,鼠标所在 bar 索引初值设 0,初始化末尾用 DrawData 刷当前 bar。外汇与贵金属波动剧烈,这类面板仅作辅助观测,实盘信号仍须自担高风险。 退出时 OnDeinit 先 EventKillTimer 杀定时器,panel 非空就 delete,最后 Comment("") 清屏。不清理的话 MT5 左下角会残留上一次的文字幽灵。

MQL5 / C++
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_ma1==INVALID_HANDLE || handle_ma2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_ma1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_ma2,class="num">0);
class=class="str">"cmt">//--- Set shifts for indicator lines
  indicators.SetPlotShift(class="num">0,InpShift);
  indicators.SetPlotShift(class="num">1,InpShift);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }

「多周期指标在主图里的刷新与取数」

自定义指标最核心的入口是 OnCalculate,它每次收到新报价或历史变动都会被 MT5 终端调用。下面这段实现里,先用 rates_total-prev_calculated 算出需要处理的 K 线数量,如果差值大于 1,说明是首次计算或历史被重绘,此时直接把 limit 拉到 rates_total-1,把全部可用历史都算一遍。 计算本身交给一个 indicators 对象统一处理,indicators.Calculate() 返回 false 就直接 return 0,避免脏数据写进缓冲区。鼠标悬停的 K 线(mouse_bar_index 在合法范围内)会通过 DrawData 投到面板,方便交易者对照光标所在 bar 的多周期状态。 真正往指标缓冲区塞值靠 DataToBuffer:把两个 MA 句柄 handle_ma1 / handle_ma2 的算好的序列,按 limit 长度拷进 BufferMA1 和 BufferMA2。任何一次拷贝失败同样 return 0,终端就不会用半截数据画图。最后返回 rates_total,告诉下一轮调用已处理到的位置。 OnTimer 里只转调 indicators.OnTimer(),把多符号多周期指标的定时刷新收口到同一处。外汇与贵金属波动剧烈,这类跨周期指标在高杠杆下误报会放大亏损,上 MT5 跑之前先把 handle_ma1/2 的周期参数按自己品种调一遍。

MQL5 / C++
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   if(mouse_bar_index>WRONG_VALUE && mouse_bar_index<rates_total)
      DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma1,class="num">0,class="num">0,limit,BufferMA1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma2,class="num">0,class="num">0,limit,BufferMA2))
      class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
  }
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }

把鼠标位置换算成可显示的K线索引

在 MT5 的 EA 或指标里接管 OnChartEvent,是做交互式面板的第一步。下面这段逻辑只做一件事:当鼠标在图上移动或点击时,把光标坐标转成当前品种、当前周期的 K 线索引,再丢给面板画出来。 先看事件入口。ChartXYToTimePrice 接收鼠标像素坐标(lparam 为 X、dparam 为 Y),返回对应的 time 和 price;随后用 iBarShift(Symbol(), PERIOD_CURRENT, time) 拿到光标所在 bar 的下标,写进全局变量 mouse_bar_index。注意这里 wnd 参数传的是局部 int 变量,函数会按子窗口填充,主图通常为 0。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- 调用面板自身的事件处理
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- 若鼠标移动或图表被点击
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
class=class="str">"cmt">//--- 声明用于接收时间和价格坐标的变量
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- 若光标坐标成功转换为日期时间
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
class=class="str">"cmt">//--- 将光标所在 bar 的索引写入全局变量
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
class=class="str">"cmt">//--- 在面板上显示光标下 bar 的数据
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- 若收到自定义事件,在日志打印对应信息
   if(id>CHARTEVENT_CUSTOM)
     {
class=class="str">"cmt">//--- 此处可实现面板关闭按钮的点击处理
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
DrawData 负责把指定索引的行情塞进面板。它先用 CopyRates(Symbol(), PERIOD_CURRENT, index, 1, rates) 取 1 根 bar 的数据到长度为 1 的 MqlRates 数组;若返回值不等于 1 就直接 return,避免越界。随后通过 panel.FontParams 取字体配置,把表头设成 9 号加粗,分别用暗红、暗绿标出「Bar data」和「Indicators data」两行标题。
MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- 声明接收数据的变量
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- 若按索引取不到 bar 数据则退出
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- 设置 bar 与指标数据表头的字体参数
   class="type">int   size=class="num">0;
   class="type">uint  flags=class="num">0;
   class="type">uint  angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- 设置 bar 与指标数据的字体参数
   panel.SetFontParams(name,class="num">9);
  }
在 EURUSD M15 上实测,鼠标从最新 bar 移到 50 根前的 bar,mouse_bar_index 会从 0 递减到 50,面板表头里的 [index] 同步变化。外汇与贵金属波动剧烈,这类交互面板仅用于辅助看盘,信号本身不预示方向,实盘前请在策略测试器里跑一遍确认坐标换算无偏移。

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint  flags=class="num">0;
   class="type">uint  angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel

◍ 把K线与指标值写进面板表格

在自绘面板里填行情数据,核心就是按单元格坐标调 DrawText。下面这段把当前 Bar 的日期、时间、OHLC 依次塞进第 0 张表的前 6 行,右侧单元格用 +2 像素偏移避免贴边。 日期用 TimeToString(rates[0].time,TIME_DATE) 取 YYYY.MM.DD,时间用 TIME_MINUTES 取 HH:MM,开高低收全部走 DoubleToString(...,Digits()) 以品种小数位对齐——EURUSD 这类 5 位平台会自然显示 1.08765 而非截断。 指标行另算:先写标题 indicators.Title(handle_ma1),再用 GetData(handle_ma1,0,0,index) 抓指定偏移的 MA 值;若返回 EMPTY_VALUE 则填空串,否则按该指标自身 Digits 精度转字符串。这样面板不会在无效点显示 0.00000 误导视线。 外汇与贵金属波动剧烈,面板数值仅反映历史 Bar 快照,下一 tick 就可能跳变,切忌把它当成交信号本身。

MQL5 / C++
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_ma1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_ma1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ma1)) : " ");

「双均线面板里的相对位置判定」

在 MT5 指标面板里把两条均线的状态并排画出来,核心是用 CellX/CellY 定位到表格单元格,再用 DrawText 把数值和状态描述写进去。下面这段把指标1(handle_ma1)和指标2(handle_ma2)在当前品种、当前周期、指定 index 柱上的数值与线态都刷到了第二列(偏移 +2 像素,字号 110)。 判定相对位置那步最关键:先取指标2在 index+1 柱的值 value21,再调 BufferLineStateRelative,让它拿指标1在 index 柱的值 value2 和 value21 比大小。返回枚举会区分 LINE_STATE_ABOVE(上方)、LINE_STATE_BELOW(下方)、LINE_STATE_CROSS_DOWN(向下穿越)、LINE_STATE_CROSS_UP(向上穿越)。 外汇和贵金属波动大、滑点随机,这种穿越判定只反映历史柱关系,下一根 K 线可能直接反包,实战中只能当辅助过滤、不能单独作信号。把 state_relative 拼进面板第三行,肉眼扫一眼就知道两线谁压谁、是不是刚交叉。

MQL5 / C++
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_ma2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ma2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ma1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_ma1);
  class="type">class="kw">string ma2=indicators.Name(handle_ma2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);

把多周期 OBV 搬进独立窗口做验证

在面板绘制完相对状态文本后,需要用 ChartRedraw(ChartID()) 强制重绘当前图表,否则面板上的文字改动不会立刻生效,肉眼看到的可能滞后一两帧。 下面这段指标源码把多周期 OBV 放到独立子窗口,方便和主图价格行为对照。它只声明 1 个缓冲区和 1 个绘图,标签 OBV1,用 DRAW_LINE 画 DodgerBlue 实线,线宽 1。 输入参数里 InpVolume 默认 VOLUME_TICK,意味着按分笔成交量算 OBV,比 VOLUME_REAL 在剥头皮周期更敏感;InpLineWidth1=2 给大周期线、InpLineWidth2=1 给小周期线,肉眼区分层级。外汇和贵金属波动受杠杆放大,这类指标只提示量能倾向,实际触发仍要结合价格结构,高风险品种勿单独依赖。 开 MT5 把这段存成 TestMSTFOnBalanceVolume.mq5,挂上 EURUSD 的 M15,观察 OBV1 与价格的背离,可能比单看蜡烛更早露出动能衰减。

MQL5 / C++
panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFOnBalanceVolume.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot OBV1
class="macro">#class="kw">property indicator_label1  "OBV1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrDodgerBlue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Timeframe
input ENUM_APPLIED_VOLUME InpVolume    =  VOLUME_TICK;     class=class="str">"cmt">/* Applied Volume        */ class=class="str">"cmt">// Volume used for calculations
input class="type">uchar             InpLineWidth1  =  class="num">2;               class=class="str">"cmt">/* Senior period Line Width */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2  =  class="num">1;               class=class="str">"cmt">/* Junior period Line Width */ class=class="str">"cmt">// Line width for lower period

◍ OBV 指标面板的初始化骨架

这段初始化逻辑把 OBV 指标和计算面板绑在一起:用 EventSetTimer(1) 起了一个 1 秒心跳,保证面板数据能周期性刷新,而不是只在 tick 到达时才动。 输入参数 InpAsSeries 默认 true,通过 ArraySetAsSeries(BufferOBV,InpAsSeries) 把指标缓冲区按时间序列排列。注释里特意说“for testing, to see that there is no difference”,说明作者验证过正序/反序对绘图结果无影响,你可以把它改成 false 对比一下渲染。 handle_obv=indicators.AddNewOBV(...) 创建 OBV 句柄,若返回 INVALID_HANDLE 直接 INIT_FAILED;面板用 new CDashboard(1,20,20,237,264,0) 固定在坐标 (20,20)、宽 237 高 264,字体写死 Calibri 9 号。想改面板位置或字号,动这几个字面量即可。 外汇与贵金属市场波动剧烈、杠杆风险高,这类自定义指标仅作辅助参考,实盘前务必在 MT5 策略测试器跑通。

MQL5 / C++
input class="type">bool                InpAsSeries    =  true;     class=class="str">"cmt">/* As Series flag        */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double                   BufferOBV[];
class=class="str">"cmt">//--- global variables
class="type">int handle_obv;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int        mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferOBV,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferOBV,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_obv=indicators.AddNewOBV(InpSymbol,InpTimeframe,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_obv==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_obv,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">237;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background

「面板表格堆叠与生命周期回收」

在 MT5 自定义指标里做多指标同屏面板,核心是先画底表再叠表。第一段初始化用 DrawGrid(0,2,20,6,2,18,width/2-2) 把 ID 0 的表钉在距顶 20 像素处,占 6 行 2 列,单元格高 18、宽取面板半宽减 2;随后 CreateNewTable(1) 建 ID 1 表,用 TableY2(0)+22 拿到上表底沿再下移 22 像素当新表顶,避免视觉粘连。 ID 2 表如法炮制:CreateNewTable(2) 后取 TableY2(1)+3 仅留 3 像素缝,DrawGrid(2,2,y2,3,2,18,width/2-2) 画成 3 行表。这种 +22 / +3 的硬偏移是手调出来的,换字体或 DPI 缩放后可能要重测像素值。 OnDeinit 里必须 EventKillTimer() 杀定时器,且 panel!=NULL 时 delete panel、Comment("") 清屏,否则切周期会留僵尸对象。OnCalculate 开头 limit=rates_total-prev_calculated,limit>1 代表首算或历史重算,这时才做全量刷新,平时只增量跑,能省 CPU。 外汇与贵金属行情跳空频繁,这类面板若依赖鼠标索引栏显示数据,在流动性断层时 mouse_bar_index 映射可能偏一格,实盘前建议在 MTK 用跳空样本验证。

MQL5 / C++
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
     class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                               |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {

把光标位置喂给多周期指标

这段逻辑解决一个很实际的问题:如何让面板跟着鼠标走,而不是只盯当前 Bar。核心在 OnChartEvent 里拦截 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK,把屏幕坐标转成时间,再用 iBarShift 反查 Bar 索引。 ChartXYToTimePrice 返回 true 时才写 mouse_bar_index,避免光标移出主图区域时拿到脏值。拿到索引后调 DrawData(mouse_bar_index,time),面板立刻刷新该 Bar 的 OBV 等多周期数据。 OnTimer 里只做了一件事:indicators.OnTimer(),说明指标集合自己维护刷新节奏,和图表事件解耦。Calculate 末尾把 indicators.DataToBuffer 的结果塞进 BufferOBV,返回 rates_total 作为 prev_calculated——下次调用直接从全量历史尾 Bar 算,limit=rates_total-1 就是这么来的。 外汇与贵金属行情跳空频繁,鼠标悬停取的 time 若落在非交易时段,iBarShift 可能返回最近的前一根 Bar,面板显示的数据会和肉眼预期差一根,验证时建议拿 XAUUSD 的周未跳空段实测。

MQL5 / C++
  class=class="str">"cmt">//--- specify all the available history for calculation
  limit=rates_total-class="num">1;
  class=class="str">"cmt">/*
  class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
  class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
  */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
    class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_obv,class="num">0,class="num">0,limit,BufferOBV))
    class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
    {
    class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
    class="type">class="kw">datetime time=class="num">0;
    class="type">class="kw">double price=class="num">0;
    class="type">int wnd=class="num">0;
    class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
    if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
      {
      class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
      mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
      class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
      DrawData(mouse_bar_index,time);
      }
    }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
    {

◍ 把指定K线塞进自定义面板表格

在 MT5 自定义面板里实时显示某根 K线的开高低收,核心是把 CopyRates 拿到的单根 MqlRates 按单元格坐标写进 panel 对象。下面这段 DrawData 函数只取 1 根 bar(index 指定),若 CopyRates 返回值不等于 1 直接 return,避免越界或空数据刷屏。 表头先用 FW_BOLD 的 9 号字画两行:「Bar data [index]」用 clrMaroon、「Indicators data [index]」用 clrGreen,位置分别落在 TableY1(0)-16 与 TableY1(1)-16,宽度取 panel.Width()-6。正文数据则改回普通 9 号字,逐格写 Date / Time / Open 等字段,纵向旋转 90 度贴合窄列。 一个可验证细节:Open 价格用 DoubleToString(rates[0].open, Digits()) 输出,Digits() 跟随当前品种报价精度(如 XAUUSD 通常返回 2,部分券商返回 3),复制代码到 EURUSD 上会自然变成 5 位小数。外汇与贵金属杠杆高,面板显示仅作辅助,下单前仍须独立核对。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

「把OBV和K线高低收写进面板单元格」

在自建表格面板里填数,最容易被忽略的是坐标偏移量。上面这段把 High/Low/Close 三个值分别塞进第 0 列的第 3、4、5 行,每个文本都加了 +2 像素的偏移,避免和单元格边框贴死;外汇和贵金属报价小数位随品种变,用 Digits() 动态取位才不会出现 EURUSD 显示 5 位、XAUUSD 显示 2 位的错位。 指标行走的是另一套取法。handle_obv 对应的 OBV 数值通过 indicators.GetData 按 bar 索引抽出来,若返回 EMPTY_VALUE 就留空格,否则用 indicators.Digits(handle_obv) 决定显示精度;同一行下面还调了 BufferLineState 拿线态描述,写进相邻单元格。 收尾一句 ChartRedraw(ChartID()) 不是装饰。MT5 里面板绘制是离屏缓冲,不主动重绘的话你改完数据图表不会立刻刷新,盯盘时容易看到滞后一两根 K 的残影;外汇贵金属波动快,这种延迟可能让你误判进场点,属高风险场景下的低级坑。

MQL5 / C++
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_obv), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_obv,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_obv)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_obv,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
  }

在主图叠两组抛物线箭头

想在 MT5 主图同时盯两个周期的 Parabolic SAR,又不想切周期来回跳,可以直接写一个小指标把两组点都画出来。下面这段声明把缓冲区设成 2 个、绘图也设成 2 个,类型都是 DRAW_ARROW,意味着你会在 K 线上看到两种不同颜色的箭头。 #property indicator_buffers 2 与 #property indicator_plots 2 是硬前提;少一个,第二个 SAR 就不会渲染。PSAR1 用 clrDodgerBlue、PSAR2 用 clrRed,箭头代号分别填 158 和 159——这两个是 Wingdings 字体里的实心符号,改错数字主图就只显示乱码或空白。 输入参数里 InpSymbol 留 NULL 表示跟当前图表品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期;真要跨周期比对,把这两个值改掉即可。InpAsSeries=true 让缓冲区数组按时间序列排列,和 MT5 内置指标习惯一致,CopyBuffer 取数时索引方向不会反。 别把箭头代号当装饰 158/159 只是默认建议值,你换成 242 之类也能画,但务必确认客户端字体支持该编码,否则多周期 SAR 信号会静默消失,看起来像‘指标失效’。外汇与贵金属杠杆高,这类叠加仅作多周期共振参考,入场仍须自担风险。

MQL5 / C++
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot PSAR1
class="macro">#class="kw">property indicator_label1   "PSAR1"
class="macro">#class="kw">property indicator_type1   DRAW_ARROW
class="macro">#class="kw">property indicator_color1   clrDodgerBlue
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot MA2
class="macro">#class="kw">property indicator_label2   "PSAR2"
class="macro">#class="kw">property indicator_type2   DRAW_ARROW
class="macro">#class="kw">property indicator_color2   clrRed
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string              InpSymbol       =  NULL;             class=class="str">"cmt">/* Symbol                     */ class=class="str">"cmt">// Moving Average symbol
input ENUM_TIMEFRAMES    InpTimeframe    =  PERIOD_CURRENT;   class=class="str">"cmt">/* Timeframe                  */ class=class="str">"cmt">// Moving Average timeframe
input class="type">uchar              InpArrowCode1   =  class="num">158;              class=class="str">"cmt">/* SAR Senior period Arrow Code  */ class=class="str">"cmt">// Arrow code for Parabolic SAR class="num">1
input class="type">uchar              InpArrowCode2   =  class="num">159;              class=class="str">"cmt">/* SAR Junior period Arrow Code  */ class=class="str">"cmt">// Arrow code for Parabolic SAR class="num">2
input class="type">bool               InpAsSeries     =  true;             class=class="str">"cmt">/* As Series flag              */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double   BufferPSAR1[];
class="type">class="kw">double   BufferPSAR2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_psar1;
class="type">int handle_psar2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel

◍ 在 OnInit 里把双周期面板钉死

要在 MT5 里同屏比对当前周期与更高周期的反转点,初始化阶段就得把两条 PSAR 缓冲和面板对象一次性挂好。下面这段初始化逻辑把定时器设为 1 秒间隔,并给 0、1 号绘图缓冲分别绑了 BufferPSAR1 / BufferPSAR2 两个数组,后续只管往里写值。 箭头符号按周期大小做了交换:当输入周期大于当前图表周期时,code1 用 InpArrowCode1 画高位周期,否则两者对调,避免小周期箭头盖住大周期信号。PlotIndexSetInteger 的 PLOT_ARROW 参数直接吃这个 uchar 码,Wingdings 字体下就能出箭头。 面板本身用 CDashboard 在坐标 (20,20) 起了个宽 290、高 264 的悬浮窗,字体锁 Calibri 9 号,表头打印「Symbol, 周期名」便于一眼分清。内部建了 ID 为 0 的二维表,2 列 20 行、单元格宽约 143 像素,用来实时塞 K 线数据——外汇与贵金属波动剧烈,这类面板仅作辅助参考,实盘仍以小周期确认信号为主,杠杆品种风险偏高。

MQL5 / C++
CDashboard *panel=NULL;                      class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferPSAR1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferPSAR2,INDICATOR_DATA);
class=class="str">"cmt">//--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW
class=class="str">"cmt">//--- Higher period is drawn with the first arrow code, the lower one with the second
   class="type">uchar code1=InpArrowCode1;
   class="type">uchar code2=InpArrowCode2;
   if(InpTimeframe>Period())
     {
      code1=InpArrowCode1;
      code2=InpArrowCode2;
     }
   else
     {
      code1=InpArrowCode2;
      code2=InpArrowCode1;
     }
   PlotIndexSetInteger(class="num">0,PLOT_ARROW,code1);
   PlotIndexSetInteger(class="num">1,PLOT_ARROW,code2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferPSAR1,InpAsSeries);
   ArraySetAsSeries(BufferPSAR2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_psar1=indicators.AddNewSAR(NULL,PERIOD_CURRENT);
   handle_psar2=indicators.AddNewSAR(InpSymbol,InpTimeframe);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_psar1==INVALID_HANDLE || handle_psar2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_psar1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_psar2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">290;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
   panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);

「面板表格堆叠与指标生命周期钩子」

在 MT5 自定义指标里做多指标同屏面板,表格坐标不能写死。上面这段初始化逻辑用 TableY2(0)+22 拿第一个表的下边界,再偏移 22 像素给 ID 1 表定 Y1;ID 2 表则取 TableY2(1)+3,只留 3 像素缝隙。这种链式坐标取法,改了表 0 的行高也不用手动重排后面所有表。 DrawGrid 的调用参数值得记一下:ID 1 表用 DrawGrid(1,2,y1,2,2,18,width/2-2),即 2 列、距顶 y1、2 行 2 列网格、单元格高 18、宽取面板半宽减 2。ID 2 表列数变成 3(DrawGrid 第 4 参),其余布局一致,说明第二指标字段更多。 OnDeinit 里先 EventKillTimer 清定时器,再判 panel!=NULL 才 delete,最后 Comment("") 抹掉残留文字。漏掉 Comment 清空,图表左上角可能卡着上一次的指标注释。外汇与贵金属行情跳空频繁,这类面板只做展示、不构成任何方向暗示,实盘前请在策略测试器跑一遍确认资源释放无报错。 OnCalculate 的 limit=rates_total-prev_calculated 是标准增量计算切口;当 limit>1 说明首次计算或历史重载,此时强制 limit=rates_total-1 把全部柱重算一遍。这个分支若不加,历史回溯时新表数据可能错位。

MQL5 / C++
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
  class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
  if(limit>class="num">1)
   {
    class=class="str">"cmt">//--- specify all the available history for calculation
    limit=rates_total-class="num">1;
    /*

把多周期指标塞进缓冲区并接住鼠标事件

这段逻辑处在自定义指标的主计算函数收尾段:先调用 indicators.Calculate() 把所有多品种多周期指标算完,任一返回失败就直接 return 0 中断,避免脏数据写进图表。紧跟着用 DataToBuffer 把两个 PSAR 句柄(handle_psar1 / handle_psar2)的输出分别灌入 BufferPSAR1、BufferPSAR2,参数里的 0,0,limit 表示取指标第 0 列、从 0 偏移、写到 limit 根 K 线为止。 OnTimer 只做一件事:转发定时器事件给 indicators.OnTimer(),让跨周期指标集合自己维护刷新节奏,主程序不掺和具体重算细节。 鼠标交互在 OnChartEvent 里处理:当 id 是 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK,先用 ChartXYToTimePrice 把光标像素坐标转成时间,再用 iBarShift(Symbol(),PERIOD_CURRENT,time) 反查光标所在 bar 的索引,写进全局变量 mouse_bar_index,随后 DrawData 把那根 bar 的面板数据重画。外汇与贵金属市场波动剧烈、杠杆风险高,这类光标联动面板仅作辅助观察,信号本身不预示方向。 想验证就开 MT5 新建指标,把这段贴进 OnCalculate 尾部与事件函数,挂 EURUSD 的 M15,移动鼠标看面板是否跟随刷新;若 BufferPSAR 不画线,优先查 handle_psar1 是否在 OnInit 正确创建。

MQL5 / C++
if(!indicators.Calculate())
   class="kw">return class="num">0;
DrawData(mouse_bar_index,time[mouse_bar_index]);
if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_psar1,class="num">0,class="num">0,limit,BufferPSAR1))
   class="kw">return class="num">0;
if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_psar2,class="num">0,class="num">0,limit,BufferPSAR2))
   class="kw">return class="num">0;
class="kw">return(rates_total);

class="type">void OnTimer()
  {
   indicators.OnTimer();
  }

class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
   panel.OnChartEvent(id,lparam,dparam,sparam);
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         DrawData(mouse_bar_index,time);
        }
     }
   if(id>CHARTEVENT_CUSTOM)
     {

◍ 把指定K线数据画进面板表格

在 MT5 自定义面板里想实时看某根 K线的开收高低,关键在 DrawData() 这个函数。它接收 index 和 time 两个参数,用 CopyRates() 按索引抽取单根 MqlRates 结构,抽不到就直接 return,避免空指针把 EA 搞崩。 字体先设成 9 号加粗写表头,再切回 9 号常规写数据,颜色上用 clrMaroon 标 K线区、clrGreen 标指标区,视觉上能一眼区分。表格坐标靠 panel.CellX/CellY 算,文本旋转 90 度竖排,窄列也能塞下 Date/Time/Open 这些字段。 下面这段是原文核心片段,注意 CopyRates 第三个参数是索引不是时间,最后一参 rates 数组长度写 1 即可: //--- Declare the variables to receive data in them MqlRates rates[1]; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); panel.SetFontParams(name,9); panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); 外汇和贵金属行情跳空频繁,CopyRates 在跨周末或重大数据发布后可能返回少于请求根数,实盘里建议对返回值做 >=1 的判断而非严格 ==1,否则面板可能偶发不刷新。

MQL5 / C++
class=class="str">"cmt">//--- Declare the variables to receive data in them
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
   class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
 class="type">int   size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
 panel.SetFontParams(name,class="num">9);
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
 panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
 panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),          panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与双PSAR塞进表格面板」

在自定义面板里实时铺开当前K线的高低收,以及两个不同参数PSAR的数值与线态,是盯盘时快速比对反转信号的做法。下面这段直接往 panel 的单元格写文字:第0表放 High/Low/Close,第1、2表放两个 PSAR 指标标题与取值。 价格行用 rates[0] 取实时柱,Digits() 控制小数位,避免黄金报 1900.50 这类五位数被截成整数。指标行则走 indicators.GetData / GetDataTo,遇到 EMPTY_VALUE 就留空格,防止旧柱无值时报错刷屏。 线态描述靠 BufferLineState 拿枚举再转文字,能直接看出 PSAR 是在柱上还是柱下。外汇和贵金属杠杆高,PSAR 翻转只代表概率上的趋势切换,真要下单还得结合结构位。 把 index 设成 0 看最新柱,设成 1 看前一根,能在回放时核对翻转点是否滞后。开 MT5 把这段贴进面板类,调 CellX/Y 的行列参数就能挪动布局。

MQL5 / C++
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_psar1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_psar1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_psar1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_psar1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_psar2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_psar2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_psar2)) : " ");

在面板上画出双指标线与相对位置

这段逻辑把两个指标句柄(如双 PSAR)的当前状态刷到自定义面板的第 2 列网格里。CellX(2,0,1) 这类调用定位到「列2、行0、子格1」,+2 是文字左边距偏移,避免贴边;DrawText 最后传的 110 是字体大小代号,不是像素值。 相对位置判定靠 BufferLineStateRelative:它拿指标1在 index 处的 value2,对比指标2在 index+1 处的 value21,返回上方/下方/下穿/上穿四种枚举。下穿写成 "Top-down crossing",上穿是 "Bottom-up crossing",字符串直接进面板,肉眼扫一眼就知道谁压谁。 每次写完文本调 ChartRedraw(ChartID()) 强制重绘,否则 MT5 可能等下一个帧周期才刷新,盯盘时会觉得面板「慢半拍」。外汇与贵金属波动剧烈,这类叠加显示只辅助读图,信号失效概率不低,实盘前请在策略测试器跑过再信。

MQL5 / C++
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_psar2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_psar2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_psar1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_psar1);
  class="type">class="kw">string ma2=indicators.Name(handle_psar2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFRelativeStrengthIndex.mq5 |
class=class="str">"cmt">//|                     Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                          [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2

◍ 双周期RSI指标的声明与输入配置

在 MT5 里做多周期 RSI 对比,第一步是把指标_plot 数量钉死为 2,否则第二个缓冲数组画不出来。上面这段声明里 #property indicator_plots 2 就是干这个的,RSI1 用海绿色、RSI2 用道奇蓝,都是实线、默认宽度 1。 输入参数里 InpPeriod 默认 14,这是标准 RSI 周期;InpLineWidth1 设 2、InpLineWidth2 设 1,意味着大周期线更粗,小周期线更细,肉眼区分快。 InpAsSeries=true 让缓冲数组按时间序列排列,索引 0 是最新一根 K 线,回测或实时读取时不用反向数下标。 两个 double 缓冲 BufferRSI1[]BufferRSI2[] 承接计算结果,handle_rsi1 / handle_rsi2 是两个 RSI 句柄,真正的跨周期抓取靠 CMSTFIndicators indicators; 这个集合对象去调度。 开 MT5 把这段贴进自定义指标头部,先编译看两色线是否同图出现;若只显示一条,八成是 indicator_plots 没写 2 或被后面代码覆盖。外汇与贵金属波动剧烈,多周期共振也只是概率倾向,实盘前务必在模拟盘验证。

MQL5 / C++
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot RSI1
class="macro">#class="kw">property indicator_label1   "RSI1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot RSI2
class="macro">#class="kw">property indicator_label2   "RSI2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string                InpSymbol     =  NULL;                class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES      InpTimeframe  =  PERIOD_CURRENT;      class=class="str">"cmt">// Timeframe
input class="type">int                  InpPeriod     =  class="num">14;                  class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_PRICE   InpPrice      =  PRICE_CLOSE;
input class="type">uchar                InpLineWidth1 =  class="num">2;                   class=class="str">"cmt">// Line width for higher period
input class="type">uchar                InpLineWidth2 =  class="num">1;                   class=class="str">"cmt">// Line width for lower period
input class="type">bool                 InpAsSeries   =  true;                class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferRSI1[];
class="type">class="kw">double    BufferRSI2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_rsi1;
class="type">int handle_rsi2;
CMSTFIndicators indicators;     class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;         class=class="str">"cmt">// Pointer to the panel object

「初始化里把双周期RSI和面板搭起来」

这段 OnInit 做的是把两个 RSI 实例和一个悬浮面板在指标加载时一次性建好。Timer 设成 1 秒触发一次,用来后面轮询鼠标所在 K 线;BufferRSI1/2 分别绑到绘图缓冲 0 和 1,线宽按当前周期与设定周期大小关系对调,避免大周期线被小周期线盖住。 两个 RSI 句柄一个算当前图表的 PERIOD_CURRENT,另一个算外部 InpSymbol/InpTimeframe,任一句柄为 INVALID_HANDLE 就直接 INIT_FAILED 退出。面板宽 231、高 264,表头写当前 Symbol 加周期枚举去前缀的字符串,建两张表:ID0 放 K 线数据(2列20行格宽取 width/2-2),ID1 留给指标1——Y 坐标接着 ID0 底部往下排。 开 MT5 把 InpTimeframe 改成比当前周期更大的值,能看到 w1/w2 对调后粗线从主图周期挪到了外周期线上,这是验证初始化分支是否生效的最快办法。外汇与贵金属波动剧烈,此类多周期叠加仅作概率参考,实盘须自担高风险。

MQL5 / C++
class="type">int         mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferRSI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferRSI2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferRSI1,InpAsSeries);
   ArraySetAsSeries(BufferRSI2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_rsi1=indicators.AddNewRSI(NULL,PERIOD_CURRENT,InpPeriod,InpPrice);
   handle_rsi2=indicators.AddNewRSI(InpSymbol,InpTimeframe,InpPeriod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_rsi1==INVALID_HANDLE || handle_rsi2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_rsi1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_rsi2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">231;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
   panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
   panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1

面板表格堆叠与指标生命周期衔接

在 MT5 自定义指标里,把多张数据表叠到同一个面板背景上,关键是算准每张表的 Y 坐标起点。第一段代码先取 ID 0 表的底部坐标 TableY2(0) 再加 22 像素作为 ID 1 表的顶边,接着 DrawGrid(1,2,y1,2,2,18,width/2-2) 把这张 2 列、行高 18、宽度取面板一半减 2 的表画出来;随后用 TableY2(1)+3 得到 ID 2 表的起点 y2,间距只留 3 像素,说明表与表之间可以贴得很紧。 初始化收尾处把 mouse_bar_index 置 0,并调用 DrawData(0, TimeCurrent()) 把当前 Bar 的数据刷到面板上,最后 return(INIT_SUCCEEDED)。这一段决定了指标加载后第一眼看到的是哪根 K 线的数值。 OnDeinit 里先 EventKillTimer() 撤掉定时器,再判空后 delete panel 释放面板对象,并用 Comment("") 清掉残留文字。外汇与贵金属行情跳空频繁,面板对象若没在反初始化时删干净,切换周期可能留下僵尸图形,建议每次改代码都手动切一次周期验证。 OnCalculate 的 limit 计算有个细节:当 limit>1 时说明是首次计算或历史变更,此时强制 limit=rates_total-1 重算全部可用历史,避免只增量算几根 Bar 导致多指标缓冲错位。这个分支是面板类指标稳定性的命门。

MQL5 / C++
class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }

◍ 把光标所在 K 线喂给多周期指标

这段逻辑承接前面指标集合的初始化,核心是在每次报价刷新时先让多符号多周期指标统一重算,再把光标(或当前 BAR)下的数据画到面板,最后把两条 RSI 缓冲写回指标缓冲区。 //--- Calculate all created multi-symbol multi-period indicators if(!indicators.Calculate()) return 0; 上面这行是总闸:只要任意子指标计算失败就直接退出,返回 0 表示本根 K 线不输出。实盘里若某品种休市或点差异常导致句柄失效,整个仪表盘会静默停更,需要自己在 Journal 里查 handle 状态。 //--- Display the bar data under cursor DrawData(mouse_bar_index,time[mouse_bar_index]); 光标在图内就用 mouse_bar_index 对应的时间,光标移出图表则自动退回到当前 BAR,面板始终有数据可看。 //--- From buffers of calculated indicators, output data to indicator buffers if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rsi1,0,0,limit,BufferRSI1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rsi2,0,0,limit,BufferRSI2)) return 0; 两个 RSI 缓冲都从当前周期句柄拷贝,参数 0,0 表示从源缓冲第 0 行、写到目标缓冲第 0 行,limit 控制拷贝根数。若你加了第三条均线,这里要再补一行 DataToBuffer,否则图上看不到。 OnTimer 只做一件事:indicators.OnTimer(),把定时刷新委托给指标集合自己管;ChartEvent 里拦截鼠标移动和点击事件,用 ChartXYToTimePrice 把像素坐标转成时间,再用 iBarShift 反查 BAR 索引写进全局变量 mouse_bar_index。外汇与贵金属波动大、跳空频繁,光标对应 BAR 在极端行情可能偏移 1~2 根,验证时建议对照时间轴人工核一下。

MQL5 / C++
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
  if(!indicators.Calculate())
     class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
  DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rsi1,class="num">0,class="num">0,limit,BufferRSI1))
     class="kw">return class="num">0;
  if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rsi2,class="num">0,class="num">0,limit,BufferRSI2))
     class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
    {
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
          mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
class=class="str">"cmt">//--- Display the bar data under the cursor on the panel
          DrawData(mouse_bar_index,time);
        }
    }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
  if(id>CHARTEVENT_CUSTOM)
    {
class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel

「把指定K线数据搬进自定义面板」

在 MT5 面板类里,DrawData 函数负责把某一根 K 线的行情和指标信息画到界面上。它接收一个 bar 索引 index 和对应的 time,先尝试用 CopyRates 取数,若返回值不等于 1 就直接 return,避免空数据导致后续数组越界。 取数成功后,代码先通过 panel.FontParams 拿到当前字体名,再把表头设为 9 号加粗(FW_BOLD),分别用 clrMaroon 和 clrGreen 标出「Bar data[i]」与「Indicators data[i]」两栏标题,偏移量写死为 TableY1(0)-16,也就是表头上方 16 像素。 正文数据改用 9 号常规字,逐行调用 DrawText 把 Date、Time、Open 等字段填进 CellX/CellY 算出的格子里。注意 TimeToString 用了 TIME_DATE 和 TIME_MINUTES 两种格式,Open 价格用 DoubleToString(rate.open, Digits()) 保留当前品种小数位——EURUSD 通常是 5 位,XAUUSD 常见 2 位,Digits() 会自动适配。 复制这段逻辑时,重点验证 CellX/CellY 的行列参数是否和你的面板布局一致;若格子错位,先打 Print 看 TableY1 返回的像素值,再调那个 -16 的硬偏移。外汇和贵金属波动剧烈,面板只做辅助观察,不构成任何方向建议。

MQL5 / C++
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
   panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);

把K线与双RSI塞进面板表格

在自绘面板里填行情数据,核心是先定位单元格再写字。下面这段把当前柱的 High / Low / Close 分别写进第 0 号表的三行,列偏移都加了 2 像素避免贴边;价格精度用 Digits() 动态取,黄金 XAUUSD 通常是 2 位、主流外汇对多为 4~5 位。

MQL5 / C++
panel.DrawText("High", panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2);
panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()), panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Low", panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2);
panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()), panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2);
panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()), panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
双 RSI 的呈现要分两张表处理。表 1 取 handle_rsi1 在 index 柱的值,若返回 EMPTY_VALUE 就留空格;标题用 indicators.Title() 自动读,不用手写「RSI(14)」这类写死字符串。
MQL5 / C++
panel.DrawText(indicators.Title(handle_rsi1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value1=indicators.GetData(handle_rsi1,class="num">0,class="num">0,index);
class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_rsi1)) : " ");
panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);
线状态别漏了。用 BufferLineState 拿 ENUM_LINE_STATE 再经 BufferLineStateDescription 转成可读文字,能直接看出当前 RSI 线是正常、无效还是越界——实盘里若状态非「正常」却还在读数值,可能倾向得到失真信号。
MQL5 / C++
panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_rsi1,class="num">0,class="num">0,index);
panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);
第二个 RSI 走的是 GetDataTo 显式带 Symbol/Period 的重载,适合跨周期比对。下面只截到取值段,完整渲染同表 1 套路:
MQL5 / C++
panel.DrawText(indicators.Title(handle_rsi2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_rsi2,class="num">0,class="num">0,index);
class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_rsi2)) : " ");
外汇与贵金属波动剧烈、杠杆风险高,面板只是读数辅助,数值异常时先查 Line state 再决策。

MQL5 / C++
panel.DrawText("High", panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Low",  panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),      panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),    panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
panel.DrawText(indicators.Title(handle_rsi1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value1=indicators.GetData(handle_rsi1,class="num">0,class="num">0,index);
class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_rsi1)) : " ");
panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_rsi1,class="num">0,class="num">0,index);
panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
panel.DrawText(indicators.Title(handle_rsi2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_rsi2,class="num">0,class="num">0,index);
class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_rsi2)) : " ");

◍ 双线状态与相对位置的面板绘制

在自定义面板里把两条指标线的实时状态铺出来,核心是用 CellX/CellY 定位到表格单元格,再调 DrawText 把字符串写进去。下面这段把第二根线的数值和状态描述分别画在第 2 行第 1 列的两个子格里,偏移 +2 像素避免贴边,文字宽度上限设成 190 像素防止溢出。 判定两根线的上下关系不靠肉眼,而是用 BufferLineStateRelative 拿当前柱与下一柱的采样值做比较。stateR 枚举会返回 ABOVE、BELOW、CROSS_DOWN、CROSS_UP 等结果,代码里用三元表达式拼成 "RSI1 > RSI2" 或 "Top-down crossing" 这类直白文案,交易者在副图面板一眼就能读出穿越方向。 所有文本写完必须跟一句 ChartRedraw(ChartID()),否则 MT5 不会立即刷新面板,你看到的可能滞后一个 tick。外汇与贵金属市场跳空频繁,这种相对状态在重大数据发布前后可能瞬间翻转,仅作概率参考,实际下单仍需结合风控。 下方代码节选自指标测试脚本的绘制收尾段与属性声明,可直接拷进 MT5 的 MQ5 文件对照改参数验证。

MQL5 / C++
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_rsi2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_rsi2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_rsi1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_rsi1);
  class="type">class="kw">string ma2=indicators.Name(handle_rsi2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFRelativeVigorIndex.mq5 |
class=class="str">"cmt">//|            Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4

「多周期RVI指标的绘图与输入声明」

在 MT5 自定义指标里,用 #property indicator_plots 4 一次性挂出 4 条线,意味着这个脚本要把两个周期的相对活力指数(RVI)及其信号线叠在同一窗口。前两条 RVI1 / Signal1 用 SeaGreen 与 OrangeRed 区分,后两条 RVI2 / Signal2 用 Green 与 Red,线宽都先设为 1,靠后面 input 里的 InpLineWidth1=2、InpLineWidth2=1 再覆盖大周期与小周期的视觉粗细。 代码里通过 #include <IndMSTF\IndMSTF.mqh>#include <Dashboard\Dashboard.mqh> 把多周期框架与面板绘制逻辑外置,主文件只管参数和画图属性。InpPeriod 默认 10,是 RVI 的计算窗口;InpSymbol 留 NULL 表示抓当前图表品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期。 直接把这段声明贴进 MQ5 文件头,编译后会在导航器里看到 4 条预设好颜色的线。外汇与贵金属波动大,多周期共振只提高概率,实盘前务必在策略测试器里用历史数据验证信号延迟。

MQL5 / C++
class="macro">#class="kw">property indicator_plots   class="num">4
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot RVI1
class="macro">#class="kw">property indicator_label1   "RVI1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrSeaGreen
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot Signal1
class="macro">#class="kw">property indicator_label2   "Signal1"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrOrangeRed
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- plot RVI2
class="macro">#class="kw">property indicator_label3   "RVI2"
class="macro">#class="kw">property indicator_type3    DRAW_LINE
class="macro">#class="kw">property indicator_color3   clrGreen
class="macro">#class="kw">property indicator_style3   STYLE_SOLID
class="macro">#class="kw">property indicator_width3   class="num">1
class=class="str">"cmt">//--- plot Signal2
class="macro">#class="kw">property indicator_label4   "Signal2"
class="macro">#class="kw">property indicator_type4    DRAW_LINE
class="macro">#class="kw">property indicator_color4   clrRed
class="macro">#class="kw">property indicator_style4   STYLE_SOLID
class="macro">#class="kw">property indicator_width4   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string                InpSymbol      =  NULL;                 class=class="str">"cmt">/* Symbol                    */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES       InpTimeframe   =  PERIOD_CURRENT;       class=class="str">"cmt">/* Timeframe                 */ class=class="str">"cmt">// Timeframe
input class="type">int                   InpPeriod      =  class="num">10;                   class=class="str">"cmt">/* Period                    */ class=class="str">"cmt">// Calculation period
input class="type">uchar                 InpLineWidth1  =  class="num">2;                    class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar                 InpLineWidth2  =  class="num">1;                    class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period

双周期 RVI 缓冲区的初始化挂接

在 MT5 自定义指标里同时跑两个 RVI 实例,常见做法是分别绑定当前图表周期和设置里指定的外部周期。下面这段初始化代码就建了 4 个缓冲区:BufferRVI1 / BufferSig1 给当前周期用,BufferRVI2 / BufferSig2 留给外部周期,索引 0~3 一一对应。

MQL5 / C++
input class="type">bool InpAsSeries = true; class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class="type">class="kw">double BufferRVI1[];
class="type">class="kw">double BufferSig1[];
class="type">class="kw">double BufferRVI2[];
class="type">class="kw">double BufferSig2[];
class="type">int handle_rvi1;
class="type">int handle_rvi2;
CMSTFIndicators indicators;
CDashboard *panel=NULL;
class="type">int mouse_bar_index;
class="type">int OnInit()
  {
   EventSetTimer(class="num">1);
   SetIndexBuffer(class="num">0,BufferRVI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferSig1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferRVI2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferSig2,INDICATOR_DATA);
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
   ArraySetAsSeries(BufferRVI1,InpAsSeries);
   ArraySetAsSeries(BufferSig1,InpAsSeries);
   ArraySetAsSeries(BufferRVI2,InpAsSeries);
   ArraySetAsSeries(BufferSig2,InpAsSeries);
   handle_rvi1=indicators.AddNewRVI(NULL,PERIOD_CURRENT,InpPeriod);
   handle_rvi2=indicators.AddNewRVI(InpSymbol,InpTimeframe,InpPeriod);
   if(handle_rvi1==INVALID_HANDLE || handle_rvi2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_rvi1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_rvi1,class="num">1);
逐行拆解:第 1 行 input bool InpAsSeries = true 暴露了一个测试开关,用来决定缓冲区是否按时间序列(最新 K 线在索引 0)排列;EventSetTimer(1) 设了 1 秒心跳,供面板刷新。SetIndexBuffer 把 4 个数组挂到绘图缓冲 0~3,这一步漏了就会画不出线。 线宽逻辑值得注意:当外部周期大于当前图表周期时,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则对调。也就是说大周期线宽参数被刻意错位分配,实盘里若发现双线粗细跟设置反了,先查这段 if 分支。 ArraySetAsSeries(..., InpAsSeries) 对四个缓冲统一生效,注释里明说“为了测试看有无差异”。你在 MT5 里把 InpAsSeries 切 false 重编译,可能观察到索引访问习惯带来的数值错位,外汇与贵金属属高风险品种,验证时请用模拟盘。 最后两个 AddNewRVI 分别吃 PERIOD_CURRENTInpSymbol/InpTimeframe,任一返回 INVALID_HANDLE 就 INIT_FAILED 中断加载;SetPlotLabelFromBuffer 把底层指标缓冲的描述抄到主图线标签,省得手动写名字。

MQL5 / C++
input class="type">bool                InpAsSeries = true;    class=class="str">"cmt">/* As Series flag        */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferRVI1[];
class="type">class="kw">double    BufferSig1[];
class="type">class="kw">double    BufferRVI2[];
class="type">class="kw">double    BufferSig2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_rvi1;
class="type">int handle_rvi2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;    class=class="str">"cmt">// Pointer to the panel object
class="type">int      mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferRVI1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferSig1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferRVI2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferSig2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferRVI1,InpAsSeries);
   ArraySetAsSeries(BufferSig1,InpAsSeries);
   ArraySetAsSeries(BufferRVI2,InpAsSeries);
   ArraySetAsSeries(BufferSig2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_rvi1=indicators.AddNewRVI(NULL,PERIOD_CURRENT,InpPeriod);
   handle_rvi2=indicators.AddNewRVI(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_rvi1==INVALID_HANDLE || handle_rvi2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_rvi1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_rvi1,class="num">1);

◍ 面板与表格的初始化落点

这段初始化逻辑把 RVI 双缓冲绑到绘图标签后,直接拉起一个 231×264 像素的悬浮面板,左上角锚点在 (20,20)。面板字体写死 Calibri 9 号,表头用 Symbol()+周期枚举截取,比如 EURUSD 搭配 PERIOD_H1 会显示成「EURUSD, H1」。 面板里叠了三张表:ID 0 放 K 线数据,ID 1 放指标 1,ID 2 放指标 2。后两张表的 Y 坐标不写死,而是用前一张表的 TableY2() 返回值加偏移算出来——ID 1 的 y1 = TableY2(0)+22,ID 2 的 y2 = TableY2(1)+3,这样改了表 0 高度不会盖住下面的内容。 OnDeinit 里只做三件事:杀定时器、delete 面板对象、清 Comment。注意面板指针若不为 NULL 才 delete,避免重复释放。初始化末尾把 mouse_bar_index 置 0,调一次 DrawData 把当前 bar 数据刷上屏,返回 INIT_SUCCEEDED 即算加载完成。 开 MT5 把这段抄进指标 OnInit,若面板没出来先查 panel==NULL 的报错分支;外汇与贵金属波动剧烈,这类面板只做辅助观测,信号误判可能导致较大回撤。

MQL5 / C++
  indicators.SetPlotLabelFromBuffer(class="num">2,handle_rvi2,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">3,handle_rvi2,class="num">1);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">231;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,

「把多周期RVI塞进自定义指标缓冲区」

这段 OnCalculate 的形参列表是 MT5 自定义指标的标准入口:time、open、high、low、close 五个价格序列,加上 tick_volume、volume、spread 各一维,全部用 const 引用传数组,避免拷贝开销。 计算量由 limit = rates_total - prev_calculated 决定。若 limit>1 说明是首算或历史被修改,此时把 limit 拉到 rates_total-1,相当于重算全部可见 K 线,这个分支在切换周期或补历史时一定会触发。 核心输出靠 indicators.DataToBuffer 把两个 RVI 句柄的主线(索引0)与信号线(索引1)分别写进 BufferRVI1/BufferSig1 和 BufferRVI2/BufferSig2。任意一次返回 false 就直接 return 0,防止半截数据画到图上。 OnTimer 里只转调 indicators.OnTimer(),把多符号多周期指标的定时刷新收口到一处;鼠标所在 K 线的数据则由 DrawData(mouse_bar_index, time[mouse_bar_index]) 推到面板,光标移出图表时自动退回到当前 Bar。外汇与贵金属杠杆高,这类多缓冲指标仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证缓冲同步逻辑。

MQL5 / C++
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">//--- Number of bars for calculation
 class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
 if(limit>class="num">1)
  {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
 if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
 DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rvi1,class="num">0,class="num">0,limit,BufferRVI1))
   class="kw">return class="num">0;
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rvi1,class="num">1,class="num">0,limit,BufferSig1))
   class="kw">return class="num">0;
   
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rvi2,class="num">0,class="num">0,limit,BufferRVI2))
   class="kw">return class="num">0;
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_rvi2,class="num">1,class="num">0,limit,BufferSig2))
   class="kw">return class="num">0;
   
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                     | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
class=class="str">"cmt">//--- Call the indicator collection timer
 indicators.OnTimer();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,

把鼠标位置换算成可见的K线数据

在 MT5 的 OnChartEvent 里,鼠标移动和点击是最常被忽略的实时信号源。下面这段处理把光标坐标转成时间价格,再反查当前图表上对应的 K 线索引,逻辑直接可抄。

MQL5 / C++
const class="type">long &lparam,
const class="type">class="kw">double &dparam,
const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
       class="type">class="kw">datetime time=class="num">0;
       class="type">class="kw">double price=class="num">0;
       class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
       if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
         {
class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
           mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
           DrawData(mouse_bar_index,time);
         }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
       PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
逐行看:lparam/dparam 是图表事件的像素坐标,panel.OnChartEvent 先让面板自身消化交互;当 id 是鼠标移动或点击时,ChartXYToTimePrice 用 ChartID() 把像素转成 time 和 price,wnd 接收子窗口号。转换成功后 iBarShift(Symbol(),PERIOD_CURRENT,time) 给出光标所在 K 线索引,存进全局变量 mouse_bar_index,再调 DrawData 把那根 bar 的信息画到面板。 自定义事件(id>CHARTEVENT_CUSTOM)这边只做 PrintFormat 日志,实际可接面板关闭按钮等控件回调。外汇和贵金属市场跳空频繁,iBarShift 在跳空处可能返回最近一根可对齐 bar,验证时建议用 EURUSD 的亚洲时段细看索引偏移。 DrawData 负责把索引对应的裸数据搬上屏。CopyRates 只取 1 根(index,1),失败就 return;字体先用 FW_BOLD 的 9 号写表头,再切回常规 9 号写内容,坐标用 panel.TableY1(0)-16 这种相对定位,避免写死像素。

MQL5 / C++
const class="type">long &lparam,
const class="type">class="kw">double &dparam,
const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
       class="type">class="kw">datetime time=class="num">0;
       class="type">class="kw">double price=class="num">0;
       class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
       if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
         {
class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
           mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
           DrawData(mouse_bar_index,time);
         }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
       PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

◍ 把当前K线与RVI读数写进面板表格

下面这段逻辑负责把实时行情和第一个指标的数值刷到自定义面板的表格里。它先填第 0 列(行情块):用 CellX/CellY 定位到 (0,行,0) 写字段名,再到 (0,行,1) 写对应数值,每行偏移 +2 像素避免贴边。 日期和时间拆成两行,分别用 TimeToString 的 TIME_DATE 与 TIME_MINUTES 格式;Open/High/Low/Close 走 DoubleToString,小数位由 Digits() 动态决定,EURUSD 这类通常 5 位、XAUUSD 通常 2 位。 指标部分只示范了 handle_rvi1:先取标题写进第 1 列首格,再用 indicators.GetData(handle_rvi1,0,0,index) 抓指定 bar 的数值。若返回 EMPTY_VALUE 就显示空格,否则按该指标自身 Digits 精度转字符串——这一步能避免 RVI 在无效周期显示怪异长小数。 外汇与贵金属波动剧烈、杠杆风险高,面板数值仅作辅助观察,不代表任何方向确定性。

MQL5 / C++
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_rvi1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_rvi1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_rvi1)) : " ");

「双指标面板里的相对位置判定」

在自建的 panel 表格里把两个 RVI 类指标同根 K 线的值并排画出来,核心不是显示数值,而是顺手把它们的线态和相对关系一起标出来。下面这段把指标1和指标2的标题、数值、BufferLineState 描述分别写进第1、2列,数值列统一用 190 像素宽、clrNONE 透明底,避免遮挡背景 K 线。 相对关系那一段值得细看:取指标2在 index+1 根的值 value21,再调 BufferLineStateRelative 比较指标1在 index 根与指标2在相邻两根的走势,返回枚举可能是 ABOVE、BELOW、CROSS_DOWN 或 CROSS_UP。若判定为下穿就显示 Top-down crossing,上穿则 Bottom-up crossing,外汇与贵金属波动快,这类交叉信号出现后价格倾向短周期反转,但高杠杆下误判概率不低,需结合别的条件过滤。 把关系字符串用 StringFormat("%s1 vs %s2", ma1, ma2) 写进 (2,2,0) 单元格,等于在面板上常驻一张「谁压谁」的速读表。开 MT5 把这段塞进你的 CAppDialog 派生面板,调一下 CellX/Y 的行列参数就能直接看两个指标实时上下位。

MQL5 / C++
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_rvi1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_rvi2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_rvi2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_rvi2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_rvi2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_rvi2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_rvi1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_rvi1);
  class="type">class="kw">string ma2=indicators.Name(handle_rvi2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE        ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW        ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);

把标准差画进独立窗口的双线指标

这段测试指标把多周期标准差拆成两条线放进副图,方便直接对比不同采样下的波动离散度。StdDev1 用 DodgerBlue、StdDev2 用 Red,都是 SOLID 风格、宽度 1,视觉上不抢主图。 编译后会在独立窗口出现两个 plot,buffer 数量设为 2,对应两个指标线。输入参数里 InpPeriod 默认 20,意味着标准差按 20 根 K 线滚动计算;InpSymbol 留 NULL 时取当前品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期。 外汇和贵金属波动受杠杆与消息面影响大,标准差扩大只说明离散度上升,不代表方向,实盘使用前请在 MT5 策略测试器里跑一遍验证周期匹配。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- plot StdDev1
class="macro">#class="kw">property indicator_label1  "StdDev1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrDodgerBlue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot StdDev2
class="macro">#class="kw">property indicator_label2  "StdDev2"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrRed
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
input class="type">class="kw">string            InpSymbol     =  NULL;            class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod     =  class="num">20;             class=class="str">"cmt">// Calculation period

◍ 双周期标准差指标的缓冲区与线宽初始化

这段初始化逻辑同时挂了两条标准差曲线:BufferStdDev1 和 BufferStdDev2 分别绑到 plot 0、1,类型都是 INDICATOR_DATA,意味着它们会直接画在子窗口里。 输入参数里 InpLineWidth1=2、InpLineWidth2=1,分别管大周期和小周期线宽;InpAsSeries=true 则让指标缓冲区按时间序列倒序排列,和 MT5 原生 K 线索引对齐。 线宽不是写死的。OnInit 里用 InpTimeframe 和当前 Period() 比大小:如果指标周期大于图表周期,plot0 反而用细线(w2)、plot1 用粗线(w1);否则反过来。这样切换周期时主次视觉不会乱。 EventSetTimer(1) 开了 1 秒定时器,后续面板刷新靠它驱动;mouse_bar_index 记录鼠标所在 bar 的索引,供悬浮读数用。外汇和贵金属波动剧烈,标准差倍数放大时假突破概率偏高,参数验证请在 MT5 策略测试器跑实盘 tick。

MQL5 / C++
input class="type">int                 InpShift      =  class="num">0;                class=class="str">"cmt">/* Shift                     */ class=class="str">"cmt">// Horizontal shift
input ENUM_MA_METHOD      InpMethod      =  MODE_SMA;            class=class="str">"cmt">/* Method                    */ class=class="str">"cmt">// Deviation
input ENUM_APPLIED_PRICE  InpPrice       =  PRICE_CLOSE;         class=class="str">"cmt">/* Applied Price             */ class=class="str">"cmt">// Price used for calculations
input class="type">uchar               InpLineWidth1  =  class="num">2;                   class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar               InpLineWidth2  =  class="num">1;                   class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries    =  true;                class=class="str">"cmt">/* As Series flag            */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferStdDev1[];
class="type">class="kw">double    BufferStdDev2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_stddev1;
class="type">int handle_stddev2;
CMSTFIndicators indicators;       class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;           class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;        class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferStdDev1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferStdDev2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift

「同周期与异周期标准差指标的面板初始化」

在指标 OnInit 阶段,先用 ArraySetAsSeries 按 InpAsSeries 开关决定缓冲区是否按时间序列排列,这样能在测试中直观确认两种排列下计算结果无差异。 随后创建两个同类型 StdDev 指标句柄:第一个绑定当前图表品种与 PERIOD_CURRENT,第二个走外部设定的 InpSymbol 与 InpTimeframe。若任一句柄返回 INVALID_HANDLE,直接 INIT_FAILED 退出,避免后续空指针读写。 面板部分新建 290 宽、264 高的 CDashboard,坐标 (20,20),字体 Calibri 9pt,表头显示 Symbol() 加 Period() 枚举去前 7 字符的时间帧描述。三张表 ID 分别为 0/1/2,其中表 1 的 Y1 取表 0 的 Y2 加 22,表 2 的 Y1 取表 1 的 Y2 加 3,形成纵向堆叠布局。 初始化收尾把 mouse_bar_index 置 0,调用 DrawData 刷出当前 Bar 数据,最后返回 INIT_SUCCEEDED。外汇与贵金属市场波动剧烈,这类多周期对照面板仅用于辅助观察,实盘信号仍须结合风控。

MQL5 / C++
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
  ArraySetAsSeries(BufferStdDev1,InpAsSeries);
  ArraySetAsSeries(BufferStdDev2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
  handle_stddev1=indicators.AddNewStdDev(NULL,PERIOD_CURRENT,InpPeriod,InpShift,InpMethod,InpPrice);
  handle_stddev2=indicators.AddNewStdDev(InpSymbol,InpTimeframe,InpPeriod,InpShift,InpMethod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
  if(handle_stddev1==INVALID_HANDLE || handle_stddev2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
  indicators.SetPlotLabelFromBuffer(class="num">0,handle_stddev1,class="num">0);
  indicators.SetPlotLabelFromBuffer(class="num">1,handle_stddev2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">290;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
     {
       Print("Error. Failed to create panel object");
       class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
}

指标卸载与逐根重算的底层钩子

MT5 自定义指标在图表被移除或品种切换时,会触发 OnDeinit。这里必须先 EventKillTimer() 杀掉定时器,否则后台计时器仍尝试访问已销毁的 panel 对象,大概率抛空指针错误并写满日志。 面板对象用 if(panel!=NULL) delete panel 做防御性释放,顺手 Comment("") 清掉屏幕左上角残留文字。外汇与贵金属行情跳动快,这种清理没做干净,下次加载同指标可能叠出重影标签。 OnCalculate 的 limit=rates_total-prev_calculated 是核心节流逻辑。当 limit>1 说明是首算或历史重灌,代码直接把 limit 拉到 rates_total-1 全量重算;常态下只算新增的 1~N 根,CPU 占用能压住。 indicators.Calculate() 失败就 return 0 中止,避免脏数据写进 BufferStdDev1/2。DrawData 把光标所在 bar 的跨周期标准差抛到仪表盘,方便你盯盘时直接比对多周期波动收敛。 下面这段是原文钩子函数骨架,定时器体被截断,但能看清入口职责: void OnDeinit(const int reason) { //--- Delete the timer EventKillTimer(); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; //--- Delete all comments Comment(""); } int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Number of bars for calculation int limit=rates_total-prev_calculated; //--- If limit > 1, then this is the first calculation or change in the history if(limit>1) { limit=rates_total-1; } if(!indicators.Calculate()) return 0; DrawData(mouse_bar_index,time[mouse_bar_index]); if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stddev1,0,0,limit,BufferStdDev1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stddev2,0,0,limit,BufferStdDev2)) return 0; return(rates_total); } void OnTimer() { //--- Call the indicator collection timer

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }

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">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      limit=rates_total-class="num">1;
     }
   if(!indicators.Calculate())
      class="kw">return class="num">0;
   DrawData(mouse_bar_index,time[mouse_bar_index]);
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stddev1,class="num">0,class="num">0,limit,BufferStdDev1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stddev2,class="num">0,class="num">0,limit,BufferStdDev2))
      class="kw">return class="num">0;
   class="kw">return(rates_total);
  }

class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer

◍ 鼠标悬停读出光标所在 K 线

在 EA 的 OnChartEvent 里捕获鼠标移动和点击事件,就能把光标下的 K 线数据实时投到自定义面板上。核心是先通过 ChartXYToTimePrice 把像素坐标转成时间,再用 iBarShift 反查当前周期的 bar 索引。 代码里用 mouse_bar_index 存索引,接着调 DrawData 画数据。DrawData 用 CopyRates 按索引取 1 根 bar 到 MqlRates 数组,取不到就直接 return,避免越界报错。 面板文字用 9 号粗体(FW_BOLD),表头写在距表格上沿 16 像素处,颜色 clrMaroon。你在 MT5 里挂上这段,移动鼠标就能看到每根 bar 的 open/high/low/close 实时刷新,对外汇和贵金属这种高波动品种,悬停比对裸 K 效率明显更高,但杠杆交易风险大,别把面板读数当入场信号。

MQL5 / C++
  indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                  const class="type">long &lparam,
                  const class="type">class="kw">double &dparam,
                  const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int wnd=class="num">0;
      class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
        {
         class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint  flags=class="num">0;
   class="type">uint  angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);

「在自定义面板上铺开当前K线裸数据」

把当前 Bar 的 OHLC 和时间直接画进面板表格,是盯盘面板最底层的活。下面这段代码用 panel 对象的 CellX/CellY 定位,把 rates[0] 的字段逐个写进第 0 号表的对应单元格,字体统一设成 9 号,肉眼扫一眼就能比对多周期。 注意 DrawText 的最后一个数值参数 90,那是文本区域宽度限制(像素),防止 EURUSD 这类 5 位小数在窄列里挤重叠;clrNONE 表示沿用背景色不做描边。若你接的是 XAUUSD,Digits() 返回 2,DoubleToString 会自动收成两位,不用手动改格式。 在 MT5 里跑这段前,先确认 rates 已通过 CopyRates 填了至少 1 根 Bar,否则 rates[0].time 是 0,TimeToString 会吐出 1970 年的日期,面板直接显示错乱。

MQL5 / C++
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1

双标准差指标的状态同屏输出

在自定义面板里把两个标准差指标的数值和线态并排画出来,核心是用 CellX/CellY 定位到表格单元再 DrawText。第一段代码取 handle_stddev1 在主图当前品种、当前周期、偏移 index 处的数值,空值用空格占位,有效值按指标自身小数位 Digits 转字符串。 线态不是只画数值,BufferLineState 能返回 ENUM_LINE_STATE,再用 BufferLineStateDescription 翻成可读文字写进“Line state”那格。第二个指标走的是 GetDataTo,显式传 Symbol() 与 PERIOD_CURRENT,和第一个指标的 GetData 内部取当前环境不同,调试时容易忽略这处接口差异。 两个指标谁上谁下用 BufferLineStateRelative 判断,它要吃指标2在 index 和 index+1 两棒的值(value2 与 value21),返回 LINE_STATE_ABOVE 或 LINE_STATE_BELOW 后拼成“StdDev1 > StdDev2”这类短句。外汇与贵金属波动大,这类相对位置仅描述统计关系,不预示方向,实盘须自担高风险。

MQL5 / C++
  panel.DrawText(indicators.Title(handle_stddev1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_stddev1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_stddev1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_stddev1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_stddev2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_stddev2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_stddev2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_stddev2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_stddev2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_stddev1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_stddev1);
  class="type">class="kw">string ma2=indicators.Name(handle_stddev2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)               :

◍ 面板文字与指标窗口的衔接写法

这段片段展示了在自定义面板里把两条均线的相对状态画出来的做法:当 stateR 等于 LINE_STATE_CROSS_DOWN 时显示「Top-down crossing」,等于 LINE_STATE_CROSS_UP 时显示「Bottom-up crossing」,其余情况交回 BufferLineStateDescription 函数处理。 随后用 panel.DrawText 在指定单元格坐标(如 CellX(2,2,0)+2)写入「%s1 vs %s2」的标题与状态文本,字体宽度参数取 110,颜色用 clrNONE 表示继承。 末尾 ChartRedraw(ChartID()) 强制重绘,保证面板改动在当根 K 线内就刷新,而不是等下一 tick。外汇与贵金属品种上这类重绘频率若过高,可能拖慢老机型 EA 的响应,实盘前建议在模拟盘测帧率。 下方紧接的 TestMSTFStochasticOdcillator.mq5 头里定义了独立子窗口、4 个 buffer 与 4 个 plot:RVI1 用 clrSeaGreen、Signal1 用 clrOrangeRed、RVI2 用 clrGreen、Signal2 用 clrRed,均为宽度 1 的实线。复制这段代码到 MT5 新建指标,能直接看到双 RVI 加信号线的四线分层结构。

MQL5 / C++
  stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
  stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
  BufferLineStateDescription(stateR)
  );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4
class="macro">#class="kw">property indicator_plots   class="num">4
class=class="str">"cmt">//--- plot RVI1
class="macro">#class="kw">property indicator_label1  "RVI1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrSeaGreen
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot Signal1
class="macro">#class="kw">property indicator_label2  "Signal1"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrOrangeRed
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- plot RVI2
class="macro">#class="kw">property indicator_label3  "RVI2"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_color3  clrGreen
class="macro">#class="kw">property indicator_style3  STYLE_SOLID
class="macro">#class="kw">property indicator_width3  class="num">1
class=class="str">"cmt">//--- plot Signal2
class="macro">#class="kw">property indicator_label4  "Signal2"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4  clrRed

「多周期随机指标的参数骨架」

把随机指标同时挂两个周期,核心不是画几条线,而是先把输入参数和句柄结构搭清楚。下面这段声明定义了主周期与次周期各一套 %K、%D、Slowing,以及线宽和序列方向的开关。 #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- includes #include <IndMSTF\IndMSTF.mqh> #include <Dashboard\Dashboard.mqh> //--- input parameters input string InpSymbol = NULL; // Symbol input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe input int InpKPeriod = 5; // %K Period input int InpDPeriod = 3; // %D Period input int InpSlowing = 3; // Slowing input ENUM_STO_PRICE InpPrice = STO_LOWHIGH; // Applied Price input ENUM_MA_METHOD InpMethod = MODE_SMA; // Calculation method input uchar InpLineWidth1 = 2; // Line width for higher period input uchar InpLineWidth2 = 1; // Line width for lower period input bool InpAsSeries = true; // Timeseries flag of indicator buffer arrays //--- indicator buffers double BufferStoch1[]; double BufferSig1[]; double BufferStoch2[]; double BufferSig2[]; //--- global variables int handle_stoch1; int handle_stoch2; 逐行看:前两句把第 4 号绘图线设为实线、宽度 1,留给次周期信号线用。两个 include 拉入多周期框架与仪表盘辅助类,缺了编译直接报错。 输入区里 InpKPeriod=5、InpDPeriod=3、InpSlowing=3 是随机指标经典快线组合;InpPrice 用 STO_LOWHIGH 表示取最高最低价归一,InpMethod 用 SMA 平滑。InpLineWidth1=2 让大周期线更粗,InpLineWidth2=1 细线画小周期,视觉上能一眼分出层级。 四个 double 数组是双周期各自的随机值与信号值缓冲;handle_stoch1、handle_stoch2 两个 int 句柄留作后面用 iStochastic 取数据用。外汇与贵金属波动剧烈,多周期共振也只是提高概率,实盘请先开 MT5 用策略测试器验证信号延迟。

MQL5 / C++
class="macro">#class="kw">property indicator_style4 STYLE_SOLID
class="macro">#class="kw">property indicator_width4 class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string InpSymbol = NULL; class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; class=class="str">"cmt">// Timeframe
input class="type">int InpKPeriod = class="num">5; class=class="str">"cmt">// %K Period
input class="type">int InpDPeriod = class="num">3; class=class="str">"cmt">// %D Period
input class="type">int InpSlowing = class="num">3; class=class="str">"cmt">// Slowing
input ENUM_STO_PRICE InpPrice = STO_LOWHIGH; class=class="str">"cmt">// Applied Price
input ENUM_MA_METHOD InpMethod = MODE_SMA; class=class="str">"cmt">// Calculation method
input class="type">uchar InpLineWidth1 = class="num">2; class=class="str">"cmt">// Line width for higher period
input class="type">uchar InpLineWidth2 = class="num">1; class=class="str">"cmt">// Line width for lower period
input class="type">bool InpAsSeries = true; class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double BufferStoch1[];
class="type">class="kw">double BufferSig1[];
class="type">class="kw">double BufferStoch2[];
class="type">class="kw">double BufferSig2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_stoch1;
class="type">int handle_stoch2;

双随机指标与面板的初始化落地

在 MT5 自定义指标里同时挂两个 Stochastic,一个算当前图表周期、一个算外部设定的品种/周期,是跨周期对照的常用做法。下面这段 OnInit 把四组缓冲区、线宽自适应和面板对象一次性建好,可直接拷进你的工程改参数验证。 核心是先 EventSetTimer(1) 开 1 秒定时器,再用 SetIndexBuffer 把 BufferStoch1~BufferSig2 绑到 0~3 号绘图缓冲。线宽按 InpTimeframe 是否大于当前 Period 翻转分配,避免大周期线被小周期线盖住。

MQL5 / C++
CMSTFIndicators indicators;              class=class="str">"cmt">// 指标集合对象实例
CDashboard *panel=NULL;                 class=class="str">"cmt">// 面板对象指针
class="type">int mouse_bar_index;                    class=class="str">"cmt">// 取数所用的柱索引
class="type">int OnInit()
  {
   EventSetTimer(class="num">1);                    class=class="str">"cmt">// 设1秒定时器
   SetIndexBuffer(class="num">0,BufferStoch1,INDICATOR_DATA);  class=class="str">"cmt">// 主随机主线缓冲
   SetIndexBuffer(class="num">1,BufferSig1,INDICATOR_DATA);    class=class="str">"cmt">// 主随机信号线缓冲
   SetIndexBuffer(class="num">2,BufferStoch2,INDICATOR_DATA);  class=class="str">"cmt">// 副随机主线缓冲
   SetIndexBuffer(class="num">3,BufferSig2,INDICATOR_DATA);    class=class="str">"cmt">// 副随机信号线缓冲
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())            class=class="str">"cmt">// 若外部周期更大
     { w1=InpLineWidth2; w2=InpLineWidth1; }  class=class="str">"cmt">// 粗细互换
   else
     { w1=InpLineWidth1; w2=InpLineWidth2; }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);  class=class="str">"cmt">// 设0号线宽
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);  class=class="str">"cmt">// 设1号线宽
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);  class=class="str">"cmt">// 设2号线宽
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);  class=class="str">"cmt">// 设3号线宽
   ArraySetAsSeries(BufferStoch1,InpAsSeries); class=class="str">"cmt">// 设时序标志
   ArraySetAsSeries(BufferSig1,InpAsSeries);
   ArraySetAsSeries(BufferStoch2,InpAsSeries);
   ArraySetAsSeries(BufferSig2,InpAsSeries);
   handle_stoch1=indicators.AddNewStochastic(NULL,PERIOD_CURRENT,InpKPeriod,InpDPeriod,InpSlowing,InpMethod,InpPrice); class=class="str">"cmt">// 当前周期随机
   handle_stoch2=indicators.AddNewStochastic(InpSymbol,InpTimeframe,InpKPeriod,InpDPeriod,InpSlowing,InpMethod,InpPrice); class=class="str">"cmt">// 外部周期随机
   if(handle_stoch1==INVALID_HANDLE || handle_stoch2==INVALID_HANDLE)
     class="kw">return INIT_FAILED;                class=class="str">"cmt">// 建柄失败则初始化错
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_stoch1,class="num">0); class=class="str">"cmt">// 抄线标签
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_stoch1,class="num">1);
   indicators.SetPlotLabelFromBuffer(class="num">2,handle_stoch2,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">3,handle_stoch2,class="num">1);
   class="type">int width=class="num">271;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0); class=class="str">"cmt">// 建271宽面板
   if(panel==NULL)
     { Print("Error. Failed to create panel object"); class="kw">return INIT_FAILED; }
  }
指标柄若返回 INVALID_HANDLE 会直接 INIT_FAILED,因此先在策略测试器里用 EURUSD 的 M15 配 H1 跑一遍,看面板是否弹出、线宽是否如预期翻转。外汇与贵金属杠杆高,跨周期信号仅作概率参考,实盘前务必在 MT5 用历史数据复核。

MQL5 / C++
CMSTFIndicators indicators;              class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;                 class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;             class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferStoch1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferSig1,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,BufferStoch2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferSig2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">2,PLOT_LINE_WIDTH,w2);
   PlotIndexSetInteger(class="num">3,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferStoch1,InpAsSeries);
   ArraySetAsSeries(BufferSig1,InpAsSeries);
   ArraySetAsSeries(BufferStoch2,InpAsSeries);
   ArraySetAsSeries(BufferSig2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_stoch1=indicators.AddNewStochastic(NULL,PERIOD_CURRENT,InpKPeriod,InpDPeriod,InpSlowing,InpMethod,InpPrice);
   handle_stoch2=indicators.AddNewStochastic(InpSymbol,InpTimeframe,InpKPeriod,InpDPeriod,InpSlowing,InpMethod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_stoch1==INVALID_HANDLE || handle_stoch2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_stoch1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_stoch1,class="num">1);
   indicators.SetPlotLabelFromBuffer(class="num">2,handle_stoch2,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">3,handle_stoch2,class="num">1);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">271;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;

◍ 面板初始化与多表布局的衔接细节

在 MT5 自定义指标里,面板对象的初始化收尾阶段决定了后续数据能不能稳定铺开。上面这段把字体设为 Calibri、字号 9,再用 Symbol() 和 Period() 拼出『品种, 周期』抬头,相当于给盯盘面板定了视觉基准。 表格是分层叠放的:ID 0 画 K 线数据,左上角坐标 (2,20),列宽 6、行高 18,右边界取到 width/2-2;ID 1 的 Y 起点靠 panel.TableY2(0)+22 动态算,避免和上方表重叠;ID 2 再接 TableY2(1)+3。这种『用前表底边推后表顶边』的写法,比写死坐标更抗 resize。 初始化末尾把 mouse_bar_index 置 0,调用 DrawData 刷出当前 bar,返回 INIT_SUCCEEDED。外汇与贵金属波动剧烈,面板只是辅助,实际信号仍需结合价格行为判断,存在滑点及错单高风险。 OnDeinit 里先 EventKillTimer 清定时器,panel 非空就 delete,最后 Comment("") 抹掉残留文字。OnCalculate 的标准签名从 rates_total 到 close[] 一应俱全,接下来就是按 bar 索引回填各表内容的迭代逻辑。

MQL5 / C++
  }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
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[],

「把多周期随机指标塞进自定义缓冲」

这段逻辑出现在指标主计算函数尾部,负责把两个 Stochastic 实例的主线与信号线分别搬进当前指标的缓冲区。注意参数里传了 tick_volume、volume、spread 三个引用数组,但本段并未直接处理它们,只是函数签名的一部分,真正干活的是 indicators 集合。 先看计算边界:limit 先取 rates_total-prev_calculated,若大于 1 说明是首算或历史重算,此时把 limit 重置为 rates_total-1,覆盖全部可用 K 线。这个写法能避免重复计算旧柱,只在新增柱上迭代。

MQL5 / C++
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[])
  {
class=class="str">"cmt">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch1,class="num">0,class="num">0,limit,BufferStoch1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch1,class="num">1,class="num">0,limit,BufferSig1))
      class="kw">return class="num">0;

   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch2,class="num">0,class="num">0,limit,BufferStoch2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch2,class="num">1,class="num">0,limit,BufferSig2))
      class="kw">return class="num">0;

class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                    | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
逐行拆一下关键调用:indicators.Calculate() 先刷新所有多品种多周期指标,失败直接 return 0 中断;DrawData 用 mouse_bar_index 把光标下那根 K 线的数据丢到面板。接着四次 DataToBuffer,前两句把 handle_stoch1 的 0 号(主线)和 1 号(信号)缓冲写入 BufferStoch1、BufferSig1,后两句对 handle_stoch2 做同样动作,limit 控制写入柱数。 OnTimer 里只转发给 indicators.OnTimer(),说明多周期数据的定时刷新靠集合自己管。OnChartEvent 把图表事件抛给 panel 处理,鼠标移动或点击的注释暗示下一步会根据光标位置重绘数据。 开 MT5 把这段接进你自己的多指标框架时,重点核对 handle_stoch1/2 的句柄是否在 OnInit 正确创建,否则 DataToBuffer 会静默返回 false 导致整段 return 0。外汇与贵金属品种点差跳变频繁,spread 数组虽未在此使用,但高频重算时建议另写逻辑监控其突变。

MQL5 / C++
const class="type">long &tick_volume[],
const class="type">long &volume[],
const class="type">int &spread[])
  {
class=class="str">"cmt">//--- Number of bars for calculation
   class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
   if(limit>class="num">1)
     {
      class=class="str">"cmt">//--- specify all the available history for calculation
      limit=rates_total-class="num">1;
      class=class="str">"cmt">/*
      class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
      class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
      */
     }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch1,class="num">0,class="num">0,limit,BufferStoch1))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch1,class="num">1,class="num">0,limit,BufferSig1))
      class="kw">return class="num">0;

   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch2,class="num">0,class="num">0,limit,BufferStoch2))
      class="kw">return class="num">0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_stoch2,class="num">1,class="num">0,limit,BufferSig2))
      class="kw">return class="num">0;

class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                    | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
class=class="str">"cmt">//--- Call the indicator collection timer
   indicators.OnTimer();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart

鼠标悬停读取光标所在 K 线数据

在 MT5 自定义指标或 EA 里,想做「鼠标移到哪根 K 线就显示哪根数据」的面板,核心是把图表鼠标事件转成时间坐标,再用 iBarShift 反查 bar 索引。下面这段处理 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK,能把光标位置换算成具体 bar 并触发绘制。 外汇与贵金属行情跳空频繁,PERIOD_CURRENT 下的 iBarShift 返回的是当前图表周期索引,跨周期核对时要自己传周期参数,否则容易读错 bar。 DrawData 函数用 CopyRates 按索引取单根 MqlRates,失败直接 return,成功才往面板写 Date / Open / Close 等字段。注意它写死 size=9、FW_BOLD 表头,面板像素布局依赖 panel.CellX/CellY 的返回,换分辨率可能要调偏移量。 让小布替你跑这套:把代码贴进 MT5 脚本,开 EURUSD 的 M5,移动鼠标看面板是否实时刷新 bar 索引;若索引乱跳,先查 ChartXYToTimePrice 的 wnd 参数是否按文档置 0。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
  class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
  class="type">class="kw">datetime time=class="num">0;
  class="type">class="kw">double price=class="num">0;
  class="type">int wnd=class="num">0;
  class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
  if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
    {
    class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
    mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
    class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
    DrawData(mouse_bar_index,time);
    }
  }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
  class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
  PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
 class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
 if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
 class="type">int  size=class="num">0;
 class="type">uint flags=class="num">0;
 class="type">uint angle=class="num">0;
 class="type">class="kw">string name=panel.FontParams(size,flags,angle);
 panel.SetFontParams(name,class="num">9,FW_BOLD);
 panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
 panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
 panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
 panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

◍ 把K线和指标值塞进表格面板

在自绘面板里实时显示当前Bar的OHLC和第一个随机指标数值,核心就是按单元格坐标定位后用 DrawText 写字符串。下面这段把 Time/Open/High/Low/Close 分别写进第0列的不同行,价格统一用 Digits() 控制小数位,避免黄金报价格式错位。 指标部分先取 handle_stoch1 在偏移 index 处的值,若返回 EMPTY_VALUE 则留空格,否则按指标自身精度转字符串,再写入第1列第0行。注意 DrawText 最后一个参数是文字角度,价格用了90(竖排),指标值用了190,纯属版面习惯,不影响数据。 在 MT5 里跑这段前,先确认 panel.CellX/CellY 的(表号,行,列)三元组和你定义的表格结构一致;若列宽不够,DoubleToString 出来的长字符串会被截断,黄金XAUUSD 用5位小数时最容易露馅。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_stoch1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_stoch1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_stoch1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

「把双指标线态与交叉关系写进面板」

这段逻辑干的事很直接:把两个随机指标(stoch1、stoch2)在指定 bar 的读数、线态,以及两者之间的相对位置,全部刷到自定义表格面板里。外汇与贵金属市场波动剧烈、杠杆风险高,这类面板只作状态观测,不构成方向暗示。 先取 stoch1 的第 0 缓冲在 index 柱的线态 ENUM_LINE_STATE,用 BufferLineStateDescription 转成文字,绘到面板第 1 行第 1 列单元格右移 2 像素处,字体宽 190。stoch2 同理:GetDataTo 拿值,DoubleToString 按指标小数位格式化,空值填空格,再画标题与线态描述。 关键的相对关系在最后一段:取 stoch2 在 index+1 柱的值 value21,调用 BufferLineStateRelative 比较 stoch1 与 stoch2 当前柱的上下与穿越。枚举映射成『ma1 > ma2』『Top-down crossing』等字符串,绘到面板第 2 行第 2 列。末尾 ChartRedraw 强制重绘,避免面板滞后。 别把相对态当信号 面板里的 CROSS_UP / CROSS_DOWN 只是描述已发生的线位关系,黄金与欧美的点差跳空可能让穿越滞后 1~2 根柱才被这个函数捕获,实盘前务必用历史回放核对。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_stoch1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_stoch2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_stoch2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_stoch2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_stoch2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_stoch2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_stoch1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_stoch1);
  class="type">class="kw">string ma2=indicators.Name(handle_stoch2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">190);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

多周期TRIX指标的MT5工程骨架

要在 MT5 里验证多时间框架三重指数平滑平均线(TRIX),先得把指标框架搭对。下面这段声明直接定义了独立窗口、双缓冲和两条绘制线,复制进 .mq5 文件即可编译出空壳。 #property indicator_separate_window 指定在副图显示;indicator_buffers 2 与 indicator_plots 2 意味着同时输出 TRIX1(红)与 TRIX2(深蓝)两条线,方便把不同周期叠在同一窗口比对。 输入参数里 InpPeriod 默认 13,这是 TRIX 经典回望长度;InpSymbol 留 NULL 表示取当前图表品种,InpTimeframe 用 PERIOD_CURRENT 则跟随图表周期。把 InpLineWidth1 设 2,仅影响大周期线的视觉粗细,不改算法。 外汇与贵金属波动剧烈,TRIX 金叉死叉仅代表动量的可能转折,实盘前务必在策略测试器跑至少 3 个月 tick 数据确认信号密度。

MQL5 / C++
class=class="str">"cmt">//|                    TestMSTFTripleExponentialAverage.mq5 |
class=class="str">"cmt">//|             Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot TRIX1
class="macro">#class="kw">property indicator_label1   "TRIX1"
class="macro">#class="kw">property indicator_type1    DRAW_LINE
class="macro">#class="kw">property indicator_color1   clrRed
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot TRIX2
class="macro">#class="kw">property indicator_label2   "TRIX2"
class="macro">#class="kw">property indicator_type2    DRAW_LINE
class="macro">#class="kw">property indicator_color2   clrDodgerBlue
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol      =  NULL;            class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe  =  PERIOD_CURRENT;  class=class="str">"cmt">// Timeframe
input class="type">int               InpPeriod     =  class="num">13;              class=class="str">"cmt">// Calculation period
input ENUM_APPLIED_PRICE InpPrice     =  PRICE_CLOSE;
input class="type">uchar             InpLineWidth1 =  class="num">2;                class=class="str">"cmt">// Line width for higher period

◍ 双周期 TRIX 的缓冲与面板初始化

这段初始化逻辑同时挂了两个 TRIX 实例:一个算当前图表品种与周期,另一个算外部指定的符号和周期。两路数据分别写进 BufferTRIX1 和 BufferTRIX2,并在 OnInit 里通过 SetIndexBuffer 绑到绘图缓冲 0 和 1。 线宽处理有个细节:若输入周期大于当前图表周期,缓冲 0 用细线(InpLineWidth2),缓冲 1 用粗线(InpLineWidth1);反之对调。用 PlotIndexSetInteger 的 PLOT_LINE_WIDTH 写入,实测改这两个 input 能直接看出主次周期视觉权重。 ArraySetAsSeries 按 InpAsSeries(默认 true)把缓冲当时间序列排,注释里明确写了『测试用,看有无差异』,说明作者故意留了开关验证索引方向。 两个句柄由 indicators.AddNewTriX 创建,任一返回 INVALID_HANDLE 就 INIT_FAILED;面板用 new CDashboard(1,20,20,247,264,0) 建在坐标 (20,20)、宽 247 高 264,读者开 MT5 把这段贴进自定义指标就能复现双线叠加面板。外汇与贵金属杠杆高,指标仅作概率参考,不代表方向必然。

MQL5 / C++
input class="type">uchar                InpLineWidth2   = class="num">1;             class=class="str">"cmt">/* Junior period Line Width   */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                InpAsSeries     = true;             class=class="str">"cmt">/* As Series flag            */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferTRIX1[];
class="type">class="kw">double    BufferTRIX2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_trix1;
class="type">int handle_trix2;
CMSTFIndicators indicators;      class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;          class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;       class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferTRIX1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferTRIX2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferTRIX1,InpAsSeries);
   ArraySetAsSeries(BufferTRIX2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_trix1=indicators.AddNewTriX(NULL,PERIOD_CURRENT,InpPeriod,InpPrice);
   handle_trix2=indicators.AddNewTriX(InpSymbol,InpTimeframe,InpPeriod,InpPrice);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_trix1==INVALID_HANDLE || handle_trix2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_trix1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_trix2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">247;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);

「面板初始化与多表布局的落点」

指标加载时若面板对象创建失败,直接返回 INIT_FAILED 并打出错误日志,这是避免后续空指针崩溃的第一道闸。 面板字体固定为 Calibri 9 号,表头用 Symbol() 拼接周期枚举(截取 EnumToString(Period()) 第 7 位起子串)显示品种与时帧,例如 "EURUSD, H1"。 三张表按垂直顺序排:ID0 画在 (2,20) 起点、20 行 6 列;ID1 的 Y1 取 ID0 的 Y2 加 22 像素;ID2 的 Y1 取 ID1 的 Y2 加 3 像素、改为 3 列布局。这种硬偏移在 MT5 里调参时容易因字体缩放错位,建议开图表实测。 // 面板创建失败即中止 if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } // 设置字体 panel.SetFontParams("Calibri",9); // 显示表头 panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); // 建表0并绘制 panel.CreateNewTable(0); panel.DrawGrid(0,2,20,6,2,18,width/2-2); // 建表1,Y偏移22 panel.CreateNewTable(1); int y1=panel.TableY2(0)+22; panel.DrawGrid(1,2,y1,2,2,18,width/2-2); // 建表2,Y偏移3 panel.CreateNewTable(2); int y2=panel.TableY2(1)+3; panel.DrawGrid(2,2,y2,3,2,18,width/2-2); 反初始化里先杀定时器、删面板对象、清 Comment,否则切换周期会残留旧对象。OnCalculate 拿到 rates_total 与 prev_calculated 后接入行情数组,是后续鼠标联动刷数据的入口。外汇与贵金属波动剧烈,这类面板仅作辅助观察,实盘决策仍需自担高风险。

MQL5 / C++
if(panel==NULL)
  {
   Print("Error. Failed to create panel object");
   class="kw">return INIT_FAILED;
  }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }
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[],

多周期指标的计算与事件回调接线

这段 MT5 自定义指标的核心逻辑落在 OnCalculate 里:先用 rates_total - prev_calculated 拿到需要计算的 bar 数,若差值大于 1 则判定为首算或历史变更,直接把 limit 拉到 rates_total - 1 覆盖全部可用 K 线。 计算前必须调 indicators.Calculate(),失败就 return 0 中断;随后用 DrawData 把光标所在 bar(或图表外时的当前 bar)推到面板显示。两条 TRIX 缓冲的回填分别走 DataToBuffer,传入 NULL 与 PERIOD_CURRENT 表示当前品种当前周期,句柄 handle_trix1 / trix2 对应 BufferTRIX1 / BufferTRIX2,任一步返回 false 同样撤出。 定时器里只转调 indicators.OnTimer(),把周期刷新交给集合类自己处理。OnChartEvent 中面板事件全权交由 panel.OnChartEvent 消化,仅当 id 为 CHARTEVENT_MOUSE_MOVE 或 CHARTEVENT_CLICK 时才在局部声明 datetime time=0 准备记录坐标——这种分工能避免鼠标噪声触发多余计算。 外汇与贵金属行情跳空频繁,这类依赖多缓冲回填的指示器在 spread 突变时可能漏算,建议开 MT5 把 limit 分支打印到 Experts 日志验证首算覆盖数。

MQL5 / C++
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">//--- Number of bars for calculation
 class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
 if(limit>class="num">1)
  {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
 if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
 DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_trix1,class="num">0,class="num">0,limit,BufferTRIX1))
   class="kw">return class="num">0;
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_trix2,class="num">0,class="num">0,limit,BufferTRIX2))
   class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
 {
 class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
 {
 class=class="str">"cmt">//--- Handling the panel
 class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
 class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
  if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
   {
    class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
    class="type">class="kw">datetime time=class="num">0;

◍ 把光标位置映射成可显示的柱数据

在 MT5 图表事件回调里,先用 ChartXYToTimePrice 把鼠标事件的 lparam/dparam 转成时间和价格,再用 iBarShift 反查光标落在哪一根柱上。拿到 bar 索引后交给 DrawData 去面板画数据,这一步是交互式盯盘的基础。 DrawData 内部用 CopyRates 按索引取 1 根 MqlRates,失败直接 return;随后用 panel 的字体接口把表头设成 9 号粗体、数据设成 9 号常规,并在 TableY1(0)-16 处写「Bar data [索引]」、TableY1(1)-16 处写「Indicators data [索引]」。 实际验证时,把下面这段接进 OnChartEvent 的 CHARTEVENT_MOUSE_MOVE 分支,开 EURUSD 的 M5 图移动鼠标,面板应实时刷新对应柱的日期与 OHLC;外汇和贵金属波动剧烈,这类悬浮读数只作辅助,不构成任何方向判断。

MQL5 / C++
class="type">class="kw">double price=class="num">0;
class="type">int wnd=class="num">0;
class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
  {
   class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
   mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
   class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
   DrawData(mouse_bar_index,time);
   }
 }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
 if(id>CHARTEVENT_CUSTOM)
  {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
  class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
  if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
    class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
  class="type">int   size=class="num">0;
  class="type">uint flags=class="num">0;
  class="type">uint angle=class="num">0;
  class="type">class="kw">string name=panel.FontParams(size,flags,angle);
  panel.SetFontParams(name,class="num">9,FW_BOLD);
  panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
  panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
  panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

「把K线与TRIX读数灌进面板表格」

在自定义面板里逐格写文字,靠的是 CellX / CellY 算坐标,再交给 DrawText 落字。下面这段把当前_bar 的 Time/Open/High/Low/Close 五项塞进第 0 列,坐标偏移统一 +2 像素避免贴边。 时间用 TimeToString(rates[0].time,TIME_MINUTES) 只取到分钟,角度参数 90 让字竖排省宽度;OHLC 全用 DoubleToString(...,Digits()) 自适应品种小数位,黄金报 1234.56、欧美报 1.08567 都不会错位。 指标区另起一列:先用 indicators.Title(handle_trix1) 写表头,再用 GetData(handle_trix1,0,0,index) 取 TRIX 主线数值。若返回 EMPTY_VALUE 就显示空格,否则按 indicators.Digits(handle_trix1) 精度转字符串,角度 110 略微倾斜以区分 K 线区。 line state 那格只写了标签没填值,说明状态判定逻辑放在后续小节;你开 MT5 把这段贴进 OnDraw,先确认面板前 6 行坐标不重叠即可。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),        panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),        panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),         panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),       panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_trix1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_trix1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_trix1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

把双 TRIX 线的相对位置写进面板

这段逻辑干的事很直接:把两个 TRIX 指标句柄在指定 Bar 上的数值与线态,分别填进 GUI 面板的第二、第三张表,并算出它们之间的相对关系。注意 index+1 被用来取第二个指标的前一根值,这是判断穿越方向的必要输入。 对外汇与贵金属这类高波动品种,TRIX 双线穿越往往只代表短期动量倾向反转,不等于趋势确认,实盘须配合波动率过滤。 代码里先用 BufferLineState 取各自线态,再用 BufferLineStateRelative 比较:高于、低于、上穿、下穿四种结论会被 StringFormat 拼成可读字符串。最后 ChartRedraw 强制重绘,避免面板滞后一帧。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_trix1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_trix2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_trix2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_trix2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_trix2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_trix2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_trix1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_trix1);
  class="type">class="kw">string ma2=indicators.Name(handle_trix2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

◍ 多周期成交量指标的骨架怎么搭

想在 MT5 副图里同时看两个周期的成交量,先把指标属性钉死:独立窗口、4 个 buffer、2 个 plot,这是最少配置。 代码里用 DRAW_COLOR_HISTOGRAM 画两根彩色柱状(Volumes1 / Volumes2),线宽分别由 InpLineWidth1=2 和 InpLineWidth2=1 控制,高阶周期画粗一点,低阶周期画细一点,视觉上能直接区分主次。 输入参数留了 InpSymbol(NULL 表示当前品种)、InpTimeframe(PERIOD_CURRENT)、InpVolume(VOLUME_TICK 即逐笔成交量),你换黄金 XAUUSD 或欧美 EURUSD 时只改前两项就能跑。外汇和贵金属杠杆高, tick 量突增可能只是流动性瞬变,别单凭柱体放大就判方向。 下面这段是声明部分的原文,逐行看就是上面说的那些定点。

MQL5 / C++
class=class="str">"cmt">//|                                                                       TestMSTFVolumes.mq5 |
class=class="str">"cmt">//|                       Copyright class="num">2023, MetaQuotes Ltd.                 |
class=class="str">"cmt">//|                                         [MQL5官方文档]          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">4
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot AC1
class="macro">#class="kw">property indicator_label1   "Volumes1"
class="macro">#class="kw">property indicator_type1   DRAW_COLOR_HISTOGRAM
class=class="str">"cmt">//class="macro">#class="kw">property indicator_color1  clrGreen,clrRed // analog in line class="num">98
class="macro">#class="kw">property indicator_style1   STYLE_SOLID
class="macro">#class="kw">property indicator_width1   class="num">1
class=class="str">"cmt">//--- plot AC2
class="macro">#class="kw">property indicator_label2   "Volumes2"
class="macro">#class="kw">property indicator_type2   DRAW_COLOR_HISTOGRAM
class=class="str">"cmt">//class="macro">#class="kw">property indicator_color2  clrGreen,clrRed // analog in line class="num">99
class="macro">#class="kw">property indicator_style2   STYLE_SOLID
class="macro">#class="kw">property indicator_width2   class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string            InpSymbol       =  NULL;            class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES   InpTimeframe    =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Timeframe
input ENUM_APPLIED_VOLUME InpVolume      =  VOLUME_TICK;    class=class="str">"cmt">/* Applied Volume        */ class=class="str">"cmt">// Used volume
input class="type">uchar             InpLineWidth1   =  class="num">2;              class=class="str">"cmt">/* Senior period Line Width  */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar             InpLineWidth2   =  class="num">1;              class=class="str">"cmt">/* Junior period Line Width  */ class=class="str">"cmt">// Line width for lower period

「双周期量能缓冲区的初始化绑定」

在 MT5 自定义指标里同时挂两个 Volumes 实例,一条算当前图表品种与周期,另一条算外部设定的品种与周期,靠的就是 OnInit 里先建句柄再绑缓冲。下面这段初始化把 4 个数组分别映射到 0~3 号图缓冲区,其中偶数号存数据、奇数号存颜色索引,方便后面按量能大小染色。 线宽逻辑有个细节:当设定周期大于当前图表周期时,w1 取 InpLineWidth2、w2 取 InpLineWidth1,否则对调。也就是说跨周期那条线在当前周期更短时会自动变细,视觉上能区分主次。 ArraySetAsSeries 用 InpAsSeries 统一控制四个缓冲区的时间序列方向,注释里明说这是为测试『正反向无差异』留的开关,实盘你可以把它设 false 对比索引写法。 两个 handle 若任一为 INVALID_HANDLE 直接 INIT_FAILED,开 MT5 加载时若报错先查 InpSymbol 是否拼错——这是最常见的句柄创建失败原因。外汇与贵金属品种量能数据受报价源限制,跨品种句柄失败概率偏高,属正常风险。

MQL5 / C++
input class="type">bool                InpAsSeries  = true;           class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferVolumes1[];
class="type">class="kw">double    BufferClrVolumes1[];
class="type">class="kw">double    BufferVolumes2[];
class="type">class="kw">double    BufferClrVolumes2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_volumes1;
class="type">int handle_volumes2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int    mouse_bar_index;        class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign arrays BufferVolumes1 and BufferVolumes2 to plot buffers class="num">0 and class="num">2, respectively,
class=class="str">"cmt">//--- and class="type">color arrays BufferClrVolumes1 and BufferClrVolumes2 to buffers class="num">1 and class="num">3
   SetIndexBuffer(class="num">0,BufferVolumes1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferClrVolumes1,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(class="num">2,BufferVolumes2,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,BufferClrVolumes2,INDICATOR_COLOR_INDEX);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferVolumes1,InpAsSeries);
   ArraySetAsSeries(BufferClrVolumes1,InpAsSeries);
   ArraySetAsSeries(BufferVolumes2,InpAsSeries);
   ArraySetAsSeries(BufferClrVolumes2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_volumes1=indicators.AddNewVolumes(NULL,PERIOD_CURRENT,InpVolume);
   handle_volumes2=indicators.AddNewVolumes(InpSymbol,InpTimeframe,InpVolume);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_volumes1==INVALID_HANDLE || handle_volumes2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set indicator line descriptions from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_volumes1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_volumes2,class="num">0);

把指标线和面板画进 MT5 界面

指标初始化收尾阶段,先把两条成交量类指标线的颜色绑定到已创建指标的计算缓冲区,避免默认配色掩盖多空差异。调用 SetPlotColorsFromBuffer 时,第一参数填线索引 0 和 1,第二参数传对应指标句柄 handle_volumes1、handle_volumes2,第三参数 0 表示取该缓冲区的第一色组。 面板部分用 CDashboard 类在坐标 (20,20) 起手,宽 267、高 264 画出主框。字体强制 Calibri 9 号,表头直接拼 Symbol() 与 Period() 字符串子串(从枚举名第 7 位截起),这样切换品种周期时标题自动跟着变。 三张数据表用 TableY2 接力定位:表 0 画完取底沿 +22 像素给表 1 当顶沿,表 1 底沿 +3 给表 2。表 0、1 均为 2 列 20 行高 18,表 2 改 3 列,列宽统一 width/2-2=131。这种硬编码间距在 4K 屏上可能略挤,自己调 y1、y2 的偏移量即可。 OnDeinit 里必须 EventKillTimer 并 delete panel,否则 MT5 切周期会残留不可点掉的 GUI 对象。初始化末尾把 mouse_bar_index 置 0 并 DrawData(0,TimeCurrent()),首屏默认显示当前 bar,外汇与贵金属波动剧烈,面板数值仅作参考,实盘高风险。

MQL5 / C++
class=class="str">"cmt">//--- Set colors for indicator lines from the buffer class="type">color set of calculation part of created indicators
  indicators.SetPlotColorsFromBuffer(class="num">0,handle_volumes1,class="num">0);
  indicators.SetPlotColorsFromBuffer(class="num">1,handle_volumes2,class="num">0);

class=class="str">"cmt">//--- Dashboard
class=class="str">"cmt">//--- Create the panel
  class="type">int width=class="num">267;
  panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
  if(panel==NULL)
    {
      Print("Error. Failed to create panel object");
      class="kw">return INIT_FAILED;
    }
class=class="str">"cmt">//--- Set font parameters
  panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
  panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
  panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
  panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
  panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
  class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
  panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
  panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
  class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
  panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
  mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
  DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
  EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
  if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
  Comment("");
  }

◍ 多周期量能指标的刷新与事件接管

这段 MT5 自定义指标的核心计算入口,靠 OnCalculate 接住每一根新 K 线。先用 rates_total-prev_calculated 算出待处理 bar 数,若差值大于 1,说明是首算或历史被重灌,此时直接把 limit 拉到 rates_total-1,把所有历史都重算一遍。 计算前必须调 indicators.Calculate(),任一多品种多周期子指标失败就 return 0,终端上该指标会留空不画。随后 DrawData 把鼠标所指 bar(或光标在图外时的当前 bar)推到面板显示,这一步是盯盘时实时读数的关键。 两个成交量缓冲区靠 DataToColorBuffer 从子指标句柄搬数据:handle_volumes1 进 BufferVolumes1 / BufferClrVolumes1,handle_volumes2 进另一对。limit 作为长度参数传进去,控制从哪根开始刷。最后返回 rates_total,下一帧 MT5 才知道已算到哪。 OnTimer 只做一件事:转发给 indicators.OnTimer(),让集合里的指标按自己的定时器节奏更新。OnChartEvent 截图表交互事件,鼠标移动、点击都会被 lparam / dparam 接住,用来定位 mouse_bar_index。外汇与贵金属波动剧烈,这类依赖历史重算的面板在跳空时可能短暂失真,实盘前应在 MT5 策略测试器用 EURUSD 1H 跑一遍验证 limit 逻辑。

MQL5 / C++
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">//--- Number of bars for calculation
 class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
 if(limit>class="num">1)
  {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
 if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
 DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
 if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_volumes1,class="num">0,class="num">0,limit,BufferVolumes1,BufferClrVolumes1))
   class="kw">return class="num">0;
 if(!indicators.DataToColorBuffer(NULL,PERIOD_CURRENT,handle_volumes2,class="num">0,class="num">0,limit,BufferVolumes2,BufferClrVolumes2))
   class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
class=class="str">"cmt">//--- Call the indicator collection timer
 indicators.OnTimer();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,

「鼠标悬停实时回写K线数据到面板」

在 MT5 自定义指标里接管 OnChartEvent,是做交互式看盘面板的关键一步。下面这段逻辑把鼠标移动和点击转成具体的 K 线索引,再喂给面板显示,省去手动对着十字光标读数的麻烦。

MQL5 / C++
const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
       class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
       class="type">class="kw">datetime time=class="num">0;
       class="type">class="kw">double price=class="num">0;
       class="type">int wnd=class="num">0;
       class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
       if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
         {
           class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
           mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
           class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
           DrawData(mouse_bar_index,time);
         }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
       class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
       PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
逐行拆解:函数入口先调 panel.OnChartEvent 把事件抛给面板基类处理;随后判断 id 是否为鼠标移动或图表点击,是的话用 ChartXYToTimePrice 把像素坐标 lparam/dparam 转成时间 price 和时间 time。转换成功后,iBarShift 用当前品种和周期把 time 映射到 K 线序号 mouse_bar_index,再调 DrawData 画数据。 DrawData 内部先开 MqlRates rates[1] 准备接一根棒线的 OHLC;CopyRates 取指定 index 的 1 根数据,取不到就直接 return。接着通过 panel.FontParams 拿字体名,设 9 号加粗给两个表头(酒红色写 Bar data、绿色写 Indicators data),坐标分别落在 TableY1(0)-16 和 TableY1(1)-16,宽度用 panel.Width()-6 控制右边界。 外汇和贵金属行情跳空频繁,iBarShift 在跳空处可能返回最近一根而非视觉正下方那根,实盘前建议在 EURUSD 的 M1 上移动鼠标核对索引是否跟十字光标一致。

MQL5 / C++
const class="type">class="kw">string &sparam)
  {
class=class="str">"cmt">//--- Handling the panel
class=class="str">"cmt">//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
       class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
       class="type">class="kw">datetime time=class="num">0;
       class="type">class="kw">double price=class="num">0;
       class="type">int wnd=class="num">0;
       class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
       if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
         {
           class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
           mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
           class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
           DrawData(mouse_bar_index,time);
         }
     }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
       class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
       PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel

把K线字段塞进表格单元

在自绘面板上显示当前柱的 OHLC 与时间,本质就是按单元格坐标逐行写文本。下面这段把 Date/Time/Open/High/Low/Close 六个标签和对应值分别填进第 0 列的不同行,坐标偏移 +2 是为了避开单元格边框。 时间用 TimeToString 配合 TIME_DATE 与 TIME_MINUTES 宏拆成日期和分钟两级;价格用 DoubleToString 接 Digits() 动态小数位,黄金 XAUUSD 通常 2 位、部分经纪商 3 位,写死小数位会错位。 指标行另起一列:先用 indicators.Title 取音量类指标名,再用 GetData 抓指定偏移 bar 的数值,EMPTY_VALUE 时留空格而不是写 0,避免假数据误导。外汇与贵金属波动剧烈,面板数值仅作实时参考,下单仍须自行核对高风险。

MQL5 / C++
  panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),     panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),       panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),       panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),        panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),      panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_volumes1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_volumes1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_volumes1)) : " ");

◍ 双指标表格的逐格绘制与相对位置判定

在 MT5 自定义面板里把两个成交量类指标并排成表,核心就是按 CellX/CellY 的(表号,行,列)坐标往里填字。上面这段把指标1的标题、数值、线状态分别写进表1的第0、1行,指标2同理写进表2,所有文本偏移量统一 +2 像素避免贴边。 数值读取用 GetDataTo(Symbol(),PERIOD_CURRENT,handle,0,0,index),若返回 EMPTY_VALUE 就显示空格,否则按 Digits(handle) 的小数位用 DoubleToString 格式化——这是防止成交量小数位错乱的直接办法。 相对位置才是重点:取指标2在 index 与 index+1 两柱的值,交给 BufferLineStateRelative 比较指标1当前柱与指标2前后柱的关系,输出高于、低于、上穿或下穿四种状态。外汇与贵金属波动受杠杆放大,这类位置关系只提示概率倾向,实盘前务必在策略测试器用历史数据核对坐标与状态枚举是否如预期。

MQL5 / C++
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">120);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_volumes1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">120);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_volumes2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_volumes2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_volumes2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">120);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_volumes2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">120);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_volumes2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_volumes1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_volumes1);
  class="type">class="kw">string ma2=indicators.Name(handle_volumes2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);

「把 WPR 双线塞进独立子窗口」

这段脚本把 Williams %R 做成双线指标,挂在独立子窗口里,方便和主图价格行为对照看超买超卖。 面板文字用 panel.DrawText 画在指定单元格偏移 2 像素的位置,颜色设 clrNONE 表示不填底色,字体大小 120;随后 ChartRedraw(ChartID()) 强制重绘,改动能立刻在 MT5 上反映,不用等下一根 K 线。 指标属性里 indicator_buffers 2indicator_plots 2 定义了两条线:WPR1 红色、WPR2 道奇蓝,线宽都是 1。计算周期默认 14,这是 WPR 最常用参数,你开 MT5 后改 InpPeriod 到 21 或 7,超买超卖灵敏度会有明显变化。 外汇和贵金属波动大,WPR 触及极值只代表概率倾向反转,实盘务必轻仓并自担高风险。

MQL5 / C++
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">120);
  
class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     TestMSTFWilliamsPercentRange.mq5 |
class=class="str">"cmt">//|            Copyright class="num">2023, MetaQuotes Ltd. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
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">2
class="macro">#class="kw">property indicator_plots   class="num">2
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- plot WPR1
class="macro">#class="kw">property indicator_label1  "WPR1"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrRed
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- plot WPR2
class="macro">#class="kw">property indicator_label2  "WPR2"
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color2  clrDodgerBlue
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">1
class=class="str">"cmt">//--- includes
class="macro">#include <IndMSTF\IndMSTF.mqh>
class="macro">#include <Dashboard\Dashboard.mqh>
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string              InpSymbol       =  NULL;            class=class="str">"cmt">/* Symbol                */ class=class="str">"cmt">// Symbol
input ENUM_TIMEFRAMES    InpTimeframe    =  PERIOD_CURRENT;  class=class="str">"cmt">/* Timeframe             */ class=class="str">"cmt">// Timeframe
input class="type">int                InpPeriod       =  class="num">14;              class=class="str">"cmt">/* Period                */ class=class="str">"cmt">// Calculation period

双周期WPR的线宽与缓冲初始化

在MT5里做跨周期WPR叠加,最先要理清的是两条线的视觉权重。代码用 InpLineWidth1=2 和 InpLineWidth2=1 分别定义大周期与小周期的默认线宽,但在 OnInit 里会根据 InpTimeframe 与当前 Period() 的大小关系对调赋值——当外部周期大于图表周期时,小周期线反而用宽线,大周期线用细线。 缓冲数组 BufferWPR1 和 BufferWPR2 通过 SetIndexBuffer 绑定到绘图槽0和1,并用 ArraySetAsSeries 按 InpAsSeries(默认true)设为时间序列方向。注意这里故意留了开关,方便你改 false 后对比索引写法有没有隐性错位。 两组WPR句柄由 indicators.AddNewWPR 创建:第一组用 NULL、PERIOD_CURRENT 抓当前图表的品种与周期,第二组用 InpSymbol、InpTimeframe 抓设定的外部品种周期。任一返回 INVALID_HANDLE 就直接 INIT_FAILED,开MT5加载时若报初始化错,先查这两个句柄而不是画线逻辑。 线宽对调那段值得手动改参数验证:把 InpTimeframe 设成小于当前周期,w1/w2 赋值分支走 else,两条线粗细会和默认预期反过来。外汇与贵金属波动剧烈,跨周期指标仅作概率参考,实盘需自担高风险。

MQL5 / C++
input class="type">uchar                InpLineWidth1  =  class="num">2;                class=class="str">"cmt">/* Senior period Line Width   */ class=class="str">"cmt">// Line width for higher period
input class="type">uchar                InpLineWidth2  =  class="num">1;                class=class="str">"cmt">/* Junior period Line Width   */ class=class="str">"cmt">// Line width for lower period
input class="type">bool                 InpAsSeries    =  true;              class=class="str">"cmt">/* As Series flag             */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double    BufferWPR1[];
class="type">class="kw">double    BufferWPR2[];
class=class="str">"cmt">//--- global variables
class="type">int handle_wpr1;
class="type">int handle_wpr2;
CMSTFIndicators indicators;    class=class="str">"cmt">// An instance of the indicator collection object
class=class="str">"cmt">//--- variables for the panel
CDashboard *panel=NULL;        class=class="str">"cmt">// Pointer to the panel object
class="type">int       mouse_bar_index;     class=class="str">"cmt">// Index of the bar the data is taken from
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second
   EventSetTimer(class="num">1);
class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively
   SetIndexBuffer(class="num">0,BufferWPR1,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,BufferWPR2,INDICATOR_DATA);
class=class="str">"cmt">//--- Set the line width
   class="type">int w1=class="num">0,w2=class="num">0;
   if(InpTimeframe>Period())
     {
      w1=InpLineWidth2;
      w2=InpLineWidth1;
     }
   else
     {
      w1=InpLineWidth1;
      w2=InpLineWidth2;
     }
   PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,w1);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,w2);
class=class="str">"cmt">//--- sets indicator shift
class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference)
   ArraySetAsSeries(BufferWPR1,InpAsSeries);
   ArraySetAsSeries(BufferWPR2,InpAsSeries);

class=class="str">"cmt">//--- Create two indicators of the same type
class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_wpr1=indicators.AddNewWPR(NULL,PERIOD_CURRENT,InpPeriod);
   handle_wpr2=indicators.AddNewWPR(InpSymbol,InpTimeframe,InpPeriod);
class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error
   if(handle_wpr1==INVALID_HANDLE || handle_wpr2==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(class="num">0,handle_wpr1,class="num">0);
   indicators.SetPlotLabelFromBuffer(class="num">1,handle_wpr2,class="num">0);
     
class=class="str">"cmt">//--- Dashboard

◍ 面板与多表布局的初始化拆解

在 MT5 指标 OnInit 里搭面板,第一步是把画布尺寸钉死:宽度写 231 像素、高度 264,左上角锚点在 (20,20)。若 new CDashboard 返回 NULL,直接 Print 报错并返回 INIT_FAILED,这能避免后面空指针拖垮整个指标加载。 字体用 Calibri 9 号,面板头部显示「品种名 + 逗号 + 周期枚举去前 7 字符」这样的标签,例如 EURUSD 配 PERIOD_H1 会截出 H1。 表格是分层叠的:ID 0 画在背景上,2 列 20 行、单元格宽高 18,列宽取 width/2-2 即 113 左右;ID 1 的 Y 起点 = 表 0 的 Y2 再加 22 像素,ID 2 又接在表 1 的 Y2 加 3 像素处。这种硬偏移写法在改宽度时要同步算,否则表会叠边。 初始化末尾把 mouse_bar_index 置 0,调 DrawData 刷当前棒数据,返回 INIT_SUCCEEDED。OnDeinit 里先 EventKillTimer 杀定时器,再 delete panel 并 Comment("") 清屏,漏掉任一步都可能留下残留对象。 开 MT5 把这段塞进自家指标,改 width 看三张表是否还对齐,是最快的验证法。

MQL5 / C++
class=class="str">"cmt">//--- Create the panel
   class="type">int width=class="num">231;
   panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264,class="num">0);
   if(panel==NULL)
     {
       Print("Error. Failed to create panel object");
       class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- Set font parameters
   panel.SetFontParams("Calibri",class="num">9);
class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7));
class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it
   panel.CreateNewTable(class="num">0);
class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the panel background
   panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1
   panel.CreateNewTable(class="num">1);
class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1
   class="type">int y1=panel.TableY2(class="num">0)+class="num">22;
class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the panel background
   panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2);
class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2
   panel.CreateNewTable(class="num">2);
class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2
   class="type">int y2=panel.TableY2(class="num">1)+class="num">3;
class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard
   panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2);

class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=class="num">0;
class=class="str">"cmt">//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());
class=class="str">"cmt">//--- Successful initialization
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the timer
   EventKillTimer();
class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it
   if(panel!=NULL)
      class="kw">delete panel;
class=class="str">"cmt">//--- Delete all comments
   Comment("");
   }

「多周期指标的计算与事件回调落地」

这段 MT5 自定义指标的核心计算逻辑,集中在 OnCalculate 里对历史与实时 bar 的区分处理。首次加载或 history 变动时,limit 会被强制拉到 rates_total-1,意味着全量重算;平时只算新增 bar,省 CPU。 代码里 indicators.Calculate() 负责把多品种多周期指标算完,失败直接 return 0 中断。随后 DrawData 把鼠标所在 bar(或当前 bar)的数据画到 dashboard,再用 DataToBuffer 把两个 WPR 缓冲(handle_wpr1 / handle_wpr2)拷进指标缓冲 BufferWPR1 / BufferWPR2,limit 控制写入深度。 OnTimer 只做一件事:转发给 indicators.OnTimer(),适合做跨周期轮询。OnChartEvent 则把图表交互(鼠标移动、点击)交给 panel.OnChartEvent 处理,注释里明确写了光标移动或点击都会触发。 开 MT5 接这段时,注意 handle_wpr1/2 必须在 OnInit 里用 iCustom 或类成员正确取得,否则 DataToBuffer 返回 false 会让指标静默退出。外汇与贵金属行情跳空频繁,这类全量重算机制在周末重连后可能短暂占用主线程,属正常高波动风险表现。

MQL5 / C++
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">//--- Number of bars for calculation
 class="type">int limit=rates_total-prev_calculated;
class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history
 if(limit>class="num">1)
  {
   class=class="str">"cmt">//--- specify all the available history for calculation
   limit=rates_total-class="num">1;
   class=class="str">"cmt">/*
   class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators),
   class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers
   */
  }
class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators
 if(!indicators.Calculate())
   class="kw">return class="num">0;
class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard
 DrawData(mouse_bar_index,time[mouse_bar_index]);
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_wpr1,class="num">0,class="num">0,limit,BufferWPR1))
   class="kw">return class="num">0;
 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_wpr2,class="num">0,class="num">0,limit,BufferWPR2))
   class="kw">return class="num">0;
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
 class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
 {
 class=class="str">"cmt">//--- Call the indicator collection timer
  indicators.OnTimer();
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent function                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,
                const class="type">long &lparam,
                const class="type">class="kw">double &dparam,
                const class="type">class="kw">string &sparam)
 {
 class=class="str">"cmt">//--- Handling the panel
 class=class="str">"cmt">//--- Call the panel event handler
  panel.OnChartEvent(id,lparam,dparam,sparam);
 class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart

把鼠标位置换算成可读的K线数据

在 MT5 自定义指标里捕获 CHARTEVENT_MOUSE_MOVE 与 CHARTEVENT_CLICK 后,第一件事是把光标的像素坐标转成时间和价格。ChartXYToTimePrice 接收图表的 x、y 像素值,回填 time 与 price,并给出窗口序号 wnd;转换成功才继续,否则容易在拖出主图区域时写脏全局变量。 拿到 time 后,用 iBarShift(Symbol(), PERIOD_CURRENT, time) 反查光标所在 K 线的序列下标,赋给 mouse_bar_index。这个下标就是后面所有面板绘制的锚点——它随周期切换自动对齐当前 PERIOD_CURRENT,不用手动处理偏移。 DrawData 函数只取 1 根 bar:CopyRates 用 index 拉 1 根到 rates[1],若返回值不等于 1 直接 return,避免越界读历史。面板先设 9 号加粗字体写表头(Bar data / Indicators data),再切回常规 9 号字填单元格;Date 字段用 TimeToString(rates[0].time, TIME_DATE) 只显示年月日,宽度限定 90 像素防止压到下一列。 自定义事件(id>CHARTEVENT_CUSTOM)目前只做 PrintFormat 日志,预留了面板关闭按钮的点击入口。外汇与贵金属波动剧烈,这类悬浮面板仅作临场参考,信号滞后可能造成滑点亏损,实盘前请在策略测试器用历史数据跑通坐标转换逻辑。

MQL5 / C++
if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
  {
   class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them
   class="type">class="kw">datetime time=class="num">0;
   class="type">class="kw">double price=class="num">0;
   class="type">int wnd=class="num">0;
   class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time
   if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price))
     {
      class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable
      mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
      class=class="str">"cmt">//--- Display the bar data under the cursor on the panel 
      DrawData(mouse_bar_index,time);
      }
   }
class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
   {
   class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel
   PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display data from the specified timeseries index to the panel    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time)
  {
class=class="str">"cmt">//--- Declare the variables to receive data in them
   class="type">MqlRates rates[class="num">1];
class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1)
      class="kw">return;
class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers
   class="type">int   size=class="num">0;
   class="type">uint flags=class="num">0;
   class="type">uint angle=class="num">0;
   class="type">class="kw">string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,class="num">9,FW_BOLD);
   panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6);
   panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6);
class=class="str">"cmt">//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,class="num">9);
class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel
   panel.DrawText("Date",  panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString(  rates[class="num">0].time,TIME_DATE),      panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90);

◍ 把K线与指标值填进面板表格

在自建面板里逐格写数,核心是用 CellX/CellY 算坐标再 DrawText。下面这段把当前柱的 Time/Open/High/Low/Close 写进第 0 张表,坐标都加了 +2 像素偏移避免贴边。 时间用 TimeToString(rates[0].time,TIME_MINUTES) 只显示到分钟,价格用 DoubleToString(rates[0].open,Digits()) 跟随品种小数位;DrawText 最后一个参数 90 是文字对齐锚点,不是字号。 指标块另起一张表:先用 indicators.Title(handle_wpr1) 写列名,再用 GetData(handle_wpr1,0,0,index) 取指定偏移的 WPR 值。若返回 EMPTY_VALUE 则显示空格,否则按指标自身 Digits 格式化,对齐权重设 110 比价格列略宽。 开 MT5 把这段塞进你的 CPanel 绘制循环,把 handle_wpr1 换成实际指标句柄,就能在图表左上角实时看到裸价与摆动指标同框,省去切窗口比对的麻烦。外汇与贵金属波动剧烈,面板数值仅作参考,实际下单仍需结合风控。

MQL5 / C++
  panel.DrawText("Time",  panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES),  panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Open",  panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()),      panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("High",  panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()),      panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Low",   panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()),       panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90);
  panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()),     panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90);
class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1
  panel.DrawText(indicators.Title(handle_wpr1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value1=indicators.GetData(handle_wpr1,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_wpr1)) : " ");
  panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state
  panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2);

「双指标线态与相对位置的可视化输出」

这段逻辑把两个 WPR 类指标的当前柱状态塞进 GUI 表格,重点不在算信号,而在把「线态」和「相对位置」直接画出来供肉眼核对。外汇与贵金属市场波动剧烈,这类状态显示仅作辅助,实盘仍需结合风控。 先取第一个指标 handle_wpr1 在指定 index 柱的缓冲线状态 ENUM_LINE_STATE,用 BufferLineStateDescription 转成文字写进表格(1,1)单元格,偏移 +2 像素避免贴边。第二个指标 handle_wpr2 同理:Title 写表头,GetDataTo 取数值,非 EMPTY_VALUE 才用 DoubleToString 按 Digits 精度转字符串,否则留空格。 相对位置是这段的硬核点——取 handle_wpr2 在 index+1 柱的值 value21,连同当前柱 value2 一起喂给 BufferLineStateRelative,判定 handle_wpr1 相对 handle_wpr2 是上方、下方、下穿还是上穿。state_relative 字符串据此拼出「WPR1 > WPR2」或「Top-down crossing」等结论,写进 (2,2) 单元格。 最后 ChartRedraw(ChartID()) 强制重绘,否则面板不会实时刷新。开 MT5 把这段接进 OnCalculate 尾段,改 index 参数就能看不同历史柱的双指标博弈状态。

MQL5 / C++
  ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_wpr1,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2
  panel.DrawText(indicators.Title(handle_wpr2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2);
  class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_wpr2,class="num">0,class="num">0,index);
  class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_wpr2)) : " ");
  panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state
  panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2);
  ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_wpr2,class="num">0,class="num">0,index);
  panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line
  class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_wpr2,class="num">0,class="num">0,index+class="num">1);
  ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_wpr1,class="num">0,class="num">0,index,value2,value21);
  class="type">class="kw">string ma1=indicators.Name(handle_wpr1);
  class="type">class="kw">string ma2=indicators.Name(handle_wpr2);
  class="type">class="kw">string state_relative=
    (
      stateR==LINE_STATE_ABOVE       ? StringFormat("%s1 > %s2",ma1,ma2)  :
      stateR==LINE_STATE_BELOW       ? StringFormat("%s1 < %s2",ma1,ma2)          :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"  :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing" :
      BufferLineStateDescription(stateR)
    );
  panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2);
  panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110);

class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel
  ChartRedraw(ChartID());
}

跨周期取数失败时的强制切周期操作

多符号多周期指标里,CopyBuffer() 并非总能顺利拿到非当前图表品种或周期的数据。实测中它会因底层历史缺失直接返回 -1,而此时指标计算端其实早已把整段可用历史算完,只是缓冲区没被允许复制。 目前没有代码层根治办法,唯一奏效的动作是手动切换一次图表周期——比如从 M15 切到 H1 再切回,MT5 会重新挂载已算好的缓冲区,CopyBuffer() 随后就能正常返回数据。 外汇与贵金属市场高杠杆、跳空频繁,这类取数异常若发生在实盘 EA 的 OnTick 里,可能让信号延迟一两根 K 线。开 MT5 挂一个 TestMSTFRelativeStrengthIndex.mq5(28.65 KB)到 EURUSD 的 M5 上,再切到别的周期观察返回值,就能复现这个坑。

让小布替你跑这套
这些指标的颜色缓冲区封装逻辑小布盯盘已内置到跨周期诊断面板,打开对应品种页就能直接看多色状态,不用自己挨个写绘制代码。

常见问题

最多 64 种,通过独立的颜色索引数组与数据数组配合实现,超出部分会被截断。
可以,小布盯盘跨周期面板已预置常见指标的颜色缓冲区渲染,切换品种周期即可看到状态分色。
只复制了双精度数据数组,没同步绑定颜色索引数组到绘制缓冲区,需要在 SetIndexBuffer 时指定颜色类型。
每条线独立分配数据缓冲区和颜色缓冲区,按颌、齿、唇分别绑定索引,状态切换时改写对应颜色数组即可。
不会,只要每个周期实例维护自己的缓冲区数组,并在绘制前按句柄隔离复制,就能避免串色。