神经网络实验(第 4 部分):模板(基础篇)
📘

神经网络实验(第 4 部分):模板(基础篇)

第 1/3 篇

◍ 从感知器到模板:这一轮实验要测什么

前几轮用 DeepNeuralNetwork.mqh 搭的神经网络做实验,结果并不理想。策略测试器里跑智能优化算法,效率偏低;反倒是简单感知器给出的成绩更稳。问题可能出在喂给网络的数据本身——散射范围太大,任务不匹配,加上读者对止盈止损比的质疑,这一轮都得重新考虑。 我在 MQL5 论坛翻到一个形态跟踪算法,核心思路是把价格规整成某种形态再判入场,并不绑定神经网络使用。我管它叫“技术”模板,只是自己觉得顺手,不一定严谨。 本轮重点就三个:传多少数据量、取多深的历史、形态对多空哪边更友好。还要看该多传参数还是少传。全程只用 MetaTrader 5 原生工具,不碰第三方软件,过程会写得像分步记录,边跑边看结果。外汇与贵金属波动剧烈,这类实验结论仅代表历史样本表现,实盘存在显著回撤风险。

EURUSD H1 的优化与前瞻测试配置

做这套价格形态 EA 的回测,先把品种和周期钉死:外汇品种 EURUSD,时间帧 H1,形态模板覆盖扇形、平行四边形与三角形。5 位报价环境下,止损止盈要按点值重算——止损 600 点、止盈 60 点的组合里,考虑点差给止盈加 30 点;另有余额类参数 200 与 230,以及 200 点止损对 430 点止盈(止盈约为止损 2 倍、余额止盈 30 点)的设定,直接照抄容易爆仓,需按自己经纪商点差复核。 优化用「仅开盘价」配「最复杂准则」模式,这点很关键:相比「最大盈利能力」,复杂准则给出的曲线更稳、样本外衰减更慢。优化窗口取 3 年(2019.02.18–2022.02.18),前瞻验证取随后 1 年(2022.02.18–2023.02.18),按多篇神经网络实验里的算法,让若干个最优参数同时跑前向来交叉印证。 这一轮把优化次数拉到 20 次、前向同时挂 40 个结果,都比早先测试翻倍。EA 本身基于 DeepNeuralNetwork.mqh 的「快速(遗传算法)」感知器做优化,绕开了第 2 部分里慢到没法用的旧算法,直接交给 MT5 遗传优化器跑。初始本金 10000、杠杆 1:500,外汇与贵金属杠杆交易风险极高,小本金高杠杆随时可能穿仓。 反复跑 20–40 次遗传优化靠的是一个 Delphi 写的小自动点击器:填好次数,鼠标悬在策略测试器「开始」按钮上,它侦测按钮颜色变化自动连点,跑完指定循环自行退出。作者不公开程序,只私发,思路却值得抄——MT5 原生不支循环优化,用外部轻量脚本补这个缺口,能省掉大量人工重跑成本。

「把价格形态压成整数点距喂给神经网络」

