在 MetaTrader 5 中应用费歇尔变换和逆费歇尔变换进行市场分析·进阶篇
逆费歇尔变换怎么给振荡指标加锐度
| 把费歇尔变换等式反解,用输出 y 倒推输入 x,就得到逆费歇尔变换。图形上它是一条在中间近似线性、两端快速压扁的曲线:当 | x | >2 时输出被夹到 ±1,而 | x | <1 时输出和输入几乎同形。 |
|---|
这套压缩特性正好适合做振荡指标的底子。把经过滤波预处理的指标值喂进去,输出很大概率会贴到 +1 或 -1 两端,而不是在中间黏糊。 对外汇、贵金属这类高波动品种来说,这种两端极化能直接转成更干净的买卖触发:越过 +1 倾向超买、跌破 -1 倾向超卖,但周期错配时假信号概率会明显上升,实战需配合周期识别。
「把平滑RSI逆费歇尔变换搬进MT5」
Sylvain Vervoort 在 2010 年 10 月《Stocks and Commodities》发表的平滑 RSI 逆费歇尔变换,已重写成 MQL5 指标,并接了交易信号模块和 EA。MT5 原生没有 iRSIOnArray,所以代码里自己补了这个函数。 针对 EURUSD 1 小时周期,我把默认 RSIPeriod 设成 21、EMAPeriod 设成 34,比原版的 4/4 在该周期上表现更顺。你打开指标把这两个参数改回 4/4,能直接对比两种设置的信号密度差异。 从数学上看,费歇尔变换其实就是反双曲正切 arctanh(x),逆费歇尔变换即双曲正切 tanh(x)。推导链很直接:由欧拉公式引出双曲函数,tanh(x) 的定义式与变换等式完全吻合,所谓「神秘指标」只是换了个名字。 外汇和贵金属杠杆高、滑点大,这套变换只解决信号形态压缩,不替代仓位与止损规则,实盘前请在模拟盘跑够样本。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SmoothedRSIInverseFisherTransform.mq5 | class=class="str">"cmt">//| Copyright class="num">2011, Investeo.pl | class=class="str">"cmt">//| http://www.investeo.pl | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2011, Investeo.pl" class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class="macro">#include <MovingAverages.mqh> class="macro">#class="kw">property description "MQL5 version of Silvain Vervoort&class="macro">#x27;s Inverse RSI" class="macro">#class="kw">property indicator_minimum -class="num">10 class="macro">#class="kw">property indicator_maximum class="num">110 class="macro">#class="kw">property indicator_buffers class="num">16 class="macro">#class="kw">property indicator_level1 class="num">12 class="macro">#class="kw">property indicator_level2 class="num">88 class="macro">#class="kw">property indicator_levelcolor Silver class="macro">#class="kw">property indicator_plots class="num">1 class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 LightSeaGreen class="macro">#class="kw">property indicator_width1 class="num">2 class="type">int ma_period=class="num">10; class=class="str">"cmt">// period of ma class="type">int ma_shift=class="num">0; class=class="str">"cmt">// shift ENUM_MA_METHOD ma_method=MODE_LWMA; class=class="str">"cmt">// type of smoothing ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE;class=class="str">"cmt">// type of price class="type">class="kw">double wma0[]; class="type">class="kw">double wma1[]; class="type">class="kw">double wma2[]; class="type">class="kw">double wma3[]; class="type">class="kw">double wma4[]; class="type">class="kw">double wma5[]; class="type">class="kw">double wma6[]; class="type">class="kw">double wma7[]; class="type">class="kw">double wma8[]; class="type">class="kw">double wma9[]; class="type">class="kw">double ema0[]; class="type">class="kw">double ema1[]; class="type">class="kw">double rainbow[]; class="type">class="kw">double rsi[]; class="type">class="kw">double bufneg[]; class="type">class="kw">double bufpos[]; class="type">class="kw">double srsi[]; class="type">class="kw">double fish[]; class="type">int hwma0; class="type">int wma1weightsum; class="type">int wma2weightsum; class="type">int wma3weightsum; class="type">int wma4weightsum; class="type">int wma5weightsum; class="type">int wma6weightsum; class="type">int wma7weightsum; class="type">int wma8weightsum; class="type">int wma9weightsum; class="kw">extern class="type">int RSIPeriod=class="num">21; class="kw">extern class="type">int EMAPeriod=class="num">34; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 初始化里把 16 个缓冲区排好队
自定义指标在 MT5 里跑之前,OnInit 必须先把所有数组绑定到绘图缓冲区。这段初始化一口气挂了 16 个 SetIndexBuffer:0 号是主输出 fish(INDICATOR_DATA),1~15 号全是中间计算用缓冲区,包含 10 条 WMA、2 条 EMA、RSI、SRSI 和 rainbow,类型统一标成 INDICATOR_CALCULATIONS,不对外绘图。 绑定完缓冲区后,逐一对 fish、wma0~wma9、ema0、ema1、rsi、srsi、rainbow 调用 ArraySetAsSeries(...,true),让时序按最新柱在 [0] 的规则排列。漏掉这步,后续用 [0] 取当前值会整段错位。 主图线通过 PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0) 把空值设为 0.0,IndicatorSetInteger(INDICATOR_DIGITS,2) 锁两位小数;接着用 iMA 建一条周期 2 的 WMA 句柄 hwma0,若返回 INVALID_HANDLE 就 PrintFormat 报错并带出错误码。外汇与贵金属波动剧烈,句柄创建失败常在品种休市或周期异常时出现,需自行查错。
class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0,fish,INDICATOR_DATA); SetIndexBuffer(class="num">1,wma0,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">2,wma1,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">3,wma2,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">4,wma3,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">5,wma4,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">6,wma5,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">7,wma6,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">8,wma7,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">9,wma8,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">10,wma9,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">11,rsi,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">12,ema0,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">13,srsi,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">14,ema1,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">15,rainbow,INDICATOR_CALCULATIONS); ArraySetAsSeries(fish,true); ArraySetAsSeries(wma0,true); ArraySetAsSeries(wma1,true); ArraySetAsSeries(wma2,true); ArraySetAsSeries(wma3,true); ArraySetAsSeries(wma4,true); ArraySetAsSeries(wma5,true); ArraySetAsSeries(wma6,true); ArraySetAsSeries(wma7,true); ArraySetAsSeries(wma8,true); ArraySetAsSeries(wma9,true); ArraySetAsSeries(ema0,true); ArraySetAsSeries(ema1,true); ArraySetAsSeries(rsi,true); ArraySetAsSeries(srsi,true); ArraySetAsSeries(rainbow,true); PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0); PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,class="num">0); class=class="str">"cmt">//--- sets drawing line empty value PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0); class=class="str">"cmt">//--- digits IndicatorSetInteger(INDICATOR_DIGITS,class="num">2); hwma0=iMA(_Symbol,PERIOD_CURRENT,class="num">2,ma_shift,ma_method,applied_price); if(hwma0==INVALID_HANDLE) { class=class="str">"cmt">//--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iMA indicator for the symbol %s/%s, error code %d", _Symbol,
彩虹均线与RSI的级联计算落点
这段 OnCalculate 只在新K线或首根加载时重算:用 CopyBuffer 拉取 hwma0 句柄数据到 wma0,随后连续九次调用 LinearWeightedMAOnBuffer,把 wma0→wma1→…→wma9 逐层做线性加权平滑,周期参数写死为 2。 真正的彩虹值落在 rainbow[i] 那一行:十层 WMA 按 5/4/3/2/1/1/1/1/1/1 的权重加总再除以 20.0,权重向近端倾斜,对近期价格变动更敏感。外汇与贵金属波动下,这种嵌套平滑可能让拐点滞后收敛,但噪声倾向被压低。 算完 rainbow 后立刻喂给 iRSIOnArray(RSI 周期由 RSIPeriod 控制,偏移 11),再经 ExponentialMAOnBuffer(EMA 周期 EMAPeriod,偏移 12)出 ema0。开 MT5 把 RSIPeriod 从默认 14 调到 8,能直接看到 ema0 对短线反转的响应变快。
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- class="type">int nLimit; if(rates_total!=prev_calculated) { CopyBuffer(hwma0,class="num">0,class="num">0,rates_total-prev_calculated+class="num">1,wma0); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma0,wma1,wma1weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma1,wma2,wma2weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma2,wma3,wma3weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma3,wma4,wma4weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma4,wma5,wma5weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma5,wma6,wma6weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma6,wma7,wma7weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma7,wma8,wma8weightsum); LinearWeightedMAOnBuffer(rates_total,prev_calculated,class="num">0,class="num">2,wma8,wma9,wma9weightsum); if(prev_calculated==class="num">0) nLimit=rates_total-class="num">1; else nLimit=rates_total-prev_calculated+class="num">1; for(class="type">int i=nLimit; i>=class="num">0; i--) rainbow[i]=(class="num">5*wma0[i]+class="num">4*wma1[i]+class="num">3*wma2[i]+class="num">2*wma3[i]+wma4[i]+wma5[i]+wma6[i]+wma7[i]+wma8[i]+wma9[i])/class="num">20.0; iRSIOnArray(rates_total,prev_calculated,class="num">11,RSIPeriod,rainbow,rsi,bufpos,bufneg); ExponentialMAOnBuffer(rates_total,prev_calculated,class="num">12,EMAPeriod,rsi,ema0);
「RSI 数组计算与双 EMA 合成的关键段」
在自定义指标里把 RSI 算到普通数组而非默认 buffer,核心函数是 iRSIOnArray。它先按 rates_total 重设 bpos、bneg 两个辅助数组,再做 as_series 标志位暂存与翻转,避免索引方向错乱导致数值偏移。 前半段用 ExponentialMAOnBuffer 算出 ema0、ema1 两条周期为 13 的指数均线,随后用 srsi[i]=ema0[i]+(ema0[i]-ema1[i]) 构造对称 RSI 的中间量,再经双曲正切式变换得到 fish[i]=((exp(2*srsi[i])-1)/(exp(2*srsi[i])+1)+1)*50,把输出压到 0–100 区间。 iRSIOnArray 的初值处理值得注意:当 ppos<=begin+period 时,前 begin 根及 begin 到 begin+period 的根 buffer 全置 0,再用窗口内 diff 的正负分别累加到 SumP、SumN,首根可见值取 bpos[begin+period]=SumP/period、bneg[begin+period]=SumN/period。外汇与贵金属波动大,这套计算在跳空时 diff 会骤变,SumN 可能瞬间翻倍,实盘前务必在 MT5 用历史数据跑一遍验证边界。
ExponentialMAOnBuffer(rates_total,prev_calculated,class="num">13,EMAPeriod,ema0,ema1); for(class="type">int i=nLimit; i>=class="num">0; i--) srsi[i]=ema0[i]+(ema0[i]-ema1[i]); for(class="type">int i=nLimit; i>=class="num">0; i--) fish[i]=((MathExp(class="num">2*srsi[i])-class="num">1)/(MathExp(class="num">2*srsi[i])+class="num">1)+class="num">1)*class="num">50; } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">/// Calculating RSI class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int iRSIOnArray(const class="type">int rates_total,const class="type">int prev_calculated,const class="type">int begin, const class="type">int period,const class="type">class="kw">double &price[],class="type">class="kw">double &buffer[],class="type">class="kw">double &bpos[],class="type">class="kw">double &bneg[]) { class="type">int i; class=class="str">"cmt">//--- check for data ArrayResize(bneg,rates_total); ArrayResize(bpos,rates_total); if(period<=class="num">1 || rates_total-begin<period) class="kw">return(class="num">0); class=class="str">"cmt">//--- save as_series flags class="type">bool as_series_price=ArrayGetAsSeries(price); class="type">bool as_series_buffer=ArrayGetAsSeries(buffer); if(as_series_price) ArraySetAsSeries(price,class="kw">false); if(as_series_buffer) ArraySetAsSeries(buffer,class="kw">false); class="type">class="kw">double diff=class="num">0.0; class=class="str">"cmt">//--- check for rates count if(rates_total<=period) class="kw">return(class="num">0); class=class="str">"cmt">//--- preliminary calculations class="type">int ppos=prev_calculated-class="num">1; if(ppos<=begin+period) { class=class="str">"cmt">//--- first RSIPeriod values of the indicator are not calculated for (i=class="num">0; i<begin; i++) { buffer[i]=class="num">0.0; bpos[i]=class="num">0.0; bneg[i]=class="num">0.0; } class="type">class="kw">double SumP=class="num">0.0; class="type">class="kw">double SumN=class="num">0.0; for(i=begin;i<=begin+period;i++) { buffer[i]=class="num">0.0; bpos[i]=class="num">0.0; bneg[i]=class="num">0.0; class=class="str">"cmt">//PrintFormat("%f %f\n", price[i], price[i-class="num">1]); diff=price[i]-price[i-class="num">1]; SumP+=(diff>class="num">0?diff:class="num">0); SumN+=(diff<class="num">0?-diff:class="num">0); } class=class="str">"cmt">//--- calculate first visible value bpos[begin+period]=SumP/period; bneg[begin+period]=SumN/period;
◍ 正负向波动的递推与缓冲写入
核心循环从 ppos 开始一直跑到 rates_total,每根 K 线先算相邻收盘价价差 diff=price[i]-price[i-1]。这个 diff 直接决定能量往正向还是负向桶里灌。 bpos 和 bneg 都用带衰减的滑动平均:拿上一根的值乘 (period-1),加上当前同向绝对价差,再除以 period。比如 period=10,上一根 bpos 是 0.002,diff 为 0.001,新 bpos=(0.002*9+0.001)/10=0.0019,旧值权重占九成。 只有 bneg[i] 大于 0.0000001 才写 buffer,公式是 0.1*((100-100/(1+bpos/bneg))-50)。把多空比塞进 0~100 的归一框架再减 50,缩放 0.1 倍后指标大致落在 -5 到 +5 之间。 循环结束把 price、buffer 的 as_series 标志按原样还原,函数返回 rates_total。开 MT5 把这段贴进自定义指标,调 period 看缓冲曲线对 EURUSD H1 的敏感度变化,外汇品种杠杆高,信号失真时止损要跟紧。
if(bneg[begin+period]>class="num">0.0000001) buffer[begin+period]=class="num">0.1*((class="num">100.0-class="num">100.0/(class="num">1+bpos[begin+period]/bneg[begin+period]))-class="num">50); class=class="str">"cmt">//--- prepare the position value for main calculation ppos=begin+period+class="num">1; } class=class="str">"cmt">//--- the main loop of calculations for(i=ppos;i<rates_total && !IsStopped();i++) { diff=price[i]-price[i-class="num">1]; bpos[i]=(bpos[i-class="num">1]*(period-class="num">1)+((diff>class="num">0.0)?(diff):class="num">0.0))/period; bneg[i]=(bneg[i-class="num">1]*(period-class="num">1)+((diff<class="num">0.0)?(-diff):class="num">0.0))/period; if (bneg[i]>class="num">0.0000001) buffer[i]=class="num">0.1*((class="num">100.0-class="num">100.0/(class="num">1+bpos[i]/bneg[i]))-class="num">50); class=class="str">"cmt">//Print(buffer[i]); } class=class="str">"cmt">//--- restore as_series flags if(as_series_price) ArraySetAsSeries(price,true); if(as_series_buffer) ArraySetAsSeries(buffer,true); class="kw">return(rates_total); }