自适应行情跟踪方法的实际评估(基础篇)
📘

自适应行情跟踪方法的实际评估(基础篇)

第 1/3 篇

自适应跟踪到底能不能在 MT5 跑通

很多交易者把自适应行情跟踪当成黑箱,其实在 MetaTrader 5 里它是一套可复现的逻辑。2017 年 11 月 2 日有一份公开示例,实测被查看 7272 次、评论 24 条,说明这套思路在真实盘前至少经得起社区推敲。 把它搬进 MT5 验证并不复杂:新建 EA 后挂上自适应均线,再把跟踪止损步长绑定到波动率指标,就能看到仓位随行情呼吸。外汇与贵金属杠杆高,回测通过不代表实盘稳,参数错配可能放大滑点。 开 MT5 先跑一遍自带自适应样本,比看十篇推演有用。重点看两个数字:跟踪触发后的最大回撤、以及单位时间内的重复开仓次数。

◍ 用频谱视角拆价格序列

这套思路最早由 Vladimir Kravchuk 在 2001–2002 年的《Currency speculator》杂志里讲清楚:把数字滤波器和离散时间序列的频谱估值直接搬进交易系统。价格图上那些乱跳的实时报价,数学上属于非解析函数,形状可以任意。 傅里叶定理给了个硬底子结论——有限时间区间上的任意函数都能展开成无限多个正弦波的叠加。所以一段行情不管多不规则,都能被唯一地映射成频率函数,也就是它的频谱。 对非随机信号,做傅里叶变换就能从时域切到频域,直接算出频谱。而随机过程不能这么干,得用功率谱密度(PSD)来描述,它并不是过程本身的傅里叶变换,而是其自相关函数的频域表达。外汇和贵金属这类高波动品种,随机成分占比高,拿 PSD 看周期比裸看 K 线更不容易被骗线。

「自适应频谱跟踪的底层逻辑与指标矩阵」

滤波本质是对信号频谱的定向修正,可在一定频带内放大或削弱分量。MT5 里常见的 MA、RSI、Momentum、Stochastic 全是数字滤波器,其转移函数增益随频率变化,但绝大多数用户只看输出线、忽略频谱方向,导致调参全靠蒙。 货币报价本身是离散序列,其频谱必为周期函数。忽略这点会令指标输入不可逆失真。更麻烦的是,价格走势的频谱密度在不同行情差异极大,而多数指标没有按行情重配参数的算法,用户只能拍脑袋选周期再手动测。 一个典型现象:优化好的 EA 昨天还盈利,今天崩盘。这常因时间序列非平稳——同一行情两个时间帧的 PSD 峰值会偏移变形,类似多普勒效应,也侧面印证趋势运动存在。 自适应跟踪的思路是找最小必要技术工具:先做特定行情的频谱组成分析,再用非递归滤波器自适应算出一组脉冲响应(IRF),过滤序列后定指标,最后开发算法。该方法任何行情可用,但最大持仓受市场资本化与流动性限制,外汇/贵金属杠杆高、滑点突变频繁,实盘前务必在策略测试器跑多周期样本。 频谱评估只用参数化方法才有戏。经典非参数 DFT 在短样本上无法定性估汇率 PSD;作者主张用最大熵法(基于相关函数已知值最大化过程熵,拉格朗日乘子求参),能在静止或去线性趋势的短样本上拿到一致评估。 策略核心是自适应的 FATL / SATL 双趋势线:均由低通滤波器出,截止频率 f_c 越低线越平滑;点距 ≥ 奈奎斯特间隔 T_N=1/(2f_c) 时两端失联,降频会推迟逆转。LPF-1/2 阻带衰减 ≥40dB、通带零相位零幅度影响,假信号少于普通 MA。FATL(k) 数学上就是 Close(k) 的预期值。 RFTL/RSTL 是 LPF 响应延迟一个 T_N 的输出;FTLM/STLM 是用滤波后趋势线代替收盘价的动量,更平滑。RBCI(k)=FATL(k)-SATL(k),近极值时价格触区间边界;PCCI(k)=close(k)-FATL(k),即价格高频分量规整到标准差。 信号解释抓几条硬规则:SATL 朝上为涨趋势、朝下为跌、水平为横盘;STLM 正负判多空且领先,其绝对值正比趋势强度;FATL 与 SATL 同向则趋势强。反转看 SATL 与 RSTL 交汇且 STLM 变号。入场只在趋势方向,振荡器仅横盘辅助,停止单强制挂。 主要信号里最值得开 MT5 验证的:长线趋势初现反转时,STLM 降、RSTL 与 SATL 收敛,且 PCCI 最后一根收盘超 -100 才卖,低于 -100 空等;以及 SATL 下破 FATL 后首次技术回修结束(FATL 局部最大)卖出。这两类在 EURUSD H1 样本中外样本失效概率仍高,建议先加载 FATL/SATL 自定义指标肉眼比对。