所谓模板,本质是一组随行情浮动的几何参照,数值始终落在某个区间内,正好适合做神经网络的输入。我们不直接用指标,而是操控烛条,把参考点之间的相对位置四舍五入成整数点距,让感知器和深度网络更容易消化,触发条件变多,前端计算负载也明显降下来。 模板不要求用相同数量的蜡烛,这反而多出一个字段来反映历史收盘价的相关性。以 H1 为例,24 根蜡烛就是一天,48 根就是两天;扇形、平行四边形、三角形三类构造,各自有 4 值或 8 值版本,覆盖 24 或 48 根蜡烛。 具体传递什么?以 4 值扇形(24 根蜡烛)为例,喂给网络的是端点 1 到端点 2、3、4、5 的四舍五入点距。8 值版本则再多出到端点 6~9 的距离。平行四边形里,5 位小数报价品种常取端点 10 加减 800 点生成端点 2 和 1(24 根蜡烛情形),两天情形加到 1200 点,避免构造被价格跨度撑破。 三角形模板同理:端点 4 加减 800 点(或两天 1200 点)得端点 2、3,再算若干端点间舍入差。注意原文提到端点 2 和端点 1 差值常固定为 8 点,这是人为约定的舍入基准,不是行情必然结果。 下面这段感知器函数展示了最基础的取整逻辑:取 24 根与 1 根蜡烛收盘价差,除以 Point() 转点距,再除以 100 取整百输出。外汇和贵金属波动剧烈,这种模板在历史跳空时可能失真,上 MT5 挂 EA 前先肉眼核对端点连线。 让小布替你跑这套 把附件 EA 里的 w1~w4 偏移从 10.0 改成你品种的典型点距,再切到 48 根蜡烛看信号密度变化,比硬看原文直观。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  The PERCEPRRON - a perceiving and recognizing function           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double perceptron1()
  {
   class="type">class="kw">double w1 = x1 - class="num">10.0;
   class="type">class="kw">double w2 = x2 - class="num">10.0;
   class="type">class="kw">double w3 = x3 - class="num">10.0;
   class="type">class="kw">double w4 = x4 - class="num">10.0;

   class="type">int a1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">1))/Point()));
   a1 = (class="type">int)(a1/class="num">100)*class="num">100;

◍ 把K线切成四段喂给感知机

这段逻辑干的事很直接:取当前周期下,24根K线前收盘与第1、7、13、19根收盘的差值,除以 Point() 转成整数点,再除以100取整乘100,把价格位移压缩成百点级的离散档位。 前半段函数用 a1~a4 算完直接返回加权结果,后半段 CandlePatterns 把同样四个值写进 xInputs 数组的第0~3位,供后续模型调用。注意它固定看 PERIOD_CURRENT,换周期时这些偏移量含义会完全变。 perceptron1 里把外部变量 x1~x4、y1~y4 各减 10.0 作为权重初值,再重新取 a1(24与1根收盘差)。外汇和贵金属波动受杠杆放大,百点档位在镑美可能只是日常噪音,在 XAUUSD 常对应一波像样回调,实盘前务必在 MT5 策略测试器用真实点差跑一遍。

MQL5 / C++
class="type">int a2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">7))/Point()));
 a2 = (class="type">int)(a2/class="num">100)*class="num">100;
 class="type">int a3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">13))/Point()));
 a3 = (class="type">int)(a3/class="num">100)*class="num">100;
 class="type">int a4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">19))/Point()));
 a4 = (class="type">int)(a4/class="num">100)*class="num">100;

 class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|percentage of each part of the candle respecting total size          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CandlePatterns(class="type">class="kw">double &xInputs[])
  {

  class="type">int a1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">1))/Point()));
  xInputs[class="num">0] = (class="type">int)(a1/class="num">100)*class="num">100;
  class="type">int a2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">7))/Point()));
  xInputs[class="num">1] = (class="type">int)(a2/class="num">100)*class="num">100;
  class="type">int a3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">13))/Point()));
  xInputs[class="num">2] = (class="type">int)(a3/class="num">100)*class="num">100;
  class="type">int a4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">19))/Point()));
  xInputs[class="num">3] = (class="type">int)(a4/class="num">100)*class="num">100;  

  class="kw">return(class="num">1);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  The PERCEPRRON - a perceiving and recognizing function            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double perceptron1()
  {
  class="type">class="kw">double w1 = x1 - class="num">10.0;
  class="type">class="kw">double w2 = x2 - class="num">10.0;
  class="type">class="kw">double w3 = x3 - class="num">10.0;
  class="type">class="kw">double w4 = x4 - class="num">10.0;

  class="type">class="kw">double v1 = y1 - class="num">10.0;
  class="type">class="kw">double v2 = y2 - class="num">10.0;
  class="type">class="kw">double v3 = y3 - class="num">10.0;
  class="type">class="kw">double v4 = y4 - class="num">10.0;

  class="type">int a1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">1))/Point()));

把多周期价差压成百点整数档

这段逻辑在算 symbolS1 上「当前 K 线往前数第 24 根」相对近期若干根的收盘价落差,统一除以 Point() 转成点数,再强制按 100 点取整。a1~a4 取的是 24 对 4、7、10、1 的价差,b1~b4 取的是 24 对 13、16、19、22 的价差,两组间距错开,等于把近一个月(按日线近似)到近几根的价格位移切成 8 个百点档位。 整数部分强转加 (x/100)*100 是个典型「降精度」写法:比如真实差值是 347 点,落档后只剩 300,噪声被砍掉,但你也丢了 47 点的细结构。权重 w1~w4、v1~v4 在外层给定,return 那行就是 8 个百点档的加权和,输出一个可比的数值指纹。 CandlePatterns 函数换了个写法,把同样思路的结果塞进 xInputs[] 数组的前 3 格(示例只贴到 xInputs[2]),方便后续批量喂给分类或打分模块。外汇和贵金属点值随合约而异,PERIOD_CURRENT 下 Point() 可能是 0.00001 也可能是 0.1,直接抄代码前先在 MT5 观测窗口确认 symbolS1 的点值,否则百点档会整体偏移。 别把正态当圣经:这种百点取整会人为制造离散堆积,回测里某些档位命中率偏高可能只是取整伪像,上实盘前用不同品种各跑 200 根以上核对分布。

MQL5 / C++
  a1 = (class="type">int)(a1/class="num">100)*class="num">100;
  class="type">int a2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">4))/Point()));
  a2 = (class="type">int)(a2/class="num">100)*class="num">100;
  class="type">int a3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">7))/Point()));
  a3 = (class="type">int)(a3/class="num">100)*class="num">100;
  class="type">int a4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">10))/Point()));
  a4 = (class="type">int)(a4/class="num">100)*class="num">100;

  class="type">int b1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">13))/Point()));
  b1 = (class="type">int)(b1/class="num">100)*class="num">100;
  class="type">int b2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">16))/Point()));
  b2 = (class="type">int)(b2/class="num">100)*class="num">100;
  class="type">int b3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">19))/Point()));
  b3 = (class="type">int)(b3/class="num">100)*class="num">100;
  class="type">int b4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">22))/Point()));
  b4 = (class="type">int)(b4/class="num">100)*class="num">100;

  class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4   +   v1 * b1 + v2 * b2 + v3 * b3 + v4 * b4);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|percentage of each part of the candle respecting total size       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CandlePatterns(class="type">class="kw">double &xInputs[])
  {

  class="type">int a1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">1))/Point()));
  xInputs[class="num">0] = (class="type">int)(a1/class="num">100)*class="num">100;
  class="type">int a2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">4))/Point()));
  xInputs[class="num">1] = (class="type">int)(a2/class="num">100)*class="num">100;
  class="type">int a3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">7))/Point()));
  xInputs[class="num">2] = (class="type">int)(a3/class="num">100)*class="num">100;

「把价差压成百点整数再喂给感知机」

这段收尾代码干的事很直接:先把不同周期收盘价落差换算成点数,再除以 100 取整乘 100,把噪声级的小跳动抹掉,只留百点级别位移。比如 24 周期与 10 周期收盘差换算后,a4 被压成百点整数写进 xInputs[3],后面 g1~g4 用 13/16/19/22 周期重复同一套路,等于用 24 周期收盘做锚,量出短中段的阶跃幅度。 感知机函数 perceptron1 另起炉灶,用 48 周期收盘当远端锚,算它与 1/13/25/37 周期收盘的点差,同样百点化得到 a1~a4。权重 w1~w4 是 x1~x4 各减 10.0,最后返回 w 与 a 的内积。外汇和贵金属这类高杠杆品种,点差百点化会吃掉细波信息,回测时建议先打印 a1~a4 分布看样本覆盖。 开 MT5 把这段贴进 EA,把 symbolS1 换成 XAUUSD 或 EURUSD,调 PERIOD_CURRENT 到 H1,跑一根历史 K 线就能看到 xInputs 和 perceptron1 返回值。若返回值连续多根贴零,说明近期波动被百点门槛滤光了,那时该把除数从 100 降到 50 试水。

MQL5 / C++
  class="type">int a4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">10))/Point()));
  xInputs[class="num">3] = (class="type">int)(a4/class="num">100)*class="num">100;

  class="type">int g1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">13))/Point()));
  xInputs[class="num">4] = (class="type">int)(g1/class="num">100)*class="num">100;
  class="type">int g2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">16))/Point()));
  xInputs[class="num">5] = (class="type">int)(g2/class="num">100)*class="num">100;
  class="type">int g3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">19))/Point()));
  xInputs[class="num">6] = (class="type">int)(g3/class="num">100)*class="num">100;
  class="type">int g4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">24)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">22))/Point()));
  xInputs[class="num">7] = (class="type">int)(g4/class="num">100)*class="num">100;

  class="kw">return(class="num">1);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  The PERCEPRRON - a perceiving and recognizing function          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double perceptron1()
  {
  class="type">class="kw">double w1 = x1 - class="num">10.0;
  class="type">class="kw">double w2 = x2 - class="num">10.0;
  class="type">class="kw">double w3 = x3 - class="num">10.0;
  class="type">class="kw">double w4 = x4 - class="num">10.0;

  class="type">int a1 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">48)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">1))/Point()));
  a1 = (class="type">int)(a1/class="num">100)*class="num">100;
  class="type">int a2 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">48)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">13))/Point()));
  a2 = (class="type">int)(a2/class="num">100)*class="num">100;
  class="type">int a3 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">48)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">25))/Point()));
  a3 = (class="type">int)(a3/class="num">100)*class="num">100;
  class="type">int a4 = (class="type">int)(((iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">48)-iClose(symbolS1.Name(),PERIOD_CURRENT,class="num">37))/Point()));
  a4 = (class="type">int)(a4/class="num">100)*class="num">100;

  class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|percentage of each part of the candle respecting total size        |

常见问题

把单根 K线拆成开高低收四段数值,再做归一化后输入;前瞻测试用样本外数据,别拿优化区间当验证。
取相邻波段拐点价差除以最小变动单位,舍入成整数;EURUSD H1 常用 10~50 点距档位,先小范围回测再定。
可以。小布能按你给的周期组合自动算价差、归整到百点档并生成喂网格式,你只需复核样本划分。
先做主周期筛选或降维,只留与标的走势相关性高的周期;百点档本身已降精度,维度控制在 10 内较稳。
看样本外准确率与最大回撤,而非训练集拟合度;外汇高风险,概率结论都加可能限定,别当确定性信号。