模式搜索的暴力方法·进阶篇
(2/3)· 当神经网络架构成为束缚,简单数字序列如何以更低算力挖掘可复用的市场模式
「EA模板里的系数与递归骨架」
这套模板最早在 MQL4 写就,后转 MQL5,两边都能跑。为了沿用 MQL4 那种预定义数组习惯,EA 里要补几行兼容代码,逻辑不复杂,但前提是看过前一篇里讲的那点适配细节。 核心变量先摊开:C1 是阶次系数数组,CNum 取最近多少根烛形进多项式,DeepBruteX 是多维展开的最大次数。我一般填 1,因为多维泰勒的系数总量随阶数陡增,计算负担比一维重得多。DatetimeStart 卡个起始时间,用来限制 EA 只在某时刻后干活。 bInvert 这个反转开关很关键。把阶次系数全变号,多项式形状不动,只是输出值符号反过来。若原设定负值为卖、正值为买,就保持 false;反过来则 true,等于告诉算法「用反号的值去交易」。建议把它设成 input,方便随时翻向。DaysToTrade 同理,控制未来几天允许交易。 系数数组尺寸靠两个递归函数算。DeepN 是中间分形,按相同总阶数累加项数;CalcDeepN 在循环里从 1 调到 deepC 去调它。比如深度限 4,就用自然数 1~4 各调一次 DeepN。 真正算多项式值的 Deep 函数结构类似,但数组自己生成不需预设大小。结果全累进全局变量 ValStart,另用 ValW 存「已有乘积 × 该柱点数位移」(Close-Open 带方向)。它内部调自己,调用树每次形态不同——这种多变结构正好适配多维级数。 若只用一维多项式,整个事就退化成「系数和 × 单阶柱运动」,柱数即项数,深度 1 时走简化版,否则走统一方法。新柱触发的主函数只管挂单平仓,柱检测几行带过。系数全部归一在 [-1,1],因为比值比绝对值更有用。下方代码即模板头部与递归骨架原文。
class="type">class="kw">double C1[] = { %%%CVALUES%%% };class=class="str">"cmt">//array of coefficients class="type">int CNum=%%%CNUMVALUE%%%;class=class="str">"cmt">//number of candlesticks in the formula class="type">int DeepBruteX=%%%DEEPVALUE%%%;class=class="str">"cmt">//formula depth class="type">int DatetimeStart=%%%DATETIMESTART%%%;class=class="str">"cmt">//start point in time class="kw">input class="type">bool bInvert=%%%INVERT%%%;class=class="str">"cmt">//inverted trading class="kw">input class="type">class="kw">double DaysToTrade=%%%DAYS%%%;class=class="str">"cmt">//number of days into the future to trade 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; 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)class=class="str">"cmt">//to launch calculations { NumCAll=class="num">0; for ( class="type">int i=class="num">0; i<deepC; i++ ) { DeepN(Nums,i+class="num">1); } } 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="num">1,class="type">class="kw">double Val0=class="num">1.0)class=class="str">"cmt">//calculate the sum of one degree { for ( class="type">int i=class="num">0; i<Nums; i++ ) { if (deepC > class="num">1) { ValW=(Close[i+class="num">1]-Open[i+class="num">1])*Val0; Deep(Ci0,Nums,deepC-class="num">1,ValW); } else { ValStart+=Ci0[NumC]*(Close[i+class="num">1]-Open[i+class="num">1])*Val0/Point; NumC++; } } }
◍ 多项式估值如何驱动开平仓
这段逻辑把多项式输出值直接映射成交易动作,核心在 PolinomTrade() 与 Trade() 两个函数。当 DeepBruteX 小于等于 1 时,用一阶系数 C1 乘上每根 K 线的 (Close-Open)/Point 累加得到 Val;若大于 1 则走 CalcDeep 递归展开高阶项,返回 ValStart。外汇与贵金属杠杆高,信号仅代表概率倾向,实盘前务必在 MT5 策略测试器验证。 Trade() 里先用 Value 与 ValueCloseE 比较:大于阈值先平买(或反转模式平卖),小于负阈值平卖(或反转平买)。开仓则额外卡了 DaysToTrade 天数窗,要求 Value 落在 (ValueOpenE, ValueOpenEMax] 或 [-ValueOpenEMax, -ValueOpenE) 区间才触发,避免极值区追单。 CalcTimer() 以 Time[1] 跨根才调用 Trade(),保证每根新 K 线只交易一次。GenerateC() 则按 DeepBrute 决定展开阶数,并用 ArrayResize 给每个变异体分配 NumCAll 长度系数数组,NumCAll 在浅层时等于 CandlesE。把下面代码贴进 EA 的对应函数区,调 ValueOpenE 与 DaysToTrade 就能观察信号密度变化。
class="type">void CalcDeep(class="type">class="kw">double &Ci0[],class="type">int Nums,class="type">int deepC=class="num">1)class=class="str">"cmt">//calculate the entire polynomial { 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); } } class="type">class="kw">double Val; class="type">class="kw">double PolinomTrade()class=class="str">"cmt">//optimized polynomial { Val=class="num">0; if ( DeepBruteX <= class="num">1 ) { for ( class="type">int i=class="num">0; i<ArraySize(C1); i++ ) { Val+=C1[i]*(Close[i+class="num">1]-Open[i+class="num">1])/Point; } class="kw">return Val; } else { CalcDeep(C1,CNum,DeepBruteX); class="kw">return ValStart; } } class="type">void Trade() { class="type">class="kw">double Value; Value=PolinomTrade(); if ( Value > ValueCloseE) { if ( !bInvert ) { CloseBuyF(); } else { CloseSellF(); } } if ( Value < -ValueCloseE) { if ( !bInvert ) { CloseSellF(); } else { CloseBuyF(); } } if ( class="type">class="kw">double(TimeCurrent()-DatetimeStart)/class="num">86400.0 <= DaysToTrade && Value > ValueOpenE && Value <= ValueOpenEMax ) { if ( !bInvert ) SellF(); else BuyF(); } if ( class="type">class="kw">double(TimeCurrent()-DatetimeStart)/class="num">86400.0 <= DaysToTrade && Value < -ValueOpenE && Value >= -ValueOpenEMax ) { if ( !bInvert ) BuyF(); else SellF(); } } class="type">void CalcTimer() { if ( Time[class="num">1] > PrevTimeAlpha ) { if ( PrevTimeAlpha > class="num">0 ) { Trade(); } PrevTimeAlpha=Time[class="num">1]; } } class="type">void GenerateC() { class="type">class="kw">double RX; if ( DeepBrute > class="num">1 ) CalcDeepN(CandlesE,DeepBrute); else NumCAll=CandlesE; for ( class="type">int j=class="num">0; j<VariantsE; j++ ) { ArrayResize(Variants[j].Ci,NumCAll,class="num">0);
变异体权重的三类随机注入
遗传算法里每个变异体 Variants[j] 需要先记下当前蜡烛数、总系数数和暴力搜索深度,再给系数数组 Ci[] 灌入随机初值。下面这段代码展示了 RANDOM_TYPE_1 到 RANDOM_TYPE_3 三种分布形态的差异,直接决定了后续进化的探索偏向。 RANDOM_TYPE_1 最简单,每个系数都是 [0,1] 均匀采样,MathRand()/32767.0 是 MT5 里生成 0~1 浮点的标准写法。RANDOM_TYPE_2 引入符号翻转:以 0.5 为界,一半概率取正、一半概率取负,系数域变成 [-1,1],适合需要双向权重的场景。 RANDOM_TYPE_3 最绕,先用 RX 存一个基准随机值,再以 RX 为阈值决定该系数是否非零;非零时再根据 RX+(1-RX)/2 的中点做正负切分。这种写法会让约 RX 比例的系数直接归零,稀疏度随 RX 浮动,外汇与贵金属回测中可能影响过拟合程度,相关品种波动大、杠杆高,实盘前务必小资金验证。 把这段逻辑抄进你的 EA 初始化函数,改 RE 宏定义就能切换分布;想看实际落点,把 RANDOM_TYPE_3 里那行 Print 注释打开,跑 100 代看 Ci[] 的零值占比是否和你预期的 RX 吻合。
Variants[j].CNum=CandlesE; Variants[j].ANum=NumCAll; Variants[j].DeepBruteX=DeepBrute; RX=MathRand()/class="num">32767.0; for ( class="type">int i=class="num">0; i<Variants[j].ANum; i++ ) { if ( RE == RANDOM_TYPE_1 ) Variants[j].Ci[i]=class="type">class="kw">double(MathRand())/class="num">32767.0; if ( RE == RANDOM_TYPE_2 ) { if ( MathRand()/class="num">32767.0 >= class="num">0.5 ) { Variants[j].Ci[i]=class="type">class="kw">double(MathRand())/class="num">32767.0; } else { Variants[j].Ci[i]=class="type">class="kw">double(-MathRand())/class="num">32767.0; } } if ( RE == RANDOM_TYPE_3 ) { if ( MathRand()/class="num">32767.0 >= RX ) { if ( MathRand()/class="num">32767.0 >= RX+(class="num">1.0-RX)/class="num">2.0 ) { Variants[j].Ci[i]=class="type">class="kw">double(MathRand())/class="num">32767.0; class=class="str">"cmt">///Print(Variants[j].Ci[i]); } else { Variants[j].Ci[i]=class="type">class="kw">double(-MathRand())/class="num">32767.0; } } else { Variants[j].Ci[i]=class="num">0.0; } } if ( RE == RANDOM_TYPE_4 )
「用随机阈值砍掉冗余基因」
在遗传算法式的参数变异里,不是每个染色体位都要动。上面这段逻辑用 MathRand() 生成的 0~1 均匀随机数跟阈值 RX 比大小,决定第 j 个变异体的第 i 个系数 Ci 是随机赋值还是直接归零。 MathRand() 返回 0 到 32767 的整数,除以 32767.0 得到闭区间 [0,1] 的浮点值。当该值 >= RX 时,Ci[i] 被赋为一个新的随机浮点;否则强制 0.0,相当于把这一维特征从当前变异体中剔除。 RX 越大,被保留为 0 的维度越多,种群稀疏化倾向越明显,搜索空间收缩但可能漏掉有效组合。在 MT5 策略测试器里把 RX 从 0.3 调到 0.7,能直接观察到代际适应度方差的变化,外汇与贵金属品种下这种随机剔除会放大过拟合风险,需以多周期样本复核。
{
if ( MathRand()/class="num">32767.0 >= RX )
{
Variants[j].Ci[i]=class="type">class="kw">double(MathRand())/class="num">32767.0;
}
else
{
Variants[j].Ci[i]=class="num">0.0;
}
}
}
}
}◍ EURUSD M5 上跑完的十二个暴力变体
作者挑了 EURUSD M5 上三段各约一个月、首尾都落在周五的连续区间做验证:2020.01.13–02.16、2020.02.13–03.15、2020.03.13–04.18。周五收尾留出了周末两天空档,实盘可用来搜模式,不过本文全程在测试器里跑所以只是顺带一提。 程序第一页管数字生成类型(仅正 / 正负 / 含零)和搜索条件,后者能在「期望值点数」和自拟的利润系数间切换。利润系数公式 P_Factor=(Profit-Loss)/(Profit+Loss) 被限制在 -1 到 +1,天然避开了除零。最大次数设 1 和 2 各六种变体,共 12 个。
| 不对称过滤器 D_Asymmetry= | (BuyTrades-SellTrades) | /(BuyTrades+SellTrades) 取值 0–1,用来逼程序别在单边市里只发同向信号。第二页把多项式值窗口切成等距区间做渐增暴力,并用 Deviation = Max( | Profit[i]-LineProfit[i] | )/EndProfit 量曲线相对首尾连线的偏离,单位都是点。 |
|---|
为压住点差噪声,作者用 MT4 测试器把 spread 锁成 1 点。每个变体暴力加优化约两小时,他自己明说这不够,最好是一两天,但本文目的只是看模式长相。注意 DaysToTrade 默认 3 天,开 EA 前不调大,暴力段后可能根本没单。 一次多项式里,第三区间 2020.03.13 起的第五个机器人起手有波、尾段衰减,明显不对称,作者直接弃用;第二区间的第三个机器人图形接近直线,未来十天也像线,属于少数看着稳的。二次多项式在暴力段总冒出尖锐正半波,出了那段就乱,第十一个机器人虽延直线但噪点偏多,更像运气。 十二个里没一个被断言「能赚」,作者倾向认为次数高于 2 的多项式反转更平滑、意外少,值得拿三天以上暴力去试三次式。外汇与贵金属属高风险品种,这类历史区间拟合出的 EA 在未来失效概率不低,上 MT5 前先把自己那段样本和 spread 设一遍再信。