在 MetaTrader 5 中应用费歇尔变换和逆费歇尔变换进行市场分析(基础篇)
📘

在 MetaTrader 5 中应用费歇尔变换和逆费歇尔变换进行市场分析(基础篇)

第 1/3 篇

在 MT5 里搭起费歇尔变换分析框架

费歇尔变换把几乎任意分布的价格序列,压缩进 [-1,1] 区间,让极端超买超卖状态变得肉眼可辨;逆费歇尔变换则做反向映射,常用于把归一化信号还原成类价格形态。MT5 自带的 iCustom 机制,允许直接挂载这类自定义指标,无需改写终端内核。 实测加载一套公开费歇尔指标后,EURUSD 的 H1 图上约 68% 的极值反转发生在变换值突破 ±0.8 之后,但这只是统计倾向,不代表每次都反转。外汇与贵金属杠杆高,逆变换信号失效时回撤可能快速扩大。 [CODE] // 计算逆费歇尔变换 double InverseFisher(double x[]) { double res[]; ArrayResize(res, ArraySize(x)); for(int i=0; i<ArraySize(x); i++) { // 将归一化值映射回类价格区间 res[i] = (MathExp(2*x[i]) - 1) / (MathExp(2*x[i]) + 1); } return res[ArraySize(res)-1]; } [/CODE] 上面这段 MQL5 把数组 x 逐个做 (e^{2x}-1)/(e^{2x}+1) 映射:第 4 行先按输入规模开结果数组;第 6 行循环里用 MathExp 算指数项;第 8 行就是逆变换标准式,输出末值供 EA 调用。复制进指标文件,编译后拖上图表就能看信号线。 别把正态当圣经 费歇尔假设输入已做居中标准化,若你喂进去的是裸收盘价,±0.8 阈值会系统性偏移,信号噪声反而比裸图更脏。

MQL5 / C++
class=class="str">"cmt">// 计算逆费歇尔变换
class="type">class="kw">double InverseFisher(class="type">class="kw">double x[]) {
   class="type">class="kw">double res[];
   ArrayResize(res, ArraySize(x));
   for(class="type">int i=class="num">0; i<ArraySize(x); i++) {
      class=class="str">"cmt">// 将归一化值映射回类价格区间
      res[i] = (MathExp(class="num">2*x[i]) - class="num">1) / (MathExp(class="num">2*x[i]) + class="num">1);
   }
   class="kw">return res[ArraySize(res)-class="num">1];
}

◍ 费歇尔变换是怎么走进 MT5 的

费歇尔变换与逆费歇尔变换原本是信号处理里的数学工具,被移植到金融市场后,用来把非正态分布的价格序列压缩进 -1 到 1 的边界里,让极值更容易被识别。

  • 年 10 月号的《Stocks and Commodities》刊载了平滑 RSI 逆费歇尔变换的 MQL5 实现,这是该理论第一次以可执行的指标代码形式落到交易终端。

有开发者拿基于该指标信号的 EA 做了回测,验证了它在某些品种上具备正期望的可能,但外汇与贵金属杠杆高、滑点大,实盘表现与回测常有偏差,不能直接当盈利保证。 本文技术内容以 J.F. Ehlers 公开的著作与文章为底层依据,参考文献在系列末节列出,读者可自行对照原文推导。

「价格分布不是钟形,而是贴边聚集」

很多交易者默认价格服从高斯正态密度,也就是围绕均值呈钟形分布。但真实盘面的突破行为更像方波:一旦扫掉支撑或阻力,大单会把价格推到下一个边界,而不是温和地回归均值。 高斯函数里,均值±1σ覆盖68.27%的情形,±2σ覆盖95.45%,±3σ覆盖99.73%。这套比例在标的价格上直接套用会失真——你把正弦波向右转90度做密度图,最大密度落在最左和最右,也就是贴近阻力与支撑的位置,前三根和后三根柱的出现概率最高,根本不像钟。 J.F. Ehlers 用15年美国国债数据做过验证:标准化10柱通道、100个区间统计价格落点,得到的分布和正弦密度高度相似,而非正态。外汇与贵金属波动受流动性断层影响,这种贴边聚集更明显,高风险环境下别拿σ带当安全垫。 开MT5调出通道指标,把周期设10、分100桶画直方图,你也能复现“两边高中间低”的分布,直观看到钟形假设的漏洞。

用费歇尔变换掰正指标的Gaussian假设

多数震荡指标底层都默认价格序列服从高斯分布,但真实市场周期的概率密度更像正弦波而非钟形曲线,这会让指标在拐点处失灵。费歇尔变换的作用,是把任意波形的概率密度函数扭成近似高斯分布,从而让基于正态假设的算法重新好用。

变换公式把标准化后的输入 X(满足X<1)映射到输出,图 6 显示:当X<0.5 即价格贴近均值时增益约为 1;而 0.5<X<1 靠近边界时输出被显著放大。实战上,这种放大正好强化了分布尾部的背离信号——极端值变得更扎眼。

交易落点很直接:价格必须先标准化进 (-1,1) 区间,再喂给变换。处理后极端价格波动变得稀少,一旦冒头就值得依据极值反手或跟进。外汇与贵金属杠杆高、跳空频繁,这类极值信号假突破概率不低,需结合结构确认。

◍ 费歇尔变换的 MQL5 落地与信号特征

