形态搜索的暴力强推方式(第四部分):最小功能(基础篇)
最小功能版的形态搜索器怎么用
这一版把形态搜索器砍到只剩最小功能集,目标很直接:降低门槛,让更多人能跑起设定搜索和市场调查。作者 Evgeniy Ilin 在 2021 年 6 月 9 日发布于 MT5 社区,原帖浏览量 2993、评论 8 条,说明即便精简版也有人盯。 新版本核心改动集中在几点:用基于修订傅立叶级数的新多项式替代旧拟合;内部实现重写;加了对抗点差噪声和结算点差的新机制;短线阶段手数可变化;依据全局历史操作调整逻辑。 暴力强推的思路没变,但这一版少谈交易细节,多给可验证信息。你在 MT5 里装上后,先拿历史数据黏合测试,注意过度拟合风险——外汇和贵金属杠杆高、点差跳变频繁,回测漂亮不等于实盘能跑。 用法上建议先用最小功能跑一轮全局搜索,把提取出的操作设置丢进策略测试器,观察手数变化和抗噪机制在滑点下的表现,再决定是否加复杂模块。
◍ 这版模板到底改了什么
这一版智能交易系统模板相较旧版做了大幅翻新,旧版被作者自述为‘非常蹩脚’,漏误不少。本次修订清单里,界面重做、超短线阶段加入手数变化机制、点差计算与降噪机制,都是实盘里能直接感知的差异。 比较硬核的一处:暴力强推机制里塞进了另一个基于修订傅立叶级数的多项式,随机数生成也增强了。对于做价格行为回测的人,这意味着采样分布和推送逻辑跟过去不一样,旧参数直接搬过来可能跑偏。 作者称大多数原计划修改已实现,后续算法修正不会太多。想验证的话,开 MT5 把旧版 EA 模板和新版并排加载,重点看超短线手数曲线与点差过滤后的成交密度变化。外汇与贵金属杠杆高,回测顺滑不等于实盘无忧,仍需小仓位试。
「用外部配置文件给 EA 喂参数」
写 EA 最烦的有两件事:一是 EA 名容易撞车,二是设置项一多就乱。直接把参数写死在代码里,改一次要重编译,来回折腾成本很高。 折中办法是让生成程序导出一份 txt 配置,EA 启动后自己读。MT4 和 MT5 读取逻辑保持一致,普通用户不必关心终端差异,复制文件就能换策略。 为什么不用 MT 原生的 set 文件?根因是 set 不支持数组,而本方案输入是变长数组。普通文本文件反而最顺手:一行一个字段,数组用分隔符摊开,EA 按行解析即可。 界面上我把控件按功能切到不同选项卡,没用到的元素直接隐藏,不占空间也不误导操作。
把傅立叶多项式改造成多维度量价函数
标准傅立叶级数原是为 [-π;π] 区间的函数分解设计的,直接套到行情上属于暴力强推,实用性很差。作者的做法是不改其形、换其神:保留接近傅立叶级数的结构,但让序列周期在正负间浮动、项数有限,且删掉首项保持信号对称——公式在 + 和 - 区间触发的信号倾向对称。 代价也很直接:每根柱线不再只取 1 个参数,而是塞进 6 个(开高低收与点的组合),且必须牺牲单根精度去照顾其余柱线。一维函数由此变成多维,能覆盖泰勒级数描述不了的规则族,在同一段样本上找形态的机会更多。 代码里用 Method=="FOURIER" 分支实现多项式核心。五个循环分别吃掉了 (收-开)、(高-开)、(开-低)、(高-收)、(收-低) 五组价差,每组按 C1 数组里 4 个系数步进(iterator+=4),用 MathSin/MathCos 加权累加进 Val。 作者在 USDJPY M15 上用去年历史跑过快速暴力强推,尚未挖出真正能用的解,但认为只是算力和时间问题。该公式对点差噪声抑制不稳定,优化时建议打开第二选项卡的 Spread Control,可关掉噪声抑制、出更干净的变体。外汇与贵金属属高风险品种,这类实验性拟合在实盘前务必过历史与样本外验证。
if ( Method == "FOURIER" ) { for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*MathSin(C1[iterator+class="num">1]*(Close[i+class="num">1]-Open[i+class="num">1])/_Point)+C1[iterator+class="num">2]*MathCos(C1[iterator+class="num">3]*(Close[i+class="num">1]-Open[i+class="num">1])/_Point); iterator+=class="num">4; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*MathSin(C1[iterator+class="num">1]*(High[i+class="num">1]-Open[i+class="num">1])/_Point)+C1[iterator+class="num">2]*MathCos(C1[iterator+class="num">3]*(High[i+class="num">1]-Open[i+class="num">1])/_Point); iterator+=class="num">4; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*MathSin(C1[iterator+class="num">1]*(Open[i+class="num">1]-Low[i+class="num">1])/_Point)+C1[iterator+class="num">2]*MathCos(C1[iterator+class="num">3]*(Open[i+class="num">1]-Low[i+class="num">1])/_Point); iterator+=class="num">4; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*MathSin(C1[iterator+class="num">1]*(High[i+class="num">1]-Close[i+class="num">1])/_Point)+C1[iterator+class="num">2]*MathCos(C1[iterator+class="num">3]*(High[i+class="num">1]-Close[i+class="num">1])/_Point); iterator+=class="num">4; } for ( class="type">int i=class="num">0; i<CNum; i++ ) { Val+=C1[iterator]*MathSin(C1[iterator+class="num">1]*(Close[i+class="num">1]-Low[i+class="num">1])/_Point)+C1[iterator+class="num">2]*MathCos(C1[iterator+class="num">3]*(Close[i+class="num">1]-Low[i+class="num">1])/_Point); iterator+=class="num">4; } class="kw">return Val; }
◍ 系数生成的随机内核
公式系数怎么来,核心就一层随机发生器。系统里内置了若干固定随机类型,还有一个通用类型 RANDOM_TYPE_R 能一次性覆盖全部分支;实盘与回测里通用型往往效率最高,但不同品种和周期报价性质不同,固定型并非总失效。 肉眼在大多数图表上看不出类型差异,但机器能抓到。NZDUSD H1 上用 RANDOM_TYPE_4(只产零和正数)时,信号质量会明显跳升,这大概是隐藏波浪过程给的提示,人眼看不出。 下面这段是生成系数的主干函数,逐行拆一下便于你在 MT5 里改。public void GenerateC(Tester CoreWorker) 是入口,接收回测核心;double RX 先取一个 [0,1) 随机数当阈值。若当前类型是 RANDOM_TYPE_R,就再随机挑一个具体子类型,否则沿用设定类型。 循环里按 RT 分支写 Ci 数组:TYPE_0 让系数随前一值乘随机衰减;TYPE_5 以 0.5 概率决定正负链式;TYPE_1 纯随机正;TYPE_2 对称正负;TYPE_3 按 RX 阈值产生零或正负;TYPE_4 只给非负——这正是 NZDUSD H1 质量陡升的那一支。外汇与贵金属波动高危,改完参数请在策略测试器多周期验证,结果仅代表历史概率倾向。
<span class="keyword">class="kw">public</span> <span class="keyword">class="type">void</span> GenerateC(Tester CoreWorker) { <span class="keyword"> class="type">class="kw">double</span> RX; TYPE_RANDOM RT; RX = RandomX.<b>NextDouble()</b>; <span class="keyword">if</span> (RandomType == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">232, class="num">183, class="num">215);">RANDOM_TYPE_R</span>) <b>RT</b> = (TYPE_RANDOM)RandomX.Next(<span class="number">class="num">0</span>, Enum.GetValues(<span class="keyword">class="kw">typeof</span>(TYPE_RANDOM)).Length-<span class="number">class="num">1</span>); <span class="keyword">else</span> <b>RT</b> = RandomType; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < CoreWorker.Variant.<b>ANum</b>; i++) { <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);">RANDOM_TYPE_0</span>) { <span class="keyword">if</span> (i > <span class="number">class="num">0</span>) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i-<span class="number">class="num">1</span>]*RandomX.<b>NextDouble()</b>; <span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>]=<span class="number">class="num">1.0</span>; } <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">164, class="num">192, class="num">228);">RANDOM_TYPE_5</span>) { <span class="keyword">if</span> (RandomX.<b>NextDouble()</b> >= <span class="number">class="num">0.5</span>) { <span class="keyword">if</span> (i > <span class="number">class="num">0</span>) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i - <span class="number">class="num">1</span>] * RandomX.<b>NextDouble()</b>; <span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>] = <span class="number">class="num">1.0</span>; } <span class="keyword">else</span> { <span class="keyword">if</span> (i > <span class="number">class="num">0</span>) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i - <span class="number">class="num">1</span>] * (-RandomX.<b>NextDouble()</b>); <span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>] = -<span class="number">class="num">1.0</span>; } } <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">255, class="num">246, class="num">200);">RANDOM_TYPE_1</span>) CoreWorker.Variant.Ci[i] = RandomX.<b>NextDouble()</b>; <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">222, class="num">188, class="num">133);">RANDOM_TYPE_2</span>) { <span class="keyword">if</span> (RandomX.<b>NextDouble()</b> >= <span class="number">class="num">0.5</span>) CoreWorker.Variant.Ci[i] = RandomX.<b>NextDouble()</b>; <span class="keyword">else</span> CoreWorker.Variant.Ci[i] = -RandomX.NextDouble(); } <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">204, class="num">204, class="num">204);">RANDOM_TYPE_3</span>) { <span class="keyword">if</span> (RandomX.NextDouble() >= RX) { <span class="keyword">if</span> (RandomX.<span style="background-class="type">color:rgb(class="num">255, class="num">255, class="num">255);"><b>NextDouble()</b></span> >= RX + (<span class="number">class="num">1.0</span> - RX) / <span class="number">class="num">2.0</span>) CoreWorker.Variant.Ci[i] = RandomX.<b>NextDouble()</b>; <span class="keyword">else</span> CoreWorker.Variant.Ci[i] = -RandomX.<b>NextDouble()</b>; } <span class="keyword">else</span> CoreWorker.Variant.Ci[i] = <span class="number">class="num">0.0</span>; } <span class="keyword">if</span> (<b>RT</b> == TYPE_RANDOM.<span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">RANDOM_TYPE_4</span>) {