在 MetaTrader 5 中应用费歇尔变换和逆费歇尔变换进行市场分析·进阶篇
📘

在 MetaTrader 5 中应用费歇尔变换和逆费歇尔变换进行市场分析·进阶篇

第 2/3 篇

逆费歇尔变换怎么给振荡指标加锐度

把费歇尔变换等式反解,用输出 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) 的定义式与变换等式完全吻合,所谓「神秘指标」只是换了个名字。 外汇和贵金属杠杆高、滑点大,这套变换只解决信号形态压缩,不替代仓位与止损规则,实盘前请在模拟盘跑够样本。

MQL5 / C++
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 报错并带出错误码。外汇与贵金属波动剧烈,句柄创建失败常在品种休市或周期异常时出现,需自行查错。

MQL5 / C++
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 对短线反转的响应变快。

MQL5 / C++
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 用历史数据跑一遍验证边界。

MQL5 / C++
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 的敏感度变化,外汇品种杠杆高,信号失真时止损要跟紧。

MQL5 / C++
   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);
   }

常见问题

它对平滑后的RSI做非线性映射,把集中在中轴的波动拉伸到两端,让超买超卖切换更锐利、更早出现信号。
容易漏掉给16个缓冲区逐一SetIndexBuffer排好队,漏了会导致彩虹均线和RSI级联计算写错位置或直接报错。
可以,小布能直接对对应品种做RSI双EMA合成与逆变换的级联计算落点检查,并把正负向波动递推结果标在盘面上。
关键是用两条不同周期EMA合成RSI数组,再喂给逆费歇尔变换,周期差决定了信号灵敏度和噪音量的平衡。
多是递推起点没接上前一根的正负向值,或缓冲写入索引偏移了一根,核对初始化队列和循环下标即可修正。