交易货币篮子时可用的形态第二部分·进阶篇
📊

交易货币篮子时可用的形态第二部分·进阶篇

(2/3)·震荡指标只是起点,跟随趋势的组合指标藏着篮子交易里被忽略的入场信号

案例拆解 第 2/3 篇
把移动平均直接拖到价格图上看篮子强弱,是多数人的直觉错误。组合跟随趋势指标因量纲和缩放差异,根本没法像单边品种那样叠在K线上读。忽视这条限制,你会误把不可比的读数当成了信号。

「指标卸载时的退出原因分类」

这段 MQL5 代码展示了自定义指标在 OnDeinit 里如何根据 reason 参数区分终止场景,并用 PrintFormat 把对应文案打到日志。 reason 的枚举覆盖了从主动移除到环境变化的 11 种情况:REASON_PROGRAM 代表指标调用 ExpertRemove() 自终止;REASON_INITFAILED 说明 OnInit 返回了非零值;REASON_CLOSE 是终端关闭;REASON_ACCOUNT 是账户切换。 交易品种或周期变动会触发 REASON_CHARTCHANGE,图表被关是 REASON_CHARTCLOSE,改输入参数是 REASON_PARAMETERS,重新编译对应 REASON_RECOMPILE,从图表删掉是 REASON_REMOVE,套用新模板则是 REASON_TEMPLATE,其余归为 default 的「其它原因」。 GetMA 函数用最简滑动求和除以 period 算均线,并用 NormalizeDouble 按 _Digits 舍入;下方 pair 数组列了 7 个 EUR 交叉盘(EURUSD、EURJPY、EURCHF、EURGBP、EURNZD、EURCAD、EURAUD),bDirect 全为 true,可作为批量加载基准。外汇与贵金属波动剧烈,这类退出日志仅用于排查,不构成任何方向判断。

MQL5 / C++
  }
    }
  else
  {
      ind[class="num">0]= GetValue(class="num">0);
      ma[class="num">0] = GetMA(ind, class="num">0, maperiod, _Digits);
  }
class=class="str">"cmt">//--- 返回 prev_calculated 的值用于下一次调用
   class="kw">return(rates_total);
  }
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   class="type">class="kw">string text;
   class="kw">switch(reason)
     {
      case REASON_PROGRAM:
         text="指标通过调用 ExpertRemove() 函数终止运行";break;
      case REASON_INITFAILED:
         text="这个数值表示 OnInit() 处理函数 "+__FILE__+" 返回了非零数值";break;
      case REASON_CLOSE:
         text="终端已经被关闭"; break;
      case REASON_ACCOUNT:
         text="账户已经改变";break;
      case REASON_CHARTCHANGE:
         text="交易品种或者时段已经改变";break;
      case REASON_CHARTCLOSE:
         text="图表被关闭";break;
      case REASON_PARAMETERS:
         text="输入参数已经改变";break;
      case REASON_RECOMPILE:
         text="程序 "+__FILE__+" 被重新编译";break;
      case REASON_REMOVE:
         text="程序 "+__FILE__+" 被从图表上删除";break;
      case REASON_TEMPLATE:
         text="图表上应用了新的模板";break;
      class="kw">default:text="其它原因";
     }
   PrintFormat("%s",text);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double GetMA(class="kw">const class="type">class="kw">double& arr[], class="type">int index , class="type">int period, class="type">int digit) {
   class="type">class="kw">double m = class="num">0;
   for (class="type">int j = class="num">0; j < period; j++)  m += arr[index + j];
   m /= period;
   class="kw">return (NormalizeDouble(m,digit));
}
class="type">class="kw">string pair[]={"EURUSD", "EURJPY", "EURCHF", "EURGBP", "EURNZD", "EURCAD", "EURAUD"};
class="type">bool bDirect[]={true,true,true,true,true,true,true};

用 ZigZag 拆解美元指数摆动幅度

