您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换(基础篇)
◍ 用傅里叶变换给行情拆频
MQL5 向导里内置的傅里叶变换,能把一段价格序列从时域拆成多个正弦波分量,让你看清当下走势里哪些是慢趋势、哪些是快噪声。对做贵金属或外汇的交易者来说,这套频域视角比单纯看均线更不容易被短期毛刺带偏,但杠杆品种波动剧烈,频域信号失效时亏损会放大,属高风险操作。 在 MT5 里调用向导生成 EA 时,可以直接用 Fourier 相关函数做周期提取。下面这段演示了如何对一个收盘价数组做离散傅里叶变换并取前几个主频: 实际验证时建议取 256 根 H1 收盘价跑一次,观察主频幅值排序。若前三大分量集中在低频段,行情倾向走趋势;若能量均匀铺在高频,大概率是在震荡消耗。
class="type">class="kw">double price[class="num">256]; CopyClose(_Symbol, PERIOD_H1, class="num">0, class="num">256, price); class="type">class="kw">double re[class="num">256], im[class="num">256]; for(class="type">int k=class="num">0; k<class="num">256; k++) { re[k]=class="num">0; im[k]=class="num">0; for(class="type">int n=class="num">0; n<class="num">256; n++) { class="type">class="kw">double angle = -class="num">2*M_PI*k*n/class="num">256; re[k] += price[n]*cos(angle); im[k] += price[n]*sin(angle); } } class=class="str">"cmt">// re、im 即频域实部与虚部,幅值 sqrt(re^class="num">2+im^class="num">2) 越大表示该频率成分越强
「傅里叶分解到底在拆什么」
傅里叶变换把一条数据波按频率拆开,变成一组分量频谱。原始函数 f(t) 被分解成不同角频率 w 对应的部分,每个部分都是一个复数:模长代表振幅,辐角代表时间偏移。理论上积分区间从负无穷到正无穷,但实盘用不上。 实战里没人跑无限频率。周转数(分量频率个数)预先锁成一个可控的小整数 n:n 越大越贴近原曲线,但超过某个点后多加的分量对已有分解几乎不产生差异,纯属浪费算力。所以大家都用离散有限周转版本,把上面那个连续积分改成 n 项求和。 系数求解有个具体技巧:求 C₂ 时等式两侧同乘 e^(-2it),积分后其它项塌成零,只留下目标项。这个 2 换成 n 就能推广到任意系数。MT5 里这些脏活全在 Math\Alglib\fasttransforms.mqh 的 CFastFourierTransform 类里封装好了,你直接调库就行,不用手推公式。 外汇和贵金属价格序列做这类分解波动剧烈、高风险,n 取多大最好在历史数据上先跑一遍看残差收敛点。
把频谱拆开能干什么
把价格或宏观序列丢进傅里叶变换,本质是把一团乱波拆成不同频率的组分。对交易者来说,这套能力的落点不在数学本身,而在你能从组分里挑出「反复出现的那一根」来用。 股价走势里,若某只标的频谱在固定频率上反复冒峰,往往意味着存在可识别的重复节奏。此时在低位接、高位抛只是基于「形态延续」的倾向,必须持续监控,因为周期会漂移。 长周期层面,商业数据(GDP、失业率、工业生产、消费者信心、利率、通胀、住房)与市周期数据(市盈率低于15甚至10常被视为低估区、股息率、200天均线价差、市场广度、VIX)都可排成时间序列后做分解。举例:利率数据的循环常持续数年,与股票或ETF呈滞后正相关,可据此在利率低位布局长仓,仅当相关性转负或预示衰退时离场。 信号过滤上,只取某标的历史中的加特利形态波形做变换,设一个与最终突破相关的频率阈值,滤掉不匹配的组分,用剩余少量波形向前推演——股票、外汇对、商品逻辑一致。 信用风险管理中,违约率、信用利差、贷款拖欠、评级、CDS都能拼成序列再分解,找出与后续金融冲击相关最佳的组分来排兵布阵。期权定价里,标的价格、波动率、利率等各自本就是价格的「频率分量」,到期前固定段分解、看哪个组分与到期虚实差最相关,对欧式期权有用,美式因可提前行权更复杂。 情绪与机器学习是另两条线:社媒参与度转序列后分解,可挖出与3至6个月后销量最相关的组分;机器学习则把每根收盘价波形拆成n个向量组分,用欧氏距离比对待测波形,比裸波形比对更准。外汇与贵金属自带高杠杆高风险,任何组分信号都只是概率倾向,开MT5把一段日线丢进自定义傅里叶脚本验证最实在。
◍ 用傅里叶系数改写尾随止损类
把区间定价时间序列(高点减低点)做一维傅里叶分解,重点不在拟合曲线,而在提取各周转频率的实/虚系数,再拿它们去驱动自定义尾随类里的止损位。原文实现里用了 6 个数据点和 5 次周转,预测点取 m_points-1,理由是傅里叶周期重复,下一个采样点就是最长周期里的续段。 存储上用 a1_complex 结构体接 FFTR1D 的输出,再即兴铺一个 _output 矩阵:行是数据点、列是周转次数,把系数“整合”进去方便逐点查表。这个类继承 CExpertTrailing,对外只暴露 Step(默认0.5)和 Index(默认0)两个参数,接的是 SignalRSI 信号与固定保证金风控。 回测锚点:EURJPY,2022.01.01–2023.01.01,4 小时周期;盈利目标与默认止损均设零,离场只靠信号反转或尾随止损触发。同样入场信号下,只微调尾随系统(最大振幅分量 / 最小振幅分量两套对照),三份样本报告权益曲线差异明显,说明外汇与贵金属这类高波动品种里,止损构造的微小改动可能显著影响回撤与持仓时长。 下面这段是自定义尾随类的头文件骨架,注意它直接 include 了 Alglib 的 fasttransforms,没自己写蝶形运算:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TrailingCT.mqh | class=class="str">"cmt">//| Copyright class="num">2009-class="num">2013, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Math\Alglib\fasttransforms.mqh> class="macro">#include <Expert\ExpertTrailing.mqh> class=class="str">"cmt">// wizard description start class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Description of the class | class=class="str">"cmt">//| Title=Trailing Stop based on &class="macro">#x27;Fourier Transform&class="macro">#x27; v3 | class=class="str">"cmt">//| Type=Trailing | class=class="str">"cmt">//| Name=CategoryTheory | class=class="str">"cmt">//| ShortName=CT | class=class="str">"cmt">//| Class=CTrailingFT | class=class="str">"cmt">//| Page=trailing_ct | class=class="str">"cmt">//| Parameter=Step,class="type">class="kw">double,class="num">0.5,Trailing Step | class=class="str">"cmt">//| Parameter=Index,class="type">int,class="num">0,FT-Index | 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. |
「把傅里叶拖损类接进专家系统」
在 MT5 的 EA 框架里, trailing stop 不一定只能靠 ATR 或固定点数。下面这个类从 CExpertTrailing 派生,把快速傅里叶变换(CFastFourierTransform)作为拖损计算的底层引擎,意味着止损位可能跟着价格周期的幅值走,而不是单纯看波动率。 类里两个可调参数很关键:m_step 是拖损步长,m_index 是选取的第几个谐波(epicycle)。外部通过 Step() 和 Index() 直接写入,没有做边界保护,所以你在面板填值时要自己确认 step>0、index 在 FFT 长度范围内,否则回测可能直接崩初始化。 构造函数里 m_used_series 一口气开了 TIME/SPREAD/OPEN/HIGH/LOW/CLOSE 六种序列。这说明 ProcessFT() 内部大概率要同时用到开盘收盘和的高低点,外汇与贵金属品种的高频跳空会让这些序列在周日开盘出现断点,实盘前最好在策略测试器用真实 tick 跑一遍验证。 代码只给到析构函数声明,ProcessFT(int Index) 的具体变换和 CheckTrailingStopLong/Short 的触发逻辑没有展开。你可以先把这段骨架编译进自己的 include 目录,把 Index 从 1 调到 3 看止损线弯曲程度的变化,再决定是否继续补完。
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="type">int m_index; class=class="str">"cmt">// the epicycle index 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 class="type">void Step(class="type">class="kw">double value) { m_step=value; } class="type">void Index(class="type">int value) { m_index=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 |