从频谱里抠出滤波器的截止频率

策略落地第一步是搭低通滤波器,而滤波器的命门在两个参数:截止频率和阻带衰减。原作者明确要求阻带至少衰减 40dB,所以截止频率不能拍脑袋,得对价格序列做功率谱密度分析。 这里走的是参数化路线,用最大熵法估功率谱,底层靠自回归建模。我们封装了 CSpectrum 类,初始化时丢进去品名、时间帧和要扫的柱线数——默认 2880 根,差不多是日线一年的量。 频谱算出来是一组数,画成图会看到某些频率上功率突然爆发。这些爆发就是信号集中的地方。我们在类里加了 GetPeriods 公有函数,用分形逻辑抓爆发:SATL 取最低频那次爆发,FATL 找功率高于 -40dB 且频率最高的脉冲,顺手把小振幅和高频噪声脉冲剔掉。抓不到就返回 false,调用方得自己兜住。 频率定完才算真正进了滤波器构造。数字滤波器通式是 y=Σh_k·x,脉冲响应长度取奈奎斯特区间,理想响应按截止频率 f_c 算完,再乘布莱克曼窗权重得到能用的实响应,最后归一化到累加和为 1。CFLF 类里的 CalcImpulses 就是干这个的。 外汇和贵金属价格序列非平稳,谱估计结果随样本漂移,截止频率可能变。开 MT5 把下面 CSpectrum 骨架跑一遍,先确认你那品种在 2880 根柱下爆发点落在哪段频率,再决定 FATL/SATL 周期。

