您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换·进阶篇
🌊

您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换·进阶篇

(2/3)· 多数交易者盯着 K 线形态找规律,却忽略了价格本就是多层频率叠加的波,拆开看才知哪层在主导

偏理论 第 2/3 篇

把收盘价序列直接当信号读,等于把混在一起的低频趋势和高频噪声一口吞下。傅里叶变换给的不是一个新指标,而是一把能拆波的刀——可惜多数人连周转次数设多少都凭感觉。

跟单尾随类的参数校验与多仓止损逻辑

在自定义尾随止损模块里,CTrailingFT 的析构函数留空,真正的初始化门槛落在 ValidationSettings。它先调用基类 CExpertTrailing 的校验,再卡一道硬约束:m_index 必须落在 0 到 4 之间(原文写 m_index<0m_index>=5 即拒绝),否则向日志抛“index must be greater than 0 and less than epicycles”并返回 false。

InitIndicators 负责把指标和时序挂进传入的 CIndicators 指针;指针为空直接返 false,随后委派基类完成基础指标初始化。这一步没额外建指标,说明本类主要复用已有高低价序列与价差序列。 多仓尾随看 CheckTrailingStopLong:先 Refresh 最近一根 K 的高低价,取 StartIndex 定位,再用 ProcessFT 算出一个百分比系数 _ft,除以 100 得到 _type。ATR 代理值用“最高价-最低价”与 2 倍点差二者取大,再乘 _type。 止损线 = 本根最低价 - m_step*代理ATR。注意 brokers 的 StopsLevel 限制:new_sl 必须高于原止损(或开仓价)且低于 Bid - StopsLevel*Point 才写回 sl,否则 sl 保持 EMPTY_VALUE 不调整。外汇与贵金属杠杆高,这类尾随参数错配可能在滑点中放大回撤,建议先在 MT5 策略测试器用历史数据验证 m_step 与 m_index 的敏感性。

