您应该知道的 MQL5 向导技术(第 06 部分):傅里叶(Fourier)变换·综合运用
(3/3)·从频域视角重看行情曲线,用离散变换把重复形态从噪声里拎出来
多空双向的 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 要调小而不是迷信历史参数。
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 切换的频率。
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 个编译错误,也有人质疑金融市场非平稳让傅里叶效率偏低,这些坑都得亲自踩一遍才清楚边界在哪。
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">// 根据需要调整缩放比例 }