相关性在交易中的实际应用·进阶篇
(2/3)· 多数交易者只看价格方向,却漏掉了变量间依赖强度这一层概率信号
把两根均线和一张走势图当作全部依据,是日内交易者最常见的盲区。价格下跌途中偶尔冒出的反抽烛形,到底只是噪音还是趋势衰弱,肉眼很难给出概率意义上的判断。相关性这个统计量,恰好能补上「方向有了、强度未知」的那块拼图。
◍ Pearson 与 Spearman 在 OnCalculate 里的两套迭代逻辑
自定义指标里相关系数计算分两条路:第一版 OnCalculate 直接拿 price 原始序列喂给 PearsonCalc,循环从 i=0 到 limit 正向跑,每次取 rangeN 个价格点算线性相关系数,适合快速看价格自身的线性黏合度。 第二版换了 Spearman 思路,先把 price[i+k]*multiply 强转成 int 再 RankPrices 排名,最后 SpearmanCalc 用 sumd2+=MathPow(Ranks[i]-i-1,2) 累计秩差平方。它从 i=limit 倒序写到 0,避免重复覆盖缓冲区,外汇与贵金属价格序列常有跳空,秩相关比线性相关更抗异常值,但仍是概率性参考,实盘高风险。 两段都靠 prev_calculated 控制重算范围:首跑 limit=rates_total-2-rangeN-begin,后续只补算新柱。开 MT5 把 rangeN 设成 20、multiply 设 10000,能直接对比两种系数在 EURUSD 的 H1 上分歧。
class="type">class="kw">double PearsonCalc(class="type">class="kw">double &Ranks[],class="type">int N) { class="type">class="kw">double ch,zn; ch=Numerator(Ranks,N); zn=Denominator(Ranks,N); class="kw">return (ch/zn); } 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[] ) { if(rates_total<rangeN+begin) class="kw">return(class="num">0); class="type">int limit; if(prev_calculated>rates_total || prev_calculated<=class="num">0) { limit=rates_total-class="num">2-rangeN-begin; if(begin>class="num">0) PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin); } else limit=rates_total-prev_calculated; ArraySetAsSeries(price,true); for(class="type">int i=limit; i>=class="num">0; i--) { for(class="type">int k=class="num">0; k<rangeN; k++) PriceInt[k]=class="type">int(price[i+k]*multiply); RankPrices(TrueRanks,PriceInt); ExtLineBuffer[i]=SpearmanCalc(R2,rangeN); } class="kw">return(rates_total); } class="type">class="kw">double SpearmanCalc(class="type">class="kw">double &Ranks[],class="type">int N) { class="type">class="kw">double sumd2=class="num">0.0; for(class="type">int i=class="num">0; i<N; i++) sumd2+=MathPow(Ranks[i]-i-class="num">1,class="num">2);
「Kendall tau 指标的 OnCalculate 与计算内核」
这段 MT5 自定义指标把 Kendall 秩相关系数塞进价格序列里,用来衡量一段窗口内走势的单调协同程度。外汇与贵金属波动高、跳空频繁,这类非参数指标比皮尔逊相关更抗异常值,但信号滞后倾向随 rangeN 增大而上升。 OnCalculate 先卡掉数据不足的情况:rates_total 小于 rangeN+begin 直接返 0。首次计算时 limit 取 rates_total-2-rangeN-begin,若 begin>0 则通过 PlotIndexSetInteger 把绘图起点推到 rangeN+begin,避免前面空段出野值。 主循环里把 price 数组倒序(ArraySetAsSeries(price,true)),对每个 i 截取 rangeN 根价格进 PriceInt,调 KendallCalc 写缓冲。prev_calculated 正常时只补算新增 bar,limit=rates_total-prev_calculated,这是 MT5 指标不掉帧的标准写法。 KendallCalc 造一个严格递减序列 Y(N,N-1,…,1)当作时间秩,再调 MathCorrelationKendall(Ranks,Y,t) 返回 t。也就是说,它测的是‘价格高低顺序’与‘时间先后顺序’的一致概率,输出范围 -1 到 1,贴近 1 可能表示单调上行倾向。 开 MT5 把 rangeN 设 20 挂 EURUSD 小时图,肉眼比对指标拐头与波段的错位,能直接验证滞后量。
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[] ) { if(rates_total<rangeN+begin) class="kw">return(class="num">0); class="type">int limit; if(prev_calculated>rates_total || prev_calculated<=class="num">0) { limit=rates_total-class="num">2-rangeN-begin; if(begin>class="num">0) PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin); } else limit=rates_total-prev_calculated; ArraySetAsSeries(price,true); for(class="type">int i=class="num">0; i<=limit; i++) { for(class="type">int k=class="num">0; k<rangeN; k++) PriceInt[k]=price[k+i]; ExtLineBuffer[i]=KendallCalc(PriceInt,rangeN); } class="kw">return(rates_total); } class="type">class="kw">double KendallCalc(class="type">class="kw">double &Ranks[],class="type">int N) { class="type">class="kw">double Y[],t; ArrayResize(Y,N); class="type">int n=N; for(class="type">int i=class="num">0; i<N; i++) { Y[i]=n; n--; } MathCorrelationKendall(Ranks,Y,t); class="kw">return (t); }
Fechner 相关系数的循环与边界处理
这段逻辑先卡住数据量底线:当可用 K 线数小于 rangeN+begin 时直接返回 0,避免数组越界。首次计算(prev_calculated<=0 或异常)会把 limit 推到 rates_total-2-rangeN-begin,若 begin>0 则通过 PlotIndexSetInteger 告诉图表从第 rangeN+begin 根 K 线才开始画线。 后续刷新只处理新增部分,limit 等于 rates_total-prev_calculated,配合 ArraySetAsSeries(price,true) 把价格数组倒序,使得 i=0 对应最新一根 K 线。内层把 price[k+i] 拷进 PriceInt,再调 FechnerCalc 得到 ExtLineBuffer[i]。 FechnerCalc 本身不碰原始价格,而是拿排序名次 Ranks 和理想序列 Y(N、N-1…1)比符号。先求两者均值 mx、my,逐点打 markx/marky 为 ±1,同向记 Na、反向记 Nb,最终 res=(Na-Nb)/(Na+Nb),值域落在 -1 到 1。 在 MT5 里把 rangeN 设为 10 跑一遍,极限情况下若 10 个点全反向,缓冲区会写出 -1.0;全同向则 1.0。外汇与贵金属杠杆高、滑点随机,该系数只反映历史片段的同向概率,实盘前务必用策略测试器看不同品种的稳定性。
if(rates_total<rangeN+begin) class="kw">return(class="num">0); class="type">int limit; if(prev_calculated>rates_total || prev_calculated<=class="num">0) { limit=rates_total-class="num">2-rangeN-begin; if(begin>class="num">0) PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,rangeN+begin); } else limit=rates_total-prev_calculated; ArraySetAsSeries(price,true); for(class="type">int i=class="num">0; i<=limit; i++) { for(class="type">int k=class="num">0; k<rangeN; k++) PriceInt[k]=price[k+i]; ExtLineBuffer[i]=FechnerCalc(PriceInt,rangeN); } class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 计算 Fechner 相关系数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double FechnerCalc(class="type">class="kw">double &Ranks[],class="type">int N) { class="type">class="kw">double Y[],res,mx,my,sum=class="num">0.0,markx[],marky[]; class="type">class="kw">double Na=class="num">0.0,Nb=class="num">0.0; ArrayResize(Y,N); ArrayResize(markx,N); ArrayResize(marky,N); class="type">int n=N; for(class="type">int i=class="num">0; i<N; i++) { Y[i]=n; n--; } mx=Average(Y); my=Average(Ranks); for(class="type">int j=class="num">0; j<N; j++) { markx[j]=(Y[j]>mx)?class="num">1:-class="num">1; marky[j]=(Ranks[j]>my)?class="num">1:-class="num">1; if(markx[j]==marky[j]) Na++; else Nb++; } res=(Na-Nb)/(Na+Nb); class="kw">return (res); }
◍ 用相关系数振荡指标抓突破
做相关性策略,先得看清指标怎么算、滞后在哪。几乎所有相关性指标都不建议拉大周期,周期一大就滞后;价格急转时,它们解释的是已完成的反向波动,弱相关会直接暴露出来。 指标盯的不是当下价格,而是一段窗口内多值的总和,所以当前相关系数别当绝对估值,要看动态。用当前值与前期值做的振荡指标,才能识别突破倾向。 策略只做两件事:系数下降模式,正系数跌破0.3卖、负系数涨破+0.3买;系数增长模式,落负区破位卖、涨破对称正位买。截图里卖位0.3、买位0.3对称摆着,向下破卖、向上破买。 EA 里把计算方法(Pearson/Spearman/Kendall/Fechner)、策略类型(降/增)、关键水平、时间框架做成输入。初始化要卡一道:水平绝对值必须落在0到1之间,否则逻辑无意义。 外汇与贵金属杠杆高,相关系数突破只是概率信号,急反转中失效可能偏大,实盘前用 MT5 策略测试器跑一遍再上。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 运行模式的枚举 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum Strategy_type { DECREASE = class="num">1, class=class="str">"cmt">//On Decrease INCREASE class=class="str">"cmt">//On Increase }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 相关性计算方法的枚举 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum Corr_method { PEARSON = class="num">1, class=class="str">"cmt">//Pearson SPEARMAN, class=class="str">"cmt">//Spearman KENDALL, class=class="str">"cmt">//Kendall FECHNER class=class="str">"cmt">//Fechner }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA 交易输入参数 | class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">class="kw">string Inp_EaComment="Correlation Strategy"; class=class="str">"cmt">//EA 注释 input class="type">class="kw">double Inp_Lot=class="num">0.01; class=class="str">"cmt">//手数 input MarginMode Inp_MMode=LOT; class=class="str">"cmt">//资金管理 class=class="str">"cmt">//--- 相关性的计算 input Corr_method Inp_Corr_method=class="num">1; class=class="str">"cmt">//相关性方法 input Strategy_type Inp_Strategy_type=class="num">1; class=class="str">"cmt">//策略类型 class=class="str">"cmt">//--- EA 参数 input class="type">class="kw">string Inp_Str_label="===EA parameters==="; class=class="str">"cmt">//标签 input class="type">int Inp_MagicNum=class="num">1111; class=class="str">"cmt">//幻数
「把相关性指标接进EA的初始化与 tick 逻辑」
下面这段初始化代码把外部相关性指标挂进 EA,并顺手做了参数合法性校验。关键水平 KeyLevel 被限制在 0~1 之间,超出就直接 INIT_FAILED,避免后续拿非法阈值去算信号。 相关系数方法用 switch 分发:1=Pearson、2=Spearman、3=Kendall、4=Fechner,分别对应 Correlation 目录下的自定义指标。改方法只需换 Inp_Corr_method 数字,不用动底层调用。 iCustom 拿句柄时若返回 INVALID_HANDLE,会打印错误码并终止初始化。实盘里若指标路径不符(例如没把 Corr 文件放进 MQL5/Indicators/Correlation),这里就会卡死,开 MT5 前先确认目录。 OnTick 里先调 GetIndValue 取数,再用 Trade.IsOpenedByMagic 判断同魔术码持仓。无持仓且出买入信号才去开单——这套结构让你复制时只需补信号函数,风控框架已经现成。外汇与贵金属杠杆高,参数 40/60 点止损止盈仅作示例,需按品种波动重设。
input class="type">int Inp_StopLoss=class="num">40; class=class="str">"cmt">//止损(点数) input class="type">int Inp_TakeProfit=class="num">60; class=class="str">"cmt">//获利(点数) class=class="str">"cmt">//--- 指标参数 input class="type">int Inp_RangeN=class="num">10; class=class="str">"cmt">//范围计算 input class="type">class="kw">double Inp_KeyLevel=class="num">0.2; class=class="str">"cmt">//关键水平 input ENUM_TIMEFRAMES Inp_Timeframe=PERIOD_CURRENT; class=class="str">"cmt">//工作时间框架 class=class="str">"cmt">//--- 检查关键水平的正确性 if(Inp_KeyLevel>class="num">1 || Inp_KeyLevel<class="num">0) { Print(Inp_EaComment,": 错误的关键水平!"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">switch(Inp_Corr_method) { case class="num">1: ind_type="Correlation\\PearsonCorrelation"; class="kw">break; case class="num">2: ind_type="Correlation\\SpearmanCorrelation"; class="kw">break; case class="num">3: ind_type="Correlation\\KendallCorrelation"; class="kw">break; case class="num">4: ind_type="Correlation\\FechnerCorrelation"; class="kw">break; class="kw">default: class="kw">break; } class=class="str">"cmt">//--- 取得指标句柄 InpInd_Handle=iCustom(Symbol(),Inp_Timeframe,ind_type,Inp_RangeN); if(InpInd_Handle==INVALID_HANDLE) { Print(Inp_EaComment,": Failed to get indicator handle"); Print("Handle = ",InpInd_Handle," error = ",GetLastError()); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易分时函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 取得用于计算的数据 if(!GetIndValue()) class="kw">return; if(!Trade.IsOpenedByMagic(Inp_MagicNum)) { class=class="str">"cmt">//--- 如果有买入信号,开启订单