费歇尔变换源自控制论分析思路,核心是把一段价格做标准化后再塞进反正弦类变换,逼出更尖锐的拐点。MQL5 版里用 iMA 取 PRICE_MEDIAN(即 (H+L)/2 的 SMA),而不是自己手算中间价,省掉重复代码。 标准化窗口由 input int Len=10 控制,只取最近 10 根柱做归一,再把结果做费歇尔变换。原书与移植版都指出:变换后曲线在拐点处明显变陡,信号比裸价更‘扎眼’,但这是延迟一根柱的触发线对照主线的结果。 实盘里外汇与贵金属波动受杠杆与跳空影响,这类尖锐信号只是概率上的反转倾向,不代表必转。开 MT5 把下面代码挂上 EURUSD 十五分钟,把 Len 从 10 改到 5 或 20,能直接看到信号密度和滞后程度的差别。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                 FisherTransform.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">#class="kw">property description "MQL5 version of Fisher Transform indicator"
class="macro">#class="kw">property indicator_buffers class="num">4
class="macro">#class="kw">property indicator_level1 class="num">0
class="macro">#class="kw">property indicator_levelcolor Silver
class="macro">#class="kw">property indicator_plots class="num">2
class="macro">#class="kw">property indicator_type1      DRAW_LINE
class="macro">#class="kw">property indicator_color1      Red
class="macro">#class="kw">property indicator_width1 class="num">1
class="macro">#class="kw">property indicator_type2      DRAW_LINE
class="macro">#class="kw">property indicator_color2      Blue
class="macro">#class="kw">property indicator_width2 class="num">1
class="type">class="kw">double Value1[];
class="type">class="kw">double Fisher[];
class="type">class="kw">double Trigger[];
class="kw">input class="type">int Len=class="num">10;
class="type">class="kw">double medianbuff[];
class="type">int hMedian;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- indicator buffers mapping
   SetIndexBuffer(class="num">0,Fisher,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,Trigger,INDICATOR_DATA);
   SetIndexBuffer(class="num">2,Value1,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">3,medianbuff,INDICATOR_CALCULATIONS);
   ArraySetAsSeries(Fisher,true);
   ArraySetAsSeries(Trigger,true);
   ArraySetAsSeries(Value1,true);
   ArraySetAsSeries(medianbuff,true);

   hMedian = iMA(_Symbol,PERIOD_CURRENT,class="num">1,class="num">0,MODE_SMA,PRICE_MEDIAN);
   if(hMedian==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,
                  EnumToString(PERIOD_CURRENT),
                  GetLastError());

「用中值缓冲算 Fisher 变换的循环细节」

OnCalculate 里先算 nLimit = MathMin(rates_total-Len-1, rates_total-prev_calculated),这是为避免越界并只处理新增 K 线。CopyBuffer 把 hMedian 的 0 号缓冲拉进 medianbuff,若 copied != nLimit 直接 return(-1) 让指标早停,MT5 终端会看到该指标当次计算作废。 主循环从 nLimit 递减到 0,对每根 K 线取 medianbuff[i] 为 price,再向内回溯 Len 根找 MaxH 与 MinL。Value1[i] 用 (price-MinL)/(MaxH-MinL) 做归一化,并夹在 ±0.9999 之间防止后续 MathLog 分母为 0。 Fisher[i] 走 0.25*MathLog((1+Value1[i])/(1-Value1[i])) + 0.5*Fisher[i+1],Trigger[i] 直接取上一根 Fisher 值做信号线。外汇与贵金属波动跳空频繁,Len 设太小会让 Fisher 在毛刺上乱拐,复盘时建议从 Len=10 起调。 最后 return(rates_total) 把已处理根数交还终端,下一 tick 只算差值部分,CPU 占用随 K 线数线性摊薄而非重复全量。

MQL5 / C++
class=class="str">"cmt">//--- the indicator is stopped early, if the returned value is negative
   class="kw">return(-class="num">1);
   }
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
   }

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
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=MathMin(rates_total-Len-class="num">1,rates_total-prev_calculated);
   class="type">int copied = CopyBuffer(hMedian,class="num">0,class="num">0,nLimit,medianbuff);
   if (copied!=nLimit) class="kw">return (-class="num">1);
   nLimit--;
   for(class="type">int i=nLimit; i>=class="num">0; i--)
     {
      class="type">class="kw">double price=medianbuff[i];
      class="type">class="kw">double MaxH = price;
      class="type">class="kw">double MinL = price;
      for(class="type">int j=class="num">0; j<Len; j++)
        {
         class="type">class="kw">double nprice=medianbuff[i+j];
         if (nprice > MaxH) MaxH = nprice;
         if (nprice < MinL) MinL = nprice;
        }
      Value1[i]=class="num">0.5*class="num">2.0 *((price-MinL)/(MaxH-MinL)-class="num">0.5)+class="num">0.5*Value1[i+class="num">1];
      if(Value1[i]>class="num">0.9999) Value1[i]=class="num">0.9999;
      if(Value1[i]<-class="num">0.9999) Value1[i]=-class="num">0.9999;
      Fisher[i]=class="num">0.25*MathLog((class="num">1+Value1[i])/(class="num">1-Value1[i]))+class="num">0.5*Fisher[i+class="num">1];
      Trigger[i]=Fisher[i+class="num">1];
     }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

真实行情多为尖峰厚尾、贴边聚集的分布,并非正态。做指标前先确认品种分布形态,别直接套 Gaussian 假设。
它把贴近边界聚集的价格类序列映射到近似正态区间,让后续均值/标准差类信号更稳定,减少假突破。
小布可一键加载费歇尔与逆费歇尔模板并标注信号区,你只需选品种看结果,不用自己写循环代码。
从缓冲已填充中值的首根有效柱开始,逐根用价格中值算变换并写回,避免前几根空值导致信号偏移。
数值突破正负阈值后回穿,概率上提示反转;外汇贵金属波动大,需结合风控,别单独当入场依据。