形态搜索的暴力强推方式(第四部分):最小功能(基础篇)
📘

形态搜索的暴力强推方式(第四部分):最小功能(基础篇)

第 1/3 篇

最小功能版的形态搜索器怎么用

这一版把形态搜索器砍到只剩最小功能集,目标很直接:降低门槛,让更多人能跑起设定搜索和市场调查。作者 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,可关掉噪声抑制、出更干净的变体。外汇与贵金属属高风险品种,这类实验性拟合在实盘前务必过历史与样本外验证。

MQL5 / C++
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 质量陡升的那一支。外汇与贵金属波动高危,改完参数请在策略测试器多周期验证,结果仅代表历史概率倾向。

MQL5 / C++
<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;
&nbsp;&nbsp;&nbsp;TYPE_RANDOM RT;
&nbsp;&nbsp;&nbsp;RX = RandomX.<b>NextDouble()</b>;
&nbsp;&nbsp;&nbsp;<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>);
&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> <b>RT</b> = RandomType;
&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; CoreWorker.Variant.<b>ANum</b>; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>)
&nbsp;&nbsp;&nbsp;  &nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (i &gt; <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>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>]=<span class="number">class="num">1.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (RandomX.<b>NextDouble()</b> &gt;= <span class="number">class="num">0.5</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (i &gt; <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>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>] = <span class="number">class="num">1.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (i &gt; <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>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[<span class="number">class="num">0</span>] = -<span class="number">class="num">1.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (RandomX.<b>NextDouble()</b> &gt;= <span class="number">class="num">0.5</span>) CoreWorker.Variant.Ci[i] = RandomX.<b>NextDouble()</b>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[i] = -RandomX.NextDouble();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (RandomX.NextDouble() &gt;= RX)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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> &gt;= 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>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[i] = -RandomX.<b>NextDouble()</b>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> CoreWorker.Variant.Ci[i] = <span class="number">class="num">0.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

常见问题

把基础 EA 挂到目标品种图表,加载外部配置文件即可启动;先小周期验证再放大周期,避免盲开实盘。
主要砍掉了冗余界面与辅助指标,只留搜索内核与配置接口;旧版参数文件不兼容,需按新格式重写。
可以,小布盯盘的 AIGC 已内置类似形态扫描,打开对应品种页就能看到候选形态与概率评分,不用自己写配置。
可喂品种过滤、形态维度权重与随机种子等;格式错 EA 会拒绝加载并写日志,不会私自用默认值硬跑。
有可能,随机内核只保证覆盖样本空间,不代表样本外有效;建议分时段回测并限定系数区间降低过拟合倾向。