把 iUniZigZagPriceSW 指标挂到美元指数图上,之字转向会画成一条蓝色粗线。我们的目的是把每段 ZigZag 折线的长度按大小排序,借此摸清楚美元指数在历史样本里的「摆动幅度」分布。 原指标在第 10 个订单分时才锁定最大可能的片段长度,把它当 100% 基准,就能反推出 1% 步长对应的绝对价格,以及每个百分位区间里出现过多少次对应大小的片段。跑完把数组落盘,再用表格软件画图即可。 下表是文件开头的一部分(片段长度单位:指数点): 数量 片段长度 片段数量

  • 0.01193 2975
  • 0.02387 850
  • 0.0358 197
  • 0.04773 54
  • 0.05967 17

再往后的区间基本被 0 填满了,说明超大摆动极少。 顺着趋势进场时,若把 ZigZag 套在货币篮子指数组合上,某段折线在价格轴上的投影超过某个阈值,就要警惕反转或假突破概率上升。以这批数据看,「关键」片段长度宜设在 0.03 以内,具体可用统计方法按你的风险偏好重算。外汇与贵金属杠杆高,这类阈值只作过滤、不构成单边依据。 上面用的是全历史样本;想抓近期节奏,把统计窗口缩到年、季或月再跑一遍就行。同样套路能铺到所有货币篮子和多数周期,算法不复杂,但别只靠这一个信号,一定要等别的确认。ZigZag 类统计对趋势指标好用,对振荡指标没意义。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                 iUniZigZagSW.mq5 |
class=class="str">"cmt">//|    Copyright class="num">2016, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2016, MetaQuotes Software Corp."
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">3
class=class="str">"cmt">//--- 最高点绘图
class="macro">#class="kw">property indicator_label1  "High"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
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">//--- 最低点绘图
class="macro">#class="kw">property indicator_label2  "Low"
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">//--- 之字转向绘图
class="macro">#class="kw">property indicator_label3  "ZigZag"
class="macro">#class="kw">property indicator_type3   DRAW_SECTION
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">//--- 方向绘图
class="macro">#class="kw">property indicator_label4  "Direction"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_style4  STYLE_SOLID
class="macro">#class="kw">property indicator_width4  class="num">1
class=class="str">"cmt">//--- LastHighBar(最近的高点柱形) 绘图
class="macro">#class="kw">property indicator_label5  "LastHighBar"
class="macro">#class="kw">property indicator_type5   DRAW_LINE
class="macro">#class="kw">property indicator_style5  STYLE_SOLID
class="macro">#class="kw">property indicator_width5  class="num">1
class=class="str">"cmt">//--- LastLowBar(最近的低点柱形) 绘图
class="macro">#class="kw">property indicator_label6  "LastLowBar"
class="macro">#class="kw">property indicator_type6   DRAW_LINE
class="macro">#class="kw">property indicator_style6  STYLE_SOLID
class="macro">#class="kw">property indicator_width6  class="num">1

◍ 把方向判定接进 ZigZag 骨架

这一段干的事很直接:给 ZigZag 指标挂两套方向判定逻辑,用枚举切,不写死在代码里。Dir_NBars 走 N 根 K 线转折判定,Dir_CCI 走 14 周期 CCI 的典型价方向,输入参数里 CCIPeriod 默认 14、ZZPeriod 默认 14,都是可以直接在 MT5 参数面板改的数。 高亮的那行 input string name="index-usd-zz.txt" 是给外部数据文件留的接口,黄金或欧美盘你换品种时改这个文件名就行,别动其它逻辑。int h 那个变量在当前片段里没用上,是预留句柄,先别管。 OnInit 里按 DirSelect 分支 new 出对应方向对象,dir.CheckHandle() 失败就弹窗“指标 2 下载错误”并返回 INIT_FAILED——说明依赖的 CSorceData 等头文件必须先在 ZigZag 目录下就位,否则指标加载直接挂。 缓冲区映射把 0~2 号设为 INDICATOR_DATA(画出来的高低点和 ZigZag 线),3~5 号是 CALCULATIONS(方向、末高末低 Bar 索引),只内部算不显示。你开 MT5 把这段塞进自定义指标 OnInit,编译过且不出 Alert,就证明头文件路径对了。

