如何在另一指标的基础上编写一个指标·进阶篇
📘

如何在另一指标的基础上编写一个指标·进阶篇

第 2/2 篇

用 iCustom 把 TSI 拆成双色直方图

想把现有指标改头换面,最省事的办法是借壳:直接调用别人写好的指标,再按自己的逻辑重绘。以 TSI(真实强弱指数)为例,我们可以把它的主线与信号线之差画成直方图,配色逻辑照搬 AO、AC——数值走高染绿,走低染红,肉眼看动量翻转比看两条线交叉更直接。 外汇与贵金属市场杠杆高、滑点随机,这类重绘指标只作辅助判势,信号失效是常态,别拿它当进场铁律。 原文给出的入口函数是 iCustom(),它能在当前 EA 或指标里挂载一个外部自定义指标并取缓冲数据;与之并列的还有 IndicatorCreate(),但本节先只拆 iCustom 这条路径。下一步就是搞清楚函数签名里 buffer 序号和 timeframe 参数怎么填,才能在 OnCalculate 里拿到 TSI 的差值缓冲。

◍ 用 iCustom 把 TSIs 包成彩色直方图指标

在 MT5 里做自定义指标,先按 Ctrl+N 走 Custom Indicator 向导,名称填 TSIsCDiCust,外部参数先随便加一个,后面会从 TSIs 原指标整段抄过来。绘图方式选独立窗口、不限定最大最小值,加一个叫 TSIsCD 的彩色直方图缓冲区,颜色给绿和红;再补两个不绘图的缓冲区 Tsi 和 TsiSignal,专门存 TSIs 的数值。 向导生成的模板里 #property indicator_buffers 是 4,因为彩色直方图吃两个缓冲区(值+颜色),而 #property indicator_plots 要改回 1。Tsi 和 TsiSignal 那两对以 2、3 结尾的绘图属性全删,OnInit 里对应的 SetIndexBuffer 第三参由 INDICATOR_DATA 改成 INDICATOR_CALCULATIONS,indicator_color1 只留 Green,Red 两种。 把 TSIs 指标的外部变量原样搬进来替换掉默认 Input1,本例实际用的是 r=25、s=13、sp=5、sm=MODE_EMA。全局声明 int Handle,在 OnInit 末尾用 iCustom(_Symbol,PERIOD_CURRENT,"TSIs",r,s,sp,sm) 拿句柄——前两个参锁定当前品种与周期,第三个是自定义指标名,后面顺次排外部参数。 OnCalculate 要换回第一种形式(单数据缓冲区)。启动判据是 prev_calculated==0,首算柱索引 begin 由指标所需历史柱数定,循环从 start=begin+1 起;后续调用则从上次已处理柱 prev_calculated-1 再算,右界是 rates_total-1。用 CopyBuffer(Handle,idx,0,rates_total-start) 拉 TSIs 数据,返回 -1 就置静态 bool error 并直接退出,下次重算整段。 上色逻辑落在循环里:TSIsCD 值填 0 显绿、填 1 显红(按 indicator_color1 位置从零数)。相邻柱相等时沿用前柱 TsiCDColors 的颜色,向上动写绿、向下动写红。最后 OnInit 加 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin) 精确掐头,IndicatorSetInteger(INDICATOR_DIGITS,2) 对齐 TSIs 两位小数,编译挂图即可。外汇与贵金属杠杆高,这类指标仅作辅助,信号失效概率不低,实盘前务必在策略测试器跑一遍。