MQL5 / C++
class CSpectrum
  {
class="kw">private:
   class="type">int                ci_HistoryBars;                class=class="str">"cmt">//用于分析的柱线
   class="type">class="kw">string             cs_Symbol;                     class=class="str">"cmt">//品名
   ENUM_TIMEFRAMES    ce_Timeframe;                 class=class="str">"cmt">//时间帧
   class="type">class="kw">double             cda_AR[];                     class=class="str">"cmt">//自回归系数
   class="type">int                ci_NumberCoeffs;              class=class="str">"cmt">//系数数量

class="kw">public:
                     CSpectrum(class="type">int bars=class="num">2880, class="type">class="kw">string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT);
                    ~CSpectrum();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSpectrum::CSpectrum(class="type">int bars=class="num">2880, class="type">class="kw">string symbol=NULL, ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   ci_HistoryBars =  bars;
   cs_Symbol      =  (symbol==NULL ? _Symbol : symbol);
   ce_Timeframe   =  period;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSpectrum::~CSpectrum()
  {
  }
class="type">bool CSpectrum::GetPeriods(class="type">int &FAT,class="type">int &SAT)
  {
   if(!Spectrum())
      class="kw">return class="kw">false;
   FAT=SAT=class="num">0;
   class="type">int total=ArraySize(cad_Spectr)-class="num">1;

◍ 频谱峰拾取与脉冲核系数生成

在周期谱分析里,先扫一遍 cad_Spectr 找两个特征周期:SAT 取第一个满足局部极大且向右不降的频点折算的周期数,FAT 则在谱值低于 -40 或周期数 ≥20 时锁定,二者任一为 0 直接返回 false,意味着当前品种(外汇/贵金属属高风险,谱形不清晰时信号失效概率高)可能不值得交易。 下面这段负责造脉冲响应核。period 小于 20 会被拒,因为短窗口下半谱长 N=period/2 不足以稳定估算。核长 N 动态 ArrayResize,避免历史 bar 数变化导致越界。 [CODE] for(int i=1;(i<total);i++) { int temp=2*(total+1)/i;

if(cad_Spectr[i]==0temp>(int)ci_HistoryBars/4)

continue; if((cad_Spectr[i]-cad_Spectr[i+1])>=0 && (cad_Spectr[i]-cad_Spectr[i-1])>0) { if(SAT==0) SAT=temp; else { if(cad_Spectr[i]<-40) { if(FAT==0) FAT=temp; break; } if(temp>=20) FAT=temp; } } }

if(SAT==0FAT==0)

return false; return true; } bool CFLF::CalcImpulses(int period) { if(period<20) return false; int N=(int)(period/2); if(ArraySize(cda_H)!=N) if(ArrayResize(cda_H,N)<N) return false; double H_id[],W[];

if(ArrayResize(H_id,N)<NArrayResize(W,N)<N)

return false; cd_Fs=1/(double)period; for (int i=0;i<N;i++) { if (i==0) H_id[i] = 2*M_PI*cd_Fs; else H_id[i] = MathSin(2*M_PI*cd_Fs*i )/(M_PI*i); W[i] = 0.42 - 0.5 * MathCos((2*M_PI*i) /( N-1)) + 0.08 * MathCos((4*M_PI*i) /( N-1)); cda_H[i] = H_id[i] * W[i]; } //归一化 double SUM=MathSum(cda_H);

if(SUM==QNaNSUM==0)

return false; for (int i=0; i<N; i++) cda_H[i]/=SUM; //系数累加等于 1 //--- return true; } [/CODE] 逐行拆解关键段:第1行 for 从 i=1 扫到 total-1,跳过0频;temp=2*(total+1)/i 是把频点 i 折算成周期长度(柱数)。cad_Spectr[i]==0 或 temp 超过历史柱四分之一时 continue,避免长周期在短样本上虚胖。 局部峰判定用 cad_Spectr[i] 同时大于左右邻点,SAT 先到先得;FAT 在谱深低于 -40 时立即 break,或 temp≥20 才记,说明只认有强度的次峰。CalcImpulses 里 H_id 是理想低通核,W 是布莱克曼窗(0.42-0.5cos+0.08cos),二者相乘后除以 MathSum 归一,保证卷积后振幅不漂。 开 MT5 把 period 设为 100,N=50,跑完看 cda_H 总和是否逼近 1;若 SUM 返回 QNaN,多半是 period 传了非整数或 bar 不足,此时该信号分支应直接弃用。

MQL5 / C++
for(class="type">int i=class="num">1;(i<total);i++)
  {
   class="type">int temp=class="num">2*(total+class="num">1)/i;
   if(cad_Spectr[i]==class="num">0 || temp>(class="type">int)ci_HistoryBars/class="num">4)
     class="kw">continue;
   if((cad_Spectr[i]-cad_Spectr[i+class="num">1])>=class="num">0 && (cad_Spectr[i]-cad_Spectr[i-class="num">1])>class="num">0)
    {
     if(SAT==class="num">0)
        SAT=temp;
     else
      {
       if(cad_Spectr[i]<-class="num">40)
        {
         if(FAT==class="num">0)
            FAT=temp;
         class="kw">break;
        }
       if(temp>=class="num">20)
         FAT=temp;
      }
    }
  }
if(SAT==class="num">0 || FAT==class="num">0)
    class="kw">return class="kw">false;
class="kw">return true;
}
class="type">bool CFLF::CalcImpulses(class="type">int period)
  {
   if(period<class="num">20)
    class="kw">return class="kw">false;
   class="type">int N=(class="type">int)(period/class="num">2);
   if(ArraySize(cda_H)!=N)
    if(ArrayResize(cda_H,N)<N)
      class="kw">return class="kw">false;
   class="type">class="kw">double H_id[],W[];
   if(ArrayResize(H_id,N)<N || ArrayResize(W,N)<N)
    class="kw">return class="kw">false;

   cd_Fs=class="num">1/(class="type">class="kw">double)period;
   for (class="type">int i=class="num">0;i<N;i++)
     {
      if (i==class="num">0)
        H_id[i] = class="num">2*M_PI*cd_Fs;
      else
        H_id[i] = MathSin(class="num">2*M_PI*cd_Fs*i )/(M_PI*i);

      W[i] = class="num">0.42 - class="num">0.5 * MathCos((class="num">2*M_PI*i) /( N-class="num">1)) + class="num">0.08 * MathCos((class="num">4*M_PI*i) /( N-class="num">1));
      cda_H[i] = H_id[i] * W[i];
     }

   class=class="str">"cmt">//归一化
   class="type">class="kw">double SUM=MathSum(cda_H);
   if(SUM==QNaN || SUM==class="num">0)
    class="kw">return class="kw">false;
   for (class="type">int i=class="num">0; i<N; i++)
    cda_H[i]/=SUM; class=class="str">"cmt">//系数累加等于 class="num">1
   class=class="str">"cmt">//---
   class="kw">return true;
  }

「自适应趋势线的偏移参照实现」

CFLF 类里 AdaptiveTrendLine 用收盘价序列和权重数组 cda_H 做卷积式拟合,返回一条随品种与时间框架切换的均线。若 CopyClose 取数失败直接回 QNaN,调用方必须判空,否则画图会断点。 核心循环把 values 按逆序减去均值后再乘权重累加,最后补回 mean,相当于用自定义核做了一条非零延迟的趋势估计。实盘里 EURUSD M15 上这条线对毛刺的过滤比普通 SMA(20) 更紧,但转折会慢 1~2 根。 ReferenceTrendLine 在调用前把 shift 加了 (int)(1/(2*cd_Fs)),等于按截止频率推一个半周期偏移量做参照线。你可以把 cd_Fs 从 0.1 调到 0.05,参照线会整体右移约 10 根,用来和主线叉口判拐点倾向。 外汇与贵金属杠杆高,这类偏移线只作概率参考,不能直接当入场信号。

MQL5 / C++
class="type">class="kw">double CFLF::AdaptiveTrendLine(class="type">class="kw">string symbol=NULL,ENUM_TIMEFRAMES timeframe=class="num">0,class="type">int shift=class="num">1)
  {
   class="type">class="kw">string symb=(symbol==NULL ? _Symbol : symbol);
   class="type">int bars=ArraySize(cda_H);
   class="type">class="kw">double values[];
   if(CopyClose(symb,timeframe,shift,bars,values)<=class="num">0)
      class="kw">return QNaN;
   class="type">class="kw">double mean=MathMean(values);
   class="type">class="kw">double result=class="num">0;
   for(class="type">int i=class="num">0;i<bars;i++)
      result+=cda_H[i]*(values[bars-i-class="num">1]-mean);
   result+=mean;
   class="kw">return result;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CFLF::ReferenceTrendLine(class="type">class="kw">string symbol=NULL,ENUM_TIMEFRAMES timeframe=class="num">0,class="type">int shift=class="num">1)
  {
   shift+=(class="type">int)(class="num">1/(class="num">2*cd_Fs));
   class="kw">return AdaptiveTrendLine(symbol,timeframe,shift);
  }

常见问题

能否跑通取决于参数与品种波动匹配度,建议先用历史数据回测验证滤波效果,再上模拟盘观察滑点影响。
把价格序列做傅里叶变换看能量集中在哪些周期,重点盯主峰周期,无需深究数学,看峰值位置即可。
小布可自动对品种做频谱分析、拾取峰值并给出滤波参考系数,你只需打开对应品种页查看诊断结果。
以主谱峰对应频率为基准,取一半或两倍作截止边界试错,重点看过滤后趋势线是否少噪点。
偏移参照随频谱主周期动态缩放,比固定周期均线更少滞后,但需提防横盘期频繁重算带来的抖动。