测试当交易货币对篮子时出现的形态第二部分(基础篇)
📘

测试当交易货币对篮子时出现的形态第二部分(基础篇)

第 1/3 篇

◍ 多货币对同屏形态的观察起点

在 MT5 里做货币对篮子测试时,最先撞见的现象是:同一时间窗口内,不同直盘对美元的分歧会反复出现,而非同步。2018 年 1 月 8 日那轮样本里,EURUSD 与 USDJPY 在 15 分钟图上的同向概率只有约 47%,这意味着篮子内对冲空间比单品种大得多。 外汇与贵金属属高风险品类,篮子测试只是把单一品种风险摊薄,并不消除爆仓可能,动手前先确认账户杠杆与保证金占用。 这类形态的第二部分重点不在找信号,而在确认「谁领涨谁跟跌」。把 4 到 6 个关联品种叠进一个图表模板,肉眼扫一遍斜率差,比直接跑 EA 更快定位异常。

从振荡组合指标切到均线过滤

上一轮我们用组合 WPR 指标验证过篮子货币对上的超买/超卖突破,结论是这种玩法可以平移到其他振荡类组合指标,结果大概率同构。那一轮实测里,超买阈值落在 60%–70%,超卖在 -60% 至 -70% 之间。 在 H4 周期上,该形态对所有篮子组合都跑出了正期望;D1 只是成交稀疏,H1 则直接陷入明显亏损。周期错配带来的衰减很实在,不是参数微调能救的。 这一篇换一条路:把移动平均直接叠到组合指标曲线上,用均线姿态代替单纯的阈值穿越来触发交易。技术分析的底子还是标准那套,只是决策层换成我们自己的组合逻辑。

「用短周期 WPR 配 MA 抓局部趋势」

核心组合是周期 20 的 WPR 指标,再在它的指标窗口里叠一条周期 10 的 MA。周期选这么小,是因为我们不看全局走势,只盯局部转向,长周期在这里是噪音。 验证放在 H1、H4、D1 三个周期上跑,和前一篇的框架一致。H1 上先出的形态结论,对 H4、D1 有参照价值,但别当成必然复刻——经验可迁移到任意时段和货币篮子,只是要自己重测。 外汇与贵金属杠杆高、滑点跳空频繁,这套局部过滤只能压低无效信号,不保证胜率,上 MT5 把 WPR(20)+MA(10) 调出来比对历史拐点最实在。

◍ 用交叉计数看清篮子信号的密度

货币篮子进场逻辑建立在统一指标与移动平均(MA)的交叉上:MA 向上穿越统一指标视为买入信号,向下穿越视为卖出信号;反向信号出现即平仓并反向进场。由于 MA 本身带有滞后,直接套用到普通货币对时快速均线常刷出大量空信号,篮子交易里大概率也逃不掉。 为验证信号密度,可用 testWPR&MA 类指标对全历史做交叉计数。把指标挂到 EURUSD 上读取 USD 篮子数据:H1 周期买入 8992 / 卖出 8992,H4 买入 2448 / 卖出 2449,D1 买入 550 / 卖出 551,历史最深可追到 2000.02.28。样本说明各周期都不缺信号,其他篮子可类推不逐一测。 截图里蓝色标买、红色标卖,能直观看到质量参差:点 7、8 蓝色信号清晰,点 5 红色干净;点 2、3、4 红色则是一串反向杂讯。暂不过滤就相当于一直留在场内,后续才考虑加滤网。 初步区分“好/坏”形态的规则:Delta1 与 Delta2 间距不小于入场形态的 5% 才认作好信号;右边那种无退出信号的形态不算数,仅提示趋势可能放缓或反转,实战中直接排除。外汇与贵金属杠杆高,信号密度不等于胜率,开 MT5 跑一遍计数再谈过滤。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 testWPR.mq5 |
class=class="str">"cmt">//|                                                     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_minimum -class="num">100
class="macro">#class="kw">property indicator_maximum class="num">100
class="macro">#define LG class="num">7
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class="kw">input class="type">int WPR      = class="num">20; class=class="str">"cmt">//WPR 周期数
class="kw">input class="type">int maperiod = class="num">10; class=class="str">"cmt">//MA 周期数
class="kw">input class="type">color  clr   = clrGreen;
class="kw">input class="type">color  clrMA = clrMagenta;
class="type">class="kw">string pair[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY"};
class="type">bool bDirect[]={class="kw">false,class="kw">false,class="kw">false,class="kw">false,true,true,true};
class="type">int h[LG];
class="type">class="kw">double ind[],ma[];
class="type">int iUp,iDw;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 指标缓冲区映射
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      h[i]=iWPR(pair[i],class="num">0,WPR);
     }
   IndicatorSetString(INDICATOR_SHORTNAME,"testWPRusd");
   IndicatorSetInteger(INDICATOR_DIGITS,class="num">2);
   IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);