MQL5 / C++
class=class="str">"cmt">//--- Tsi 绘图
class="macro">#class="kw">property indicator_label2 "Tsi"
class="macro">#class="kw">property indicator_type2 DRAW_LINE
class="macro">#class="kw">property indicator_color2 Red
class="macro">#class="kw">property indicator_style2 STYLE_SOLID
class="macro">#class="kw">property indicator_width2 class="num">1
class=class="str">"cmt">//--- TsiSignal 绘图
class="macro">#class="kw">property indicator_label3 "TsiSignal"
class="macro">#class="kw">property indicator_type3 DRAW_LINE
class="macro">#class="kw">property indicator_color3 Red
class="macro">#class="kw">property indicator_style3 STYLE_SOLID
class="macro">#class="kw">property indicator_width3 class="num">1
SetIndexBuffer(class="num">2,TsiBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">3,TsiSignalBuffer,INDICATOR_CALCULATIONS);
class="macro">#class="kw">property indicator_color1 Green,Red
class=class="str">"cmt">//--- 输入参数
class="kw">input class="type">int r=class="num">25;
class="kw">input class="type">int s=class="num">13;
class="kw">input class="type">int sp=class="num">5;                 class=class="str">"cmt">// 平滑周期
class="kw">input ENUM_MA_METHOD sm=MODE_EMA; class=class="str">"cmt">// 平滑类型
Handle=iCustom(_Symbol,PERIOD_CURRENT,"TSIs",r,s,sp,sm);
class="type">int OnCalculate(const class="type">int rates_total,        class=class="str">"cmt">// price[] 数组的大小
                const class="type">int prev_calculated,   class=class="str">"cmt">// 在前一次调用中处理的柱数

「TSI 差值线的增量计算与容错起点」

在自定义指标里接管 TSI 主线与信号线后,真正产生交易视角的其实是两者的差(TsiCDBuffer)。上面这段代码把差值缓冲和配色逻辑放在 OnCalculate 的增量循环里,只对新增 K 线算一遍,而不是每次重算全历史,这是 MT5 指标不掉帧的关键。 注意 start 的取法:首次加载 prev_calculated==0 时走 begin+1,之后走 prev_calculated-1,保证前一根不被漏算。若 CopyBuffer 任意一条返回 -1,就把静态 error 置 true 并 return(0),下次调用从头拉数据,避免半截缓冲画出鬼线。 差值着色只看相邻两根谁大谁小——上升染 0、下降染 1,没有阈值判断,所以肉眼看色带翻转比读数值更直观。外汇和贵金属波动大,这种色带翻转只提示动量倾向,不代表方向确定性,实盘仍需结合结构。 最后两行设定了绘制的起始位移和小数点位数:PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s+sp) 把前置平滑周期让出来,IndicatorSetInteger(INDICATOR_DIGITS,2) 锁死两位小数,拷贝到你的指标里改 r/s/sp 三个参数就能直接看效果。

MQL5 / C++
const class="type">int begin,                 class=class="str">"cmt">// 大数据起点
      const class="type">class="kw">double &price[]       class=class="str">"cmt">// 用于计算的数组
      )
  {
   class="kw">return(rates_total);
  }
   class="kw">static class="type">bool error=true;
   class="type">int start;
   if(prev_calculated==class="num">0) class=class="str">"cmt">// 在开始指标后第一次执行OnCalculate()函数
     {
       error=true; class=class="str">"cmt">// 把值设为 True 用于计算所有柱
     }
   if(error) class=class="str">"cmt">// 如果 error=true, 说明是在开始指标后第一次执行
class=class="str">"cmt">// 或者之前开始时复制数据出错
     {
       start=begin+class="num">1;
       error=false;
     }
   else
     {
       start=prev_calculated-class="num">1;
     }
   if(CopyBuffer(Handle,class="num">0,class="num">0,rates_total-start,TsiBuffer)==-class="num">1) class=class="str">"cmt">// 复制指标主线的数据
     {
       error=true; class=class="str">"cmt">// 复制数据失败, 把error变量的值设为 True 准备下次调用
class=class="str">"cmt">// OnCalculate() 的时候全部重新计算指标
       class="kw">return(class="num">0);  class=class="str">"cmt">// 退出函数工作
     }
   if(CopyBuffer(Handle,class="num">1,class="num">0,rates_total-start,TsiSignalBuffer)==-class="num">1) class=class="str">"cmt">// 复制指标的信号线数据
     {
       error=true; class=class="str">"cmt">// 复制数据失败, 把 error 变量的值设为 true 准备下次调用
class=class="str">"cmt">// OnCalculate() 函数的时候全部重新计算指标
       class="kw">return(class="num">0);  class=class="str">"cmt">// 退出函数工作
     }
for(class="type">int i=start;i<rates_total;i++)
{
}
TsiCDBuffer[i]=TsiBuffer[i]-TsiSignalBuffer[i];
TsiCDColors[i]=TsiCDColors[i-class="num">1];
if(TsiCDBuffer[i]>TsiCDBuffer[i-class="num">1])TsiCDColors[i]=class="num">0;
if(TsiCDBuffer[i]<TsiCDBuffer[i-class="num">1])TsiCDColors[i]=class="num">1;
PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,r+s+sp);
IndicatorSetInteger(INDICATOR_DIGITS,class="num">2);

用 IndicatorCreate() 替换 iCustom() 的实操差异