MQL5 / C++
class="macro">#include <ZigZag\CSorceData.mqh>
class="macro">#include <ZigZag\CZZDirection.mqh>
class="macro">#include <ZigZag\CZZDraw.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum EDirection
  {
   Dir_NBars=class="num">0,
   Dir_CCI=class="num">1
  };
class=class="str">"cmt">//--- 输入参数
class="kw">input EDirection   DirSelect=Dir_NBars;
class="kw">input class="type">int          CCIPeriod   =  class="num">14;
class="kw">input ENUM_APPLIED_PRICE   CCIPrice     =  PRICE_TYPICAL;
class="kw">input class="type">int          ZZPeriod=class="num">14;
class="kw">input class="type">class="kw">string       name="index-usd-zz.txt";
CZZDirection*dir;
CZZDraw*zz;
class=class="str">"cmt">//--- 指标缓冲区
class="type">class="kw">double    HighBuffer[];
class="type">class="kw">double    LowBuffer[];
class="type">class="kw">double    ZigZagBuffer[];
class="type">class="kw">double    DirectionBuffer[];
class="type">class="kw">double    LastHighBarBuffer[];
class="type">class="kw">double    LastLowBarBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int h;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class="kw">switch(DirSelect)
     {
      case Dir_NBars:
        dir=new CNBars(ZZPeriod);
        break;
      case Dir_CCI:
        dir=new CCCIDir(CCIPeriod,CCIPrice);
        break;
     }
   if(!dir.CheckHandle())
     {
      Alert("指标 class="num">2 下载错误");
      class="kw">return(INIT_FAILED);
     }
   zz=new CSimpleDraw();