MQL5 / C++
CTrailingFT::~CTrailingFT(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation settings class="kw">protected data.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::ValidationSettings(class="type">void)
  {
class=class="str">"cmt">//--- validation settings of additional filters
   if(!CExpertTrailing::ValidationSettings())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data checks
   if(m_index<class="num">0 || m_index>=class="num">5)
     {
       printf(__FUNCTION__+": index must be greater than class="num">0 and less than epicycles");
       class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create indicators.                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::InitIndicators(CIndicators *indicators)
  {
class=class="str">"cmt">//--- check pointer
   if(indicators==NULL)
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initialization of indicators and timeseries of additional filters
   if(!CExpertTrailing::InitIndicators(indicators))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position.         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::CheckTrailingStopLong(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_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">//---

◍ 空单 trailing 与傅里叶预测的实现细节

空单 trailing 的判定逻辑和的多头版本对称,但方向完全反过来:用近期最高价加步长乘波动基线来算候选止损,且只在候选值低于原基准、又高于经纪商最小止损位时才下发。代码里 base 的取法很关键——若持仓尚无止损,就以开仓价为基准,否则沿用现有 SL,避免把止损往亏损方向推。 候选 SL 的计算式为 _sl = m_high.GetData(_x) + (m_step*_atr),其中 _atr 取了两倍点差与波段振幅的较大值再乘 _type(傅里叶输出除以100)。若 new_sl < base 且 new_sl > level 才赋值 sl,否则保持 EMPTY_VALUE 表示本 tick 不调整。 ProcessFT 负责把收盘价序列喂给傅里叶变换,输出对价格变化的预测值。注释写明用 6 个数据点、5 个本轮(epicycle)做拟合,_output 为 matrix 类型、_f 为复数数组,这是 MT5 新矩阵/复数 API 的典型用法。 外汇与贵金属杠杆高、滑点随机,这套 trailing 在剧烈单边中可能频繁重算 SL;上 MT5 把 m_step 从默认调到 1.5 倍,观察回测中止损触发距离的变化倾向。

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

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

「用傅里叶变换算 trailing stop 的实测片段」

下面这段 MT5 代码把最近 6 根收盘价差分后做一维实数 FFT,再用频谱重构出 trailing 用的偏移量。外汇与贵金属波动无序,FFT 仅刻画历史周期成分,实盘信号失效概率不低,务必先在策略测试器跑样本。 数组先定长 6 并清零,输出矩阵 Init(6,5) 代表 6 行价格点、5 行频率分量;差分循环取 m_close[_index+p] 减后一根,得到 6 个收益率残差。 FFTR1D(_a,5,_f) 只取前 5 个频点,双重循环里用 1/5 做归一、用 p/6 当相位角,按 exp(-2π·(im/re)·angle) 复数衰减重构每个 (价格点,频率) 单元。最后 _ft 拿第 5 行某频点除以自身绝对值与收盘价差的最大值,乘 100 压到百分比尺度,返回给移动止损模块。 直接把这段塞进你自己的 CT trailing 类,把 m_index 设成想跟踪的频点序号,能在 EURUSD 的 M15 上直观看到重构幅度随价差收缩。

MQL5 / C++
  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);
  
  _ft=(_output[class="num">5][m_index]/fmax(m_symbol.Point(),fabs(_output[class="num">5][m_index])+fabs(_close)))*class="num">100.0;
  
  class="kw">return(_ft);

用傅里叶变换做跟踪止损的类骨架

MT5 里把傅里叶变换塞进 trailing stop,不是直接算价格,而是借频域视角重构止损参考。下面这段类声明定义了 CTrailingFT,它继承自 CExpertTrailing,核心成员是一个 CFastFourierTransform 实例。 类头部注释里暴露了三个可调参数:Points=6、Epicycles=5、Step=0.5。Points 控制采样点数量,Epicycles 是傅里叶级数截断项数,Step 是实际拖拽止损的步长(报价点)。 宏定义里 __PI 写成 245850922/78256779,这是用分数逼近 π(约 3.14159265),避免浮点字面量误差。外汇和贵金属波动剧烈,这类基于频域的止损在高杠杆下仍可能快速触损,属高风险用法。 类里只声明了接口:ValidationSettings 做参数校验,InitIndicators 挂指标,CheckTrailingStopLong/Short 分别处理多空仓的止损止盈刷新。具体傅里叶计算逻辑在别处实现,但从这个骨架能直接开 MT5 新建 trailing 类验证编译。

MQL5 / C++
class=class="str">"cmt">//| Class=CTrailingFT                                                                              |
class=class="str">"cmt">//| Page=trailing_ct                                                                                |
class=class="str">"cmt">//| Parameter=Points,class="type">int,class="num">6,FT-Points                                                                |
class=class="str">"cmt">//| Parameter=Epicycles,class="type">int,class="num">5,FT-Epicycles                                                          |
class=class="str">"cmt">//| Parameter=Step,class="type">class="kw">double,class="num">0.5,Trailing Step                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// wizard description end
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CTrailingFT.                                                                              |
class=class="str">"cmt">//| Appointment: Class traling stops with &class="macro">#x27;Fourier Transform&class="macro">#x27; v3                                     |
class=class="str">"cmt">//|               relative-sets concepts.                                                            |
class=class="str">"cmt">//| Derives from class CExpertTrailing.                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define    __PI class="num">245850922/class="num">78256779
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTrailingFT : class="kw">public CExpertTrailing
  {
class="kw">protected:
   CFastFourierTransform   FFT;

   class=class="str">"cmt">//--- adjusted parameters

   class="type">class="kw">double                  m_step;             class=class="str">"cmt">// trailing step
class="kw">public:
   class=class="str">"cmt">//--- methods of setting adjustable parameters



   class=class="str">"cmt">//--- method of verification of settings
   class="kw">virtual class="type">bool            ValidationSettings(class="type">void);
   class=class="str">"cmt">//--- method of creating the indicator and timeseries
   class="kw">virtual class="type">bool            InitIndicators(CIndicators *indicators);
   class=class="str">"cmt">//--- methods of checking if the market models are formed
   class="kw">virtual class="type">bool            CheckTrailingStopLong(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp);
   class="kw">virtual class="type">bool            CheckTrailingStopShort(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp);
   class=class="str">"cmt">//---
                     CTrailingFT(class="type">void);
                    ~CTrailingFT(class="type">void);
   class=class="str">"cmt">//--- methods of setting adjustable parameters

◍ 把类骨架接进实盘平仓判定

上面这段是尾随止损类 CTrailingFT 的底层实现残片,负责把滤波逻辑挂到 EA 的持仓管理链上。构造里只做了一件事:把时间、点差、开高低收五类序列全部标记为可用,m_used_series 用位加写成 USE_SERIES_TIME+USE_SERIES_SPREAD+USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE,意味着后续 ProcessFT 能直接读这些缓存。 ValidationSettings 与 InitIndicators 都先调父类 CExpertTrailing 的同名方法,返回 false 就中断,等于把过滤器的准入门槛交回标准框架,你自己加的自定义校验得插在父类调用之后才有意义。 真正产生平仓信号的是 CheckTrailingStopLong:先 Refresh 最近一根 K 的 high/low,再用 StartIndex() 取当前索引送进 ProcessFT。注意 _type=_ft/100.0 这个除法——说明 ProcessFT 返回的是以「基点百分之一」为单位的整数化数值,比如返回 250 时实际滤波阈值倾向对应 2.5 个基点。外汇与贵金属杠杆高,这类尾随阈值过小可能在震荡里被反复触发止损,上 MT5 用策略测试器跑 EURUSD M15 改这个除数就能看到滑点敏感度的变化。 Step 方法只是把外部传入的 double 写进 m_step 成员,典型用法是 EA 参数面板把步长暴露出来。你复制这段代码时若报 m_high/m_low 未声明,说明头文件里少写了 CTimeseries 类型的 protected 成员,补上再编译。

MQL5 / C++
class="type">void                Step(class="type">class="kw">double value)                { m_step=value;       }
class="kw">protected:

  class="type">class="kw">double            ProcessFT(class="type">int Index);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailingFT::CTrailingFT(class="type">void)
  {
class=class="str">"cmt">//--- initialization of class="kw">protected data
   m_used_series=USE_SERIES_TIME+USE_SERIES_SPREAD+USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTrailingFT::~CTrailingFT(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation settings class="kw">protected data.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::ValidationSettings(class="type">void)
  {
class=class="str">"cmt">//--- validation settings of additional filters
   if(!CExpertTrailing::ValidationSettings())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initial data checks
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create indicators.                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::InitIndicators(CIndicators *indicators)
  {
class=class="str">"cmt">//--- check pointer
   if(indicators==NULL)
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- initialization of indicators and timeseries of additional filters
   if(!CExpertTrailing::InitIndicators(indicators))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position.           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingFT::CheckTrailingStopLong(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;
      
把频谱诊断交给小布
这些分量幅度和相位的初步筛查,小布盯盘的 AIGC 已内置,打开对应品种页即可看到主要周转的占比变化,你只管判断哪层频率值得跟。

常见问题

不需要,系统库 Math\Alglib\fasttransforms.mqh 中的 CFastFourierTransform 类已封装离散变换,直接调用即可获得复数分量。
n 越大越贴近原曲线,但超过某个点后新增分量对描述已分解部分无实质差异,反而拖慢计算且易过拟合噪声。
相位代表各频率分量的时间偏移,能提示重复形态在周期内的位置,辅助判断当前处于波峰还是波谷区间,属概率参考。
可以,小布盯盘的品种页内置了基于离散傅里叶的频谱概览,直接列出贡献度最高的几个周转,省去手动跑脚本。
外汇贵金属受突发消息影响大,频率结构可能骤变,历史频谱仅作概率参考,实际杠杆交易高风险,须严格风控。