模式搜索的暴力方法(第二部分):深入·进阶篇
(2/3)· 仅靠利润因子排序会漏掉大量优质形态,引入形状相似度后结果结构彻底改变
「多维因子重构下的模板函数改造」
逻辑升级后,模板里的计算函数必须能吃下新参数:原先只处理 1 个数值,现在同时跑 4 个独立乘数,公式搜索能力随数据维度上升而提高,但代价是组合可能重复。 写代码时暴露出一个缺陷——多维多项式的调用树不控制因子排列,导致相同因子组合被重复计入。两种修法:枚举所有排列,或用总系数除以重复因子个数做补偿。我选了后者,副作用是平均系数会向 0.5 收敛,但因子至少不会失效。 固定总次数下乘法器组合数可直接算:Comb = n! / (n-k)!,n 是独立乘数数目,k 是总次数。下面这段就是组合数的 MQL5 实现,注意它用两次循环规避了完整阶乘溢出。 [CODE] double Combinations(int k,int n) { int i1=1; int i2=1; for ( int i=n; i<n-k; i-- ) i1*=i; for ( int i=n-k; i>1; i-- ) i2*=i; return double(i1/i2); } [/CODE] 逐行拆解:第 3–4 行初始化分子 i1、分母 i2 为 1;第 5 行从 n 累乘到 n-k+1 填进 i1;第 6 行从 n-k 累乘到 2 填进 i2;第 7 行返回 i1/i2 即组合数。 主多项式函数 PolinomTrade 现在依赖 5 个柱面参数而非 1 个:4 个初始数组外加价格差分。代码里把相邻开盘/收盘、最高/开盘、开盘/最低、最高/收盘、收盘/最低的差都除以 Point 后乘系数累加,循环长度已是旧版的 5 倍。 这类递归树用 OOP 反而笨重,用类 C 的平铺函数建调用树更直白,虽然逻辑绕但能跑。配套 C# 原型还在内部用旧版逻辑,目前不适合普通 PC 做全局模式扫描,属于半成品。外汇与贵金属品种用这套做因子扩展时波动剧烈,高风险,参数没调稳前别直接上实盘。
class="type">class="kw">double Combinations(class="type">int k,class="type">int n) { class="type">int i1=class="num">1; class="type">int i2=class="num">1; for ( class="type">int i=n; i<n-k; i-- ) i1*=i; for ( class="type">int i=n-k; i>class="num">1; i-- ) i2*=i; class="kw">return class="type">class="kw">double(i1/i2); } class="type">class="kw">double Val; class="type">int iterator; class="type">class="kw">double PolinomTrade()class=class="str">"cmt">//Polynomial for trading { Val=class="num">0; iterator=class="num">0; if ( DeepBruteX <= class="num">1 ) { for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*(Close[i+class="num">1]-Open[i+class="num">1])/Point; iterator++; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*(High[i+class="num">1]-Open[i+class="num">1])/Point; iterator++; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*(Open[i+class="num">1]-Low[i+class="num">1])/Point; iterator++; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*(High[i+class="num">1]-Close[i+class="num">1])/Point; iterator++; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*(Close[i+class="num">1]-Low[i+class="num">1])/Point; iterator++; } class="kw">return Val; } else { CalcDeep(C1,CNum,DeepBruteX); class="kw">return ValStart; } } class=class="str">"cmt">///Fractal calculation of numbers class="type">class="kw">double ValW;class=class="str">"cmt">//the number where everything is multiplied(and then added to ValStart) class="type">uint NumC;class=class="str">"cmt">//the current number for the coefficient class="type">class="kw">double ValStart;class=class="str">"cmt">//the number where to add everything class="type">void Deep(class="type">class="kw">double &Ci0[],class="type">int Nums,class="type">int deepC,class="type">int deepStart,class="type">class="kw">double Val0=class="num">1.0)class=class="str">"cmt">//intermediary fractal { for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) {
◍ 五类价格波幅的递归加权累加
这段逻辑把单根 K 线的价格运动拆成五种典型波幅:收盘减开盘、最高减开盘、开盘减最低、最高减收盘、收盘减最低,分别作为递归入口的权重基值 ValW。 当递归深度 deepC 大于 1 时,代码把当前波幅乘上 Val0 后继续往 Deep() 里塞,让组合在更深层展开;若 deepC 已到 1,则直接把 Ci0[NumC] 除以组合数 Combinations(deepStart, Nums*5) 再做归一化累加,并自增 NumC 指针。 注意分母里写死 Nums*5:因为总共恰好枚举了 5 种波幅结构,所以组合空间按 5 倍 Nums 来配。外汇与贵金属杠杆高,这类递归权重若直接拿来出信号,需先在小周期回测验证稳定性,价格行为存在跳空时可能偏离。 CalcDeep() 是总入口,先把 NumC 和 ValStart 归零,再按 i 从 0 到 deepC-1 循环调用 Deep(),每层 deepStart 与 deepC 同步设为 i+1,相当于由浅入深把各阶组合都扫一遍。
ValW=(Close[i+class="num">1]-Open[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,deepStart,ValW); } else { ValStart+=(Ci0[NumC]/Combinations(deepStart,Nums*class="num">5))*(Close[i+class="num">1]-Open[i+class="num">1])*Val0/Point; NumC++; } } for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) { ValW=(High[i+class="num">1]-Open[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,deepStart,ValW); } else { ValStart+=(Ci0[NumC]/Combinations(deepStart,Nums*class="num">5))*(High[i+class="num">1]-Open[i+class="num">1])*Val0/Point; NumC++; } } for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) { ValW=(Open[i+class="num">1]-Low[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,deepStart,ValW); } else { ValStart+=(Ci0[NumC]/Combinations(deepStart,Nums*class="num">5))*(Open[i+class="num">1]-Low[i+class="num">1])*Val0/Point; NumC++; } } for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) { ValW=(High[i+class="num">1]-Close[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,deepStart,ValW); } else { ValStart+=(Ci0[NumC]/Combinations(deepStart,Nums*class="num">5))*(High[i+class="num">1]-Close[i+class="num">1])*Val0/Point; NumC++; } } for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) { ValW=(Close[i+class="num">1]-Low[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,deepStart,ValW); } else { ValStart+=(Ci0[NumC]/Combinations(deepStart,Nums*class="num">5))*(Close[i+class="num">1]-Low[i+class="num">1])*Val0/Point; NumC++; } } } class="type">void CalcDeep(class="type">class="kw">double &Ci0[],class="type">int Nums,class="type">int deepC=class="num">1) { NumC=class="num">0; ValStart=class="num">0.0; for ( class="type">int i=class="num">0; i<deepC; i++ ) Deep(Ci0,Nums,i+class="num">1,i+class="num">1); }
递归展开分形系数的层数代价
下面这段 MQL5 函数用来统计一个中间分形在给定深度下会展开出多少系数项。核心思路是:DeepN 按 Nums*5 的循环次数向下递归,deepC 每减一层就再展开一次,直到 deepC 为 1 才把 NumCAll 累加。 CalcDeepN 先清零计数器,再从深度 1 到 deepC 逐层调用 DeepN。以 Nums=2、deepC=3 为例,外层循环 3 次分别走 deepC=1/2/3:deepC=1 时 i 跑 10 次全加计数;deepC=2 时每次再嵌套一层 10 次;deepC=3 时再嵌套两层。实际跑完 NumCAll 会远大于 30,递归层数越深,系数数组膨胀越快,MT5 里直接拖大 deepC 就能看到终端打印的 NumCAll 跳变。 外汇与贵金属行情里这类分形展开常用于多周期嵌套过滤,但递归深度过高会吃掉回测速度,属于典型的高风险策略计算负担,调参时建议先在策略测试器里盯内存与耗时。
class="type">int NumCAll=class="num">0;class=class="str">"cmt">//size of the array of coefficients class="type">void DeepN(class="type">int Nums,class="type">int deepC=class="num">1)class=class="str">"cmt">//intermediate fractal { for ( class="type">int i=class="num">0; i<Nums*class="num">5; i++ ) { if (deepC > class="num">1) DeepN(Nums,deepC-class="num">1); else NumCAll++; } } class="type">void CalcDeepN(class="type">int Nums,class="type">int deepC=class="num">1) { NumCAll=class="num">0; for ( class="type">int i=class="num">0; i<deepC; i++ ) DeepN(Nums,i+class="num">1); }
「H1暴力扫描下的全局模式成色」
作者受算力所限只跑 H1 全局模式,且刻意退回 MetaTrader 4 做测试——MT5 测试器在自定义品种下也会按经纪商实时点差下限兜底,容易把回测润色得过于乐观,而 MT4 能避开这层修饰。EURUSD H1 取 2010–2020 十年数据、用 50 根 K 线暴力枚举约 20 万组变体,单核跑完得到的全局模式数学期望仅约 8 个点,P_Factor(类利润系数,区间[0,1])全样本均值约 0.02、最优约 0.029。 把同一组参数推到 2020.01–2020.11 前向验证,EURUSD 前半年还勉强有效,六个月后直接反转,说明这类全局模式若训练期质量不够,实战里大概率只能撑几个月。EURCHF 则明显不同:训练期 P_Factor 峰值约 0.057,比 EURUSD 高近 2 倍,前向全年虽不均匀但未崩,利润系数实跑高出 0.09。 USDJPY 是最差的,前向后半段复制了 EURUSD 的“先上后直线反转”;USDCHF likewise 年中反转。初步结论很直白:训练期指标成色直接决定前向表现,EURCHF 在训练和验证两段都压过其余货币对,非 EURCHF 图表普遍只挺到年中。外汇与贵金属属高风险品种,这类统计优势随时被点差和波动吞噬,实盘前务必自测。 换小样本试一刀:EURJPY H1 取 2017 全年(样本缩到十年变量的 1/12),训练出的利润系数和数学期望反而高于十年组。推到 2018.01–02 一个月前向,模式几乎立刻反转——样本越小越容易过拟合,几个月的可交易窗口往往伴生更高的随机性,验证成本不低但值得手动复一遍。