测试当交易货币对篮子时出现的形态第二部分(基础篇)
◍ 多货币对同屏形态的观察起点
在 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 跑一遍计数再谈过滤。
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 跑一轮看曲线偏移量再定参数。
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 能直接看到穿线次数随参数漂移。
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;