class=class="str">"cmt">//--- 指标缓冲区映射
   SetIndexBuffer(class="num">0,HighBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,LowBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,ZigZagBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">3,DirectionBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">4,LastHighBarBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">5,LastLowBarBuffer,INDICATOR_CALCULATIONS);

「指标析构与逐根 K 线的计算回路」

退出指标时先判断两个动态对象指针是否还活着,再分别 delete。dir 和 zz 若用 CheckPointer 返回 POINTER_DYNAMIC 才释放,能避免重复析构导致的 MT5 日志报错。 OnCalculate 里用 prev_calculated 控制起点:首根 K 线走 start=0,后续只从 prev_calculated-1 补算一根,这是标准增量计算写法,能把 EURUSD 这类品种在 M15 上的重绘开销压到最低。 代码把 price 同时写进 HighBuffer 和 LowBuffer 做占位,再交给 dir.Calculate 算方向、zz.Calculate 算之字形。rv 为 0 直接 return(0) 中断,说明方向缓冲区没就绪时绝不画 ZigZag。 开 MT5 把这段塞进自定义指标,把 zz 的回调深度参数从默认 12 调到 8,黄金 H1 上的拐点会密一倍但假突破也更多,属于高风险品种的高频噪音,自己跑一遍对比。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">int begin,
                class="kw">const class="type">class="kw">double &price[]
                )
  {
   class="type">int start;
   if(prev_calculated==class="num">0)
     {
      start=class="num">0;
     }
   else
     {
      start=prev_calculated-class="num">1;
     }
   for(class="type">int i=start;i<rates_total;i++)
     {
      HighBuffer[i]=price[i];
      LowBuffer[i]=price[i];
     }
   class="type">int rv;
   rv=dir.Calculate(rates_total,
                    prev_calculated,
                    HighBuffer,
                    LowBuffer,
                    DirectionBuffer);
   if(rv==class="num">0)class="kw">return(class="num">0);
   zz.Calculate(rates_total,
                prev_calculated,
                HighBuffer,
                LowBuffer,
                DirectionBuffer,
                LastHighBarBuffer,
                LastLowBarBuffer,
                ZigZagBuffer);

用 ZigZag 拐点距做百分百位数分布

当指标计数为 10 时,这段代码会扫描整段 ZigZagBuffer,先抓出全部有效拐点中的最高价与最低价,两者差值作为总振幅 lg。若某品种 ZigZag 极值分别为 1.05000 与 1.10000,则 lg=0.05000,除以 100 后得到每 1% 档位步长 0.00050。 随后它把相邻拐点间的绝对距离逐一比对 100 个档位阈值,落入哪一档就在 count[] 对应位置加一,本质是统计「任意两拐点间距小于某百分比」的频次。外汇与贵金属杠杆高、跳空频繁,这种分布仅反映历史形态,不预示后市方向。 最后循环把 0~99 档的档位值与计数通过 PrintFormat 打到日志,并写入文件句柄 h,FileClose 收尾。你可在 MT5 策略测试器跑完看日志,把 count 最高的档位当作该品种 ZigZag 常见折返密度的参考,再调 EA 的回调入场阈值。

MQL5 / C++
  if(ind<= class="num">10) ind++;
  if(ind == class="num">10)
    {
      class="type">class="kw">double mx=class="num">0,mn=class="num">1000000;
      class="type">class="kw">double lg;
      for(class="type">int i=class="num">0;i<rates_total;i++)
        {
         if(ZigZagBuffer[i]==class="num">0 || ZigZagBuffer[i]==EMPTY_VALUE) class="kw">continue;
         if(ZigZagBuffer[i] > mx) mx = ZigZagBuffer[i];
         if(ZigZagBuffer[i] < mn) mn = ZigZagBuffer[i];
        }
      lg=mx-mn;
      PrintFormat("Min index: %.05f Max index: %.05f Length: %.05f",mn,mx,lg);
      lg/=class="num">100;
      class="type">class="kw">double levels[class="num">100];
      class="type">int    count[class="num">100];
      ArrayInitialize(count,class="num">0);
      for(class="type">int i=class="num">1; i<class="num">101; i++) levels[i-class="num">1]=NormalizeDouble(lg*i,_Digits);
      mn=class="num">0;
      for(class="type">int i=class="num">0;i<rates_total;i++)
        {
         if(ZigZagBuffer[i]==class="num">0 || ZigZagBuffer[i]==EMPTY_VALUE) class="kw">continue;
         if(mn==class="num">0) mn=ZigZagBuffer[i];
         else
           {
            lg=MathAbs(mn-ZigZagBuffer[i]);
            for(class="type">int j=class="num">0; j<class="num">100; j++)
              {
               if(lg<levels[j])
                 {
                  count[j]++;
                  break;
                 }
              }
            mn=ZigZagBuffer[i];
           }
        }
      for(class="type">int i=class="num">0; i<class="num">100; i++)
        {
         PrintFormat("%d level: %.05f count: %d",i,levels[i],count[i]);
         FileWrite(h,i,levels[i],count[i]);
        }
      FileClose(h);
    }
  class="kw">return(rates_total);
}
让小布替你跑这套
这些组合指标的编译与交叉点统计,小布盯盘的 AIGC 已内置,打开对应篮子品种页即可看到预生成的趋势形态提示,你只需在烛形关闭时核对信号。

常见问题

平均构建原则下量纲与图表不同,移动平均、布林带等无法作父指标,放主图无意义且易读错。
经预先检验,ADX与StdDev符合需求,其余因缩放无界或单位冲突被排除。
不能,需两个独立窗口指标分别显示两侧货币,因缩放尺度差异无法合并计算。
可以,对应品种页已内置该类AIGC诊断,省去手动编译testIndexMA与计数的重复劳动。
能,本篇后续用其压缩波动结构,辅助判断趋势段落而非精确拐点,外汇贵金属属高风险。