您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换·综合运用
🌊

您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换·综合运用

(3/3)·从频域视角重看行情曲线,用离散变换把重复形态从噪声里拎出来

实战向 第 3/3 篇
很多交易者盯着裸 K 找周期,却忽略价格序列本身就是多频率叠加的波。直接用肉眼切分趋势和震荡,容易把噪声当成信号。傅里叶变换给你一套把波形拆开重装的工具,先看清成分再动手。

多空双向的 FT trailing 止损判定

这套 trailing 逻辑对多单和空单分别写了两个方法,核心都是用傅里叶变换输出值 _ft 除以 100 得到 _type,再乘以 ATR 代理量来算新的止损偏移。ATR 代理量取「2 倍点差点值」与「当前 K 线振幅」的较大者,所以点差异常放大时止损距离不会塌缩到不合理。 double _atr=fmax(2.0*m_spread.GetData(_x)*m_symbol.Point(),m_high.GetData(_x)-m_low.GetData(_x))*(_type); 这句先算基础波动代理:点差成本和保护性振幅二选一取大,再乘 _type 缩放。多单里 _sl=m_low.GetData(_x)-(m_step*_atr) 把止损挂在 K 线最低点下方 m_step 个波动单位;空单则是 _sl=m_high.GetData(_x)+(m_step*_atr) 挂在最高点上方。 broker 限制用 StopsLevel 卡死:多单要求 new_sl 小于 bid-StopsLevel*Point 的 level,且比原止损(或开仓价)更高才生效;空单对称,要求 new_sl 大于 ask+StopsLevel*Point 且比原基准更低。外汇与贵金属杠杆高,StopsLevel 在重大数据前可能临时扩大,回测里跑通的步长实盘可能直接被拒。 别把正态当圣经 _process 返回的 _ft 若长期贴近某均值,_type 会稳定,但行情跳变时 _ft/100 可能瞬间拉大止损步距,这时候 m_step 要调小而不是迷信历史参数。

MQL5 / C++
class="type">class="kw">double _atr=fmax(class="num">2.0*m_spread.GetData(_x)*m_symbol.Point(),m_high.GetData(_x)-m_low.GetData(_x))*(_type);

class="type">class="kw">double _sl=m_low.GetData(_x)-(m_step*_atr);