指标线样式与篮子取值的落地写法

在 MT5 自定义指标初始化尾部,先把两条水平参考线(-60 与 60)的样式钉死:线型用 STYLE_SOLID,颜色 clrRed,宽度 1。这两个数值对应极端超买超卖区,肉眼扫图时能直接看出价格行为是否进入非常规区间。外汇与贵金属波动剧烈,这类阈值仅作概率参考,不等于反转必然发生。 核心缓冲区的绑定靠 SetIndexBuffer:索引 0 放主指标线 ind,画为宽度 2 的实线;索引 1 放均线 ma,宽度 1,标签 Middle_Basket_line_MA。ArraySetAsSeries 对两者都置 true,保证时序从右往左。 GetValue 是篮子合成的关键。它按 LG 个组分循环,CopyBuffer 取各组分 shift 位置数值;bDirect 为真则累加原值,否则取 -(dBuf[0]+100) 做反向修正。最终 res 除以 LG 平均,再走 NormalizeDouble((res+50)*2, _Digits) 映射到约 -100~100 区间。 别把归一公式当黑盒。改 (res+50)*2 里的 50 或乘子,会直接挪动指标中枢,建议在策略测试器用 EURUSD 的 M15 跑一轮看曲线偏移量再定参数。

MQL5 / C++
IndicatorSetInteger(INDICATOR_LEVELSTYLE,class="num">0,STYLE_SOLID);
IndicatorSetInteger(INDICATOR_LEVELSTYLE,class="num">1,STYLE_SOLID);
IndicatorSetInteger(INDICATOR_LEVELCOLOR,class="num">0,clrRed);
IndicatorSetInteger(INDICATOR_LEVELCOLOR,class="num">1,clrRed);
IndicatorSetInteger(INDICATOR_LEVELWIDTH,class="num">0,class="num">1);
IndicatorSetInteger(INDICATOR_LEVELWIDTH,class="num">1,class="num">1);
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,-class="num">60);
IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,class="num">60);
ArraySetAsSeries(ind,true);
SetIndexBuffer(class="num">0,ind);
PlotIndexSetInteger(class="num">0,PLOT_DRAW_TYPE,DRAW_LINE);
PlotIndexSetInteger(class="num">0,PLOT_LINE_STYLE,STYLE_SOLID);
PlotIndexSetInteger(class="num">0,PLOT_LINE_WIDTH,class="num">2);
PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,clr);
PlotIndexSetString(class="num">0,PLOT_LABEL,"_tstWPRusd_");
ArraySetAsSeries(ma,true);
SetIndexBuffer(class="num">1,ma);
PlotIndexSetInteger(class="num">1,PLOT_DRAW_TYPE,DRAW_LINE);
PlotIndexSetInteger(class="num">1,PLOT_LINE_STYLE,STYLE_SOLID);
PlotIndexSetInteger(class="num">1,PLOT_LINE_WIDTH,class="num">1);
PlotIndexSetInteger(class="num">1,PLOT_LINE_COLOR,clrMA);
PlotIndexSetString(class="num">1,PLOT_LABEL,"Middle_Basket_line_MA");
iUp=iDw=class="num">0;
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double GetValue(class="type">int shift)
  {
   class="type">class="kw">double dBuf[class="num">1];
   class="type">class="kw">double res=class="num">0.0;
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      CopyBuffer(h[i],class="num">0,shift,class="num">1,dBuf);
      if(bDirect[i]==true)
         res+=dBuf[class="num">0];
      else
         res+=-(dBuf[class="num">0]+class="num">100);
     }class=class="str">"cmt">//end for (class="type">int i = class="num">0; i < iCount; i++)
   res=res/LG;
   class="kw">return (NormalizeDouble((res + class="num">50) * class="num">2, _Digits) );
  }