把 TSIsCDiCust 复制成 TSIsCDiCreate 后,核心改动只有一处:原本调用 iCustom() 的地方,改成 IndicatorCreate()。两者前两个参数一样,都是 _Symbol 和 PERIOD_CURRENT,用来锁定计算品种和当前周期;第三个参数对自定义指标固定填 IND_CUSTOM。 MqlParam 结构里带 type 和三种值容器(double_value、integer_value、string_value),type 决定用哪种。TSIs 指标有 4 个外部参数,加上自定义指标名本身占数组第 0 位,所以 Params 要开 5 个元素。下面这段代码直接放在原 iCustom() 调用位即可。 MqlParam Params[5]; Params[0].type=TYPE_STRING; Params[0].string_value="TSIs"; // 在第一个参数中指定调用自定义指标的名称 Params[1].type=TYPE_INT; Params[1].integer_value=r; Params[2].type=TYPE_INT; Params[2].integer_value=s; Params[3].type=TYPE_INT; Params[3].integer_value=sp; Params[4].type=TYPE_INT; Params[4].integer_value=sm; Handle=IndicatorCreate(_Symbol,PERIOD_CURRENT,IND_CUSTOM,5,Params); 逐行看:第 0 元素 type 设字符串,装指标名 "TSIs";1 到 4 元素都是整型,依次塞入 r、s、sp、sm 四个外部参数;最后 IndicatorCreate() 第四个参数传 5,表示 Params 数组长度。 编译后到 MT5 客户端挂上指标,验证句柄 Handle 非零即代表创建成功。外汇与贵金属杠杆高、滑点随机,指标句柄异常时可能漏算,需自行核对图表数据。

MQL5 / C++
class="type">MqlParam Params[class="num">5];
  Params[class="num">0].type=TYPE_STRING;
  Params[class="num">0].string_value="TSIs"; class=class="str">"cmt">// 在第一个参数中指定调用自定义指标的名称
  
  Params[class="num">1].type=TYPE_INT;
  Params[class="num">1].integer_value=r;
  
  Params[class="num">2].type=TYPE_INT;
  Params[class="num">2].integer_value=s;  
  
  Params[class="num">3].type=TYPE_INT;
  Params[class="num">3].integer_value=sp;      
  
  Params[class="num">4].type=TYPE_INT;
  Params[class="num">4].integer_value=sm;  
Handle=IndicatorCreate(_Symbol,PERIOD_CURRENT,IND_CUSTOM,class="num">5,Params);

◍ 缓冲区和参数传递的收尾注意点

基于已有指标写自定义指标,最容易翻车的地方是指缓冲区声明和实际绘制对不上。indicator_buffers 和 indicator_plots 两个属性必须按真实用途填,多出来的计算缓冲别算进 plots 里,否则图表上要么空白要么重叠。 新缓冲区的属性(label、type、color、style、width)要在 OnInit 里一次定死,SetIndexBuffer() 的第三个参数 INDICATOR_DATA 与 INDICATOR_CALCULATIONS 不能混用——前者是拿去画线的,后者只是中间量。第一个参数的索引号也得和前面申请的缓冲序号严格一致,漏掉重新索引在某些起算点会直接错位。 跨指标调用时 iCustom() 的参数顺序、IndicatorCreate() 的参数字段结构,都得照着被调用指标的 input 顺序排。prev_calculated、rates_total、begin 这三个变量控制着从哪根柱开始算,MetaEditor 的 Errors 选项卡只管编译期报错,逻辑期越界它不拦。 下面这段是原文里读者反馈后补的 LWMA 权重修正,权重和必须预先累加再传参,否则编译报“错误参数计数”: int LWMA_weight=0; for(int j=1; j<=sp; j++) LWMA_weight+=j; // 将 LWMA_weight 增加 j LinearWeightedMAOnBuffer(rates_total,prev_calculated,begin2,sp,TSIBuffer,TSISigBuffer,LWMA_weight)

MQL5 / C++
class="type">int LWMA_weight=class="num">0;
for(class="type">int j=class="num">1; j<=sp; j++)
  LWMA_weight+=j; class=class="str">"cmt">// 将 LWMA_weight 增加 j
LinearWeightedMAOnBuffer(rates_total,prev_calculated,begin2,sp,TSIBuffer,TSISigBuffer,LWMA_weight)

常见问题

用 iCustom 获取 TSI 的两个线缓冲,再按正负值分别填到新指标的直方缓冲即可,注意 handle 失效要重取。
从两个序列都已有有效值的索引开始算差值,之前的位置直接留空或填 EMPTY_VALUE,避免误画零轴。
可以,把指标加载到小布对应品种页,它会用 AIGC 比对缓冲输出与原始 TSI 形态,标出异常柱。
IndicatorCreate() 直接建缓冲不跨指标调用,省 handle 管理;iCustom() 依赖原指标存在,改源码更灵活。
检查 SetIndexBuffer 的编号和 inputs 顺序是否一一对应,字符串参数要用 INDICATOR_DATA 以外类型显式声明。