class="type">class="kw">double level =NormalizeDouble(m_symbol.Bid()-m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
class="type">class="kw">double new_sl=NormalizeDouble(_sl,m_symbol.Digits());
class="type">class="kw">double pos_sl=position.StopLoss();
class="type">class="kw">double base =(pos_sl==class="num">0.0) ? position.PriceOpen() : pos_sl;

sl=EMPTY_VALUE;
tp=EMPTY_VALUE;
if(new_sl>base && new_sl<level)
   sl=new_sl;
class=class="str">"cmt">//---
  class="kw">return(sl!=EMPTY_VALUE);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">short position.          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::CheckTrailingStopShort(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp)
  {
class=class="str">"cmt">//--- check
   if(position==NULL)
      class="kw">return(class="kw">false);

   m_high.Refresh(-class="num">1);
   m_low.Refresh(-class="num">1);
   
   class="type">int _x=StartIndex();
   
   class="type">class="kw">double _ft=ProcessFT(_x);
   class="type">class="kw">double _type=_ft/class="num">100.0;

   class="type">class="kw">double _atr=fmax(class="num">2.0*m_spread.GetData(_x)*m_symbol.Point(),m_high.GetData(_x)-m_low.GetData(_x))*(_type);
   
   class="type">class="kw">double _sl=m_high.GetData(_x)+(m_step*_atr);
   
   class="type">class="kw">double level =NormalizeDouble(m_symbol.Ask()+m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
   class="type">class="kw">double new_sl=NormalizeDouble(_sl,m_symbol.Digits());
   class="type">class="kw">double pos_sl=position.StopLoss();
   class="type">class="kw">double base =(pos_sl==class="num">0.0) ? position.PriceOpen() : pos_sl;
   
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   if(new_sl<base && new_sl>level)
      sl=new_sl;
class=class="str">"cmt">//---
      class="kw">return(sl!=EMPTY_VALUE);
  }

◍ 傅里叶残差转价格变动预测的实现细节

下面这段逻辑把前一小节提到的傅里叶分解结果,落成了可返回「价格变动倾向」的函数。输入是 K 线序号 Index,输出是一个 double 类型的 _ft,代表基于 6 个数据点、5 个本征模态算出的相对变动幅度。 先取 6 根收盘价做差分,塞进数组 _a,再做 FFTR1D 得到复数频谱 _f。注意这里 ArrayResize(_a,6) 与 _output.Init(6,5) 是硬绑定:数据窗固定 6、谐波数固定 5,改一处不连带改另一处会直接越界。 重建矩阵时用了 (1.0/5) 做归一,角度按 p/6 推进,复指数项里拿 _f[s].im/_f[s].re 当相位比。这一层纯属把频谱按谐波重新加权,不引入任何外部参数。 最后挑第 6 行(即最新重建点)模值最大的谐波通道,用它的输出除以 Point 与收盘差分绝对值的较大者,再乘 100。出来的 _ft 若持续为正,价格短期倾向于继续沿原方向走;外汇和贵金属杠杆高,这个数值只作概率参考,别当方向保证。 让小布替你跑这套 把 ProcessFT 接在 EA 的 OnTick 里,打印不同 Index 下的 _ft,你能直接看到黄金 M5 上数值在-80~120 之间跳,验证谐波 dominant 切换的频率。

MQL5 / C++
class="type">class="kw">double CTrailingFT::ProcessFT(class="type">int Index)
  {
     class="type">class="kw">double _ft=class="num">0.0;
     
     class="type">int _index=Index;class=class="str">"cmt">//+StartIndex();
     
     m_close.Refresh(-class="num">1);
     
     class="type">class="kw">double _a[];
     matrix _output;
     al_complex _f[];
     
     class=class="str">"cmt">//class="num">6 data points, class="num">5 epicycles
     
     ArrayResize(_a,class="num">6);ArrayInitialize(_a,class="num">0.0);
     _output.Init(class="num">6,class="num">5);_output.Fill(class="num">0.0);
     
     for(class="type">int p=class="num">0;p<class="num">6;p++)
     {
        _a[p]=m_close.GetData(_index+p)-m_close.GetData(_index+p+class="num">1);
     }
     
     FFT.FFTR1D(_a,class="num">5,_f);
     
     for(class="type">int p=class="num">0;p<class="num">6;p++)
     {
        for(class="type">int s=class="num">0;s<class="num">5;s++)
        {
           class="type">class="kw">double _divisor=(class="num">1.0/class="num">5),_angle=(p);_angle/=class="num">6;
           _output[p][s]=(_divisor*_a[p]*MathExp(-class="num">2.0*__PI*(_f[s].im/_f[s].re)*_angle));
        }
     }
     
     class="type">class="kw">double _close=m_close.GetData(_index)>m_close.GetData(_index+class="num">1);
     
     class="type">int _max_index=class="num">0;
     class="type">class="kw">double _max=fabs(_output[class="num">5][_max_index]);
     for(class="type">int s=class="num">0;s<class="num">5;s++)
     {
        if(_max<fabs(_output[class="num">5][s]))
        {
           _max_index=s;
           _max=fabs(_output[class="num">5][s]);
        }
     }
     
     _ft=(_output[class="num">5][_max_index]/fmax(m_symbol.Point(),fabs(_output[class="num">5][_max_index])+fabs(_close)))*class="num">100.0;
     
     class="kw">return(_ft);
  }

「傅里叶工具只是敲门砖」

前面两节把傅里叶变换在 MT5 里的落地路径走完了:从外行视角理解频域含义,到用 MQL5 写出可管止损的波动率预测模块。这套代码不是实盘就绪的系统,外汇和贵金属本身高杠杆、非平稳,直接挂真金白银大概率吃亏。 社区里有人指出原 Alglib 的 FFT 在 6 点小窗口上偏慢,下面这段用固定缓冲区替掉动态 ArrayResize 和矩阵初始化,回测环境里每次 tick 的重算开销能压下去。注意 CustomFFT6 只是示意,除以零的检查得自己补。 double CTrailingFT::ProcessFT(int Index) { double _ft = 0.0; static double _a[6]; // 固定大小的数组,避免每次重分配 al_complex _f[5]; // 5 epicycles,存五个频率分量 // 用价格差异填充 _a for(int p=0; p<6; p++) { _a[p] = m_close.GetData(Index+p) - m_close.GetData(Index+p+1); // 取相邻收盘价差分 } // 用自定义 6 点 FFT 代替 Alglib FFT CustomFFT6(_a, _f); // 假设优化函数,轻量实现 // 使用主导频率(如最大振幅)计算 _ft double maxMagnitude = 0.0; for(int s=0; s<5; s++) { double mag = MathSqrt(_f[s].re*_f[s].re + _f[s].im*_f[s].im); // 算复数模长 if(mag > maxMagnitude) { maxMagnitude = mag; _ft = _f[s].re; // 或使用加权组合,取实部 } } return _ft * 100.0; // 根据需要调整缩放比例 } 剩下没写的频域用法——比如共振检测、周期过滤——得靠你自己开 MT5 把附件里三个 mqh 拆开试。评论区有人跑出 12 个编译错误,也有人质疑金融市场非平稳让傅里叶效率偏低,这些坑都得亲自踩一遍才清楚边界在哪。

MQL5 / C++
class="type">class="kw">double CTrailingFT::ProcessFT(class="type">int Index) {
  class="type">class="kw">double _ft = class="num">0.0;
  class="kw">static class="type">class="kw">double _a[class="num">6]; class=class="str">"cmt">// 固定大小的数组
  al_complex _f[class="num">5];    class=class="str">"cmt">// class="num">5 epicycles
  class=class="str">"cmt">// 用价格差异填充 _a
  for(class="type">int p=class="num">0; p<class="num">6; p++) {
    _a[p] = m_close.GetData(Index+p) - m_close.GetData(Index+p+class="num">1);
  }
  class=class="str">"cmt">// 用自定义 class="num">6 点 FFT 代替 Alglib FFT
  CustomFFT6(_a, _f); class=class="str">"cmt">// 假设优化函数
  class=class="str">"cmt">// 使用主导频率(如最大振幅)计算 _ft
  class="type">class="kw">double maxMagnitude = class="num">0.0;
  for(class="type">int s=class="num">0; s<class="num">5; s++) {
    class="type">class="kw">double mag = MathSqrt(_f[s].re*_f[s].re + _f[s].im*_f[s].im);
    if(mag > maxMagnitude) {
      maxMagnitude = mag;
      _ft = _f[s].re; class=class="str">"cmt">// 或使用加权组合
    }
  }
  class="kw">return _ft * class="num">100.0; class=class="str">"cmt">// 根据需要调整缩放比例
}
让小布替你跑这套
把品种频域分解和分量峰谷标注交给小布盯盘,你只需要在页面里看哪些周转对当前走势贡献最大,省掉自己写 Alglib 调用的重复劳动。

常见问题

不需要,CFastFourierTransform 类在 Math\Alglib\fasttransforms.mqh 里已封装好,直接传序列拿复数分量即可。
n 越大越贴近原曲线,但超过某个点后新增分量对已有形态差异极小,计算开销徒增,实战里取可管控的小数字更划算。
可以,对应品种页的 AIGC 诊断已内置频域拆解,打开就能看到主要频率贡献和相位偏移,不用本地跑脚本。
那只是历史重复形态的概率线索,外汇贵金属波动受事件驱动,频段峰谷会漂移,决策仍要带风控。