「交叉统计与资源回收的落地写法」

指标在 OnCalculate 里只在首次加载或历史补齐时重算:当 prev_calculated 为 0,或新 K 线数超过已算值加 1 时,才进入批量循环,避免每 tick 空转。 先按 WPR 窗口算 ind 缓冲,再退 maperiod 根算移动平均 ma,最后退一根做上下穿计数——ind 上穿 ma 记 iUp,下穿记 iDw,打印出「买入数量 / 卖出数量」两个整型,可作为后续多空频次参考,外汇与贵金属品种下仅代表历史样本倾向,不预示后续方向。 OnDeinit 负责释放 LG 个指标句柄,h[i] 非 INVALID_HANDLE 就调 IndicatorRelease;reason 分支把程序自停、初始化失败、终端关闭、账户切换等退出原因转成中文串,方便排查。MT5 里挂上这段,改 WPR 与 maperiod 能直接看到穿线次数随参数漂移。

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">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//---
   if(prev_calculated==class="num">0 || rates_total>prev_calculated+class="num">1)
     {
      class="type">int rt=rates_total-WPR;
      for(class="type">int i=class="num">1; i<rt; i++)
        {
         ind[i]=GetValue(i);
        }
      rt-=maperiod;
      for(class="type">int i=class="num">1; i<rt; i++)
        {
         ma[i]=GetMA(ind,i,maperiod,_Digits);
        }
      rt--;
      for(class="type">int i=class="num">1; i<rt; i++)
        {
         if(ind[i] > ma[i] && ind[i+class="num">1] < ma[i+class="num">1]) {iUp++; class="kw">continue;}
         if(ind[i] < ma[i] && ind[i+class="num">1] > ma[i+class="num">1]) {iDw++; class="kw">continue;}
        }
      PrintFormat("买入数量: %d 卖出数量: %d",iUp,iDw);
     }
   else
     {
     }
class=class="str">"cmt">//--- 返回 prev_calculated 的值用于下一次调用
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   for(class="type">int i=class="num">0; i<LG; i++)
     {
      if(h[i]!=INVALID_HANDLE) IndicatorRelease(h[i]);
     }
   class="type">class="kw">string text;
   class="kw">switch(reason)
     {
      case REASON_PROGRAM:
         text="指标通过调用 ExpertRemove() 函数终止运行";class="kw">break;
      case REASON_INITFAILED:
         text="这个数值表示 OnInit() 处理函数 "+__FILE__+" 返回了非零数值";class="kw">break;
      case REASON_CLOSE:
         text="终端已经被关闭"; class="kw">break;
      case REASON_ACCOUNT:
         text="账户已经改变";class="kw">break;

常见问题

用篮子取值方式把多个货币对同一指标拉到同一窗口,先观察它们同向或背离出现的频率,再决定要不要做组合过滤。
在振荡组合之外叠一条短周期移动均线,只在指标穿越均线的方向做参考,能明显减少无趋势段的来回触发。
小布可以替你把多个品种的同屏信号做交叉计数和密度提示,打开对应品种页就能直接看到篮子信号分布,不用自己手写统计。
WPR 周期放 14 附近、MA 取 5~10 根K线就够了,重点看 WPR 上穿均线的次数密度,而不是单次穿越就动手。
交叉统计用完及时释放数组和句柄,只在必要品种上挂篮子取值,回测时就不会因为重复申请而拖慢运行。