神经网络实验(第 7 部分):传递指标·综合运用
「把回归通道与CCI喂给感知机」
这段初始化收尾在 OnInit 里用 iCCI 建了当前周期、周期参数14、收盘价来源的 CCI 句柄,若返回 INVALID_HANDLE 直接 INIT_FAILED,否则 INIT_SUCCEEDED。句柄拿不到,后续所有计算都免谈,MT5 日志会直接报初始化失败。 OnTick 中把四个指标数组都设成时间倒序(ArraySetAsSeries(...,true)),并用 iGetArray 各拉 1010 根数据,任何一个取数失败就 return 不跑感知机。注意三个 i-Regr 缓冲和 CCI 缓冲都按 0~1009 索引对齐,少一根都可能让循环越界。 perceptron1 里 w1/w2/w3 用 x1/x2/x3 减 10.0 做权重偏移,随后对 i=0..1000 做遍历:以最新柱 ind_In1S1[0] 与 ind_In2S1[0] 的大小关系定方向,过程中若两者交叉就 break,否则把两通道差值累加到 sum1。这种结构适合抓回归通道的短期背离,但外汇与贵金属杠杆高,信号失效时回撤可能偏大,验证前先用策略测试器跑历史。
} class=class="str">"cmt">//--- handle_In4S1=iCCI(Symbol(),PERIOD_CURRENT,class="num">14,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In4S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In1S1,true); if(!iGetArray(handle_In1S1,class="num">0,class="num">0,class="num">1010,ind_In1S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In2S1,true); if(!iGetArray(handle_In2S1,class="num">0,class="num">0,class="num">1010,ind_In2S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In3S1,true); if(!iGetArray(handle_In3S1,class="num">0,class="num">0,class="num">1010,ind_In3S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In4S1,true); if(!iGetArray(handle_In4S1,class="num">0,class="num">0,class="num">1010,ind_In4S1)) { class="kw">return; } class=class="str">"cmt">//--- perceptron1(); } 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 sum1 = class="num">0; class="type">class="kw">double sum2 = class="num">0; class="type">class="kw">double sum3 = class="num">0; 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; for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In1S1[class="num">0]>ind_In2S1[class="num">0]) { if(ind_In1S1[i]<ind_In2S1[i]) { class="kw">break; }; sum1+=(ind_In1S1[i]-ind_In2S1[i]); } if(ind_In1S1[class="num">0]<ind_In2S1[class="num">0]) { if(ind_In1S1[i]>ind_In2S1[i]) { class="kw">break; }; sum1+=(ind_In1S1[i]-ind_In2S1[i]); } }
◍ 同向累加与感知机输出的代码拆解
这段逻辑在做一件事:从当前柱往回扫最多 1000 根,只要指标数组首值符号不变,就把同号柱的 ind_In3S1[i] 累加到 sum2,一旦碰到反号就 break。ind_In3S1[0] 大于 0 时只加正值段,小于 0 时只加负值段,a2 就是这段连续同向区的面积和。 第二个循环对 ind_In4S1 完全复刻同一套规则,得到 a3。两个循环上限都写成 1000,实际数据长度不够时会读到未初始化缓冲,MT5 里可能返回 0 或EMPTY_VALUE,建议把上限改成 ArraySize(ind_In3S1)-1 再验证。 最后用 w1*a1 + w2*a2 + w3*a3 抛出感知机加权结果,并通过 Print 把 a1/a2/a3 及分隔线打到专家日志。外汇与贵金属杠杆高,这类特征累加值只反映历史形态强度,对后续拐点的指示倾向而非必然。 下面把核心累加段逐行拆开看:double a1 = sum1 先承接外部已算好的 a1;for(int i=0; i<=1000; i++) 从零号柱向后遍历;if(ind_In3S1[0]>0) 判定当前处于多头段;内层 if(ind_In3S1[i]<0) break 遇到空头柱立刻停;否则 sum2+=ind_In3S1[i] 把同号柱数值叠进 sum2。镜像分支处理 ind_In3S1[0]<0 的空头段,逻辑对称。
class="type">class="kw">double a1 = sum1; for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In3S1[class="num">0]>class="num">0) { if(ind_In3S1[i]<class="num">0) { class="kw">break; }; sum2+=ind_In3S1[i]; } if(ind_In3S1[class="num">0]<class="num">0) { if(ind_In3S1[i]>class="num">0) { class="kw">break; }; sum2+=ind_In3S1[i]; } } class="type">class="kw">double a2 = sum2; for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In4S1[class="num">0]>class="num">0) { if(ind_In4S1[i]<class="num">0) { class="kw">break; }; sum3+=ind_In4S1[i]; } if(ind_In4S1[class="num">0]<class="num">0) { if(ind_In4S1[i]>class="num">0) { class="kw">break; }; sum3+=ind_In4S1[i]; } } class="type">class="kw">double a3 = sum3; Print("a1 = ", a1); Print("a2 = ", a2); Print("a3 = ", a3); Print("Perceptron = ", (w1 * a1 + w2 * a2 + w3 * a3)); Print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); class="kw">return (w1 * a1 + w2 * a2 + w3 * a3); } class="macro">#class="kw">property copyright "class="num">2023, Roman Poshtar" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property strict class="macro">#class="kw">property version "class="num">1.0" input class="type">int Candles= class="num">10; input class="type">int x1 = class="num">1; input class="type">int x2 = class="num">1; input class="type">int x3 = class="num">1; input class="type">int x4 = class="num">1; class="type">int handle_In1S1; class="type">int handle_In2S1; class="type">int handle_In3S1; class="type">int handle_In4S1; class="type">class="kw">double ind_In1S1[]; class="type">class="kw">double ind_In2S1[]; class="type">class="kw">double ind_In3S1[]; class="type">class="kw">double ind_In4S1[]; class=class="str">"cmt">//+------------------------------------------------------------------+
初始化四个指标句柄并在逐笔行情里取数
EA 启动阶段先把四类指标句柄建好:1 期 SMA、100 期 SMA、MACD(12,26,9)、CCI(14),全部绑定当前品种与当前周期、以收盘价计算。任一 handle 返回 INVALID_HANDLE 就直接 INIT_FAILED,避免在 OnTick 里拿着空句柄去读数组导致崩策略。 OnTick 里对四个数组都先 ArraySetAsSeries(...,true) 把时间轴倒序,再用 iGetArray 从 0 号偏移一次性拉 1010 根。若某次取数失败直接 return,不跑 perceptron1(),相当于这一 tick 跳过推理。
- 这根长度不是随手写的:后续感知机若按 1000 根窗口做特征,多留 10 根缓冲能少踩边界错。外汇与贵金属杠杆高、滑点跳空频繁,句柄建不好就开仓的 EA 在重大数据夜容易直接废掉。
别把 1010 当万金油 换小周期如 M1 时,1010 根约 17 小时数据;切到 H4 就跨了半年多。取数长度要跟着周期重算,否则特征尺度漂移,感知机输入会失真。
class="type">int OnInit() { handle_In1S1=iMA(Symbol(),PERIOD_CURRENT,class="num">1,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In1S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- handle_In2S1=iMA(Symbol(),PERIOD_CURRENT,class="num">100,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In2S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- handle_In3S1=iMACD(Symbol(),PERIOD_CURRENT,class="num">12,class="num">26,class="num">9,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In3S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- handle_In4S1=iCCI(Symbol(),PERIOD_CURRENT,class="num">14,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In4S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnTick() { class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In1S1,true); if(!iGetArray(handle_In1S1,class="num">0,class="num">0,class="num">1010,ind_In1S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In2S1,true); if(!iGetArray(handle_In2S1,class="num">0,class="num">0,class="num">1010,ind_In2S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In3S1,true); if(!iGetArray(handle_In3S1,class="num">0,class="num">0,class="num">1010,ind_In3S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In4S1,true); if(!iGetArray(handle_In4S1,class="num">0,class="num">0,class="num">1010,ind_In4S1)) { class="kw">return; } class=class="str">"cmt">//--- perceptron1(); }
「把四个输入压成一个感知机读数」
下面这段函数把四个不同指标通道的线性变化率,按权重叠成一个标量输出。它先给每个输入 x1~x4 减掉 10.0 当作基准权重 w,再把指标首尾差除以周期数,得到单位步长的平均位移 a1~a4。 a1、a2 走的是价格类均线(除以 Point() 转成点数),a3、a4 直接用裸值差,意味着后两者量纲和前两者不同,实盘里权重会失衡。函数末尾用 Print 把四个 a 和加权和打进日志,方便你直接在 MT5 终端抓数核对。 初始化部分挂了两条 SMA(周期 1 和 100)加一条 MACD(12/26/9),任何句柄失败就 INIT_FAILED。外汇和贵金属波动跳点不规整,这种跨量纲加权在极端行情可能给出误导性读数,验证时建议先跑历史样本看分布。 权重输入默认全是 1,但函数里实际用的是 x-10,也就是说默认净权重是 -9。想让某通道真正起正向作用,x 得填大于 10 的数,这个偏移坑很多人第一次抄代码会忽略。
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 a1 = (((ind_In1S1[class="num">0]-ind_In1S1[Candles])/Point())/Candles); class="type">class="kw">double a2 = (((ind_In2S1[class="num">0]-ind_In2S1[Candles])/Point())/Candles); class="type">class="kw">double a3 = ((ind_In3S1[class="num">0]-ind_In3S1[Candles])/Candles); class="type">class="kw">double a4 = ((ind_In4S1[class="num">0]-ind_In4S1[Candles])/Candles); Print("a1 = ", a1); Print("a2 = ", a2); Print("a3 = ", a3); Print("a4 = ", a4); Print("Perceptron = ", (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4)); Print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4); } class="macro">#class="kw">property copyright "class="num">2023, Roman Poshtar" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property strict class="macro">#class="kw">property version "class="num">1.0" input class="type">int x1 = class="num">1; input class="type">int x2 = class="num">1; input class="type">int x3 = class="num">1; input class="type">int x4 = class="num">1; class="type">int handle_In1S1; class="type">int handle_In2S1; class="type">int handle_In3S1; class="type">int handle_In4S1; class="type">class="kw">double ind_In1S1[]; class="type">class="kw">double ind_In2S1[]; class="type">class="kw">double ind_In3S1[]; class="type">class="kw">double ind_In4S1[]; class="type">int OnInit() { handle_In1S1=iMA(Symbol(),PERIOD_CURRENT,class="num">1,class="num">0,MODE_SMA,PRICE_CLOSE); if(handle_In1S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } handle_In2S1=iMA(Symbol(),PERIOD_CURRENT,class="num">100,class="num">0,MODE_SMA,PRICE_CLOSE); if(handle_In2S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } handle_In3S1=iMACD(Symbol(),PERIOD_CURRENT,class="num">12,class="num">26,class="num">9,PRICE_CLOSE); if(handle_In3S1==INVALID_HANDLE)
◍ 指标句柄校验与逐根K线回看
EA 初始化阶段对 CCI(14) 句柄做硬校验:若 iCCI 返回 INVALID_HANDLE 直接 INIT_FAILED,避免后续 OnTick 空指针崩。外汇与贵金属杠杆高,句柄创建失败常见于品种不可交易或周期异常,必须拦在跑盘前。 OnTick 里对四个指标缓冲区统一 ArraySetAsSeries(true),再用 iGetArray 拉 0~1009 共 1010 根数据。注意循环上限写死 1000,实际取了 1010 根,回看深度差 10 根可能让边界判断偏早,复制代码时建议对齐这两个数。 perceptron1 里 w1~w4 用 x1~x4 减 10.0 做偏移,随后 for(i=0;i<=1000) 扫 1001 根:以最新一根 In1 与 In2 的大小关系为基准,向后找第一次反向交叉的位置 c1,作为信号老化距离。这套结构在 MT5 里可直接编译验证,调 1000 这个上限就能改变感知的历史窗口。
if(handle_In3S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- handle_In4S1=iCCI(Symbol(),PERIOD_CURRENT,class="num">14,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In4S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In1S1,true); if(!iGetArray(handle_In1S1,class="num">0,class="num">0,class="num">1010,ind_In1S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In2S1,true); if(!iGetArray(handle_In2S1,class="num">0,class="num">0,class="num">1010,ind_In2S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In3S1,true); if(!iGetArray(handle_In3S1,class="num">0,class="num">0,class="num">1010,ind_In3S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In4S1,true); if(!iGetArray(handle_In4S1,class="num">0,class="num">0,class="num">1010,ind_In4S1)) { class="kw">return; } class=class="str">"cmt">//--- perceptron1(); } 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">int c1=class="num">0; class="type">int c2=class="num">0; class="type">int c3=class="num">0; 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; for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In1S1[class="num">0]>ind_In2S1[class="num">0]) { if(ind_In1S1[i]<ind_In2S1[i]) { c1=i; class="kw">break; } } if(ind_In1S1[class="num">0]<ind_In2S1[class="num">0]) { if(ind_In1S1[i]>ind_In2S1[i]) {
感知机输出的斜率与权重拼装
这段逻辑在前面找到各指标穿越零轴的位置 c1/c2/c3 后,开始算每条线的「平均变化率」。a1 与 a2 用价格差除以 Point() 再除以柱数,得到每根 K 线的点数斜率;a3、a4 直接拿指标值差除以柱数,量纲不同,接进同一个加权式前心里要有数。 循环里对 ind_In3S1、ind_In4S1 都只向后扫 1000 根,若当前值大于 0 就找第一个小于 0 的索引,反之找第一个大于 0 的,c2、c3 即为零轴反向穿越点。扫描上限写死 1000,意味着超过 1000 根没反转就取不到值,实盘低波动品种要当心。 最后 Print 把 a1~a4 和加权结果全打出来,返回 w1*a1 + w2*a2 + w3*a3 + w4*a4。外汇与贵金属波动随机性强、杠杆风险高,这套斜率加权只是特征合成,信号方向倾向参考、不代表确定性。 文件尾的 input 把 Candles 默认设 10,x1~x4 全为 1,也就是四个特征等权。想看哪条线主导,直接改 x1~x4 再开 MT5 跑一遍 Print 即可。
c1=i; class="kw">break; } } } class="type">class="kw">double a1 = (((ind_In1S1[class="num">0]-ind_In1S1[c1])/Point())/c1); class="type">class="kw">double a2 = (((ind_In2S1[class="num">0]-ind_In2S1[c1])/Point())/c1); for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In3S1[class="num">0]>class="num">0) { if(ind_In3S1[i]<class="num">0) { c2=i; class="kw">break; } } if(ind_In3S1[class="num">0]<class="num">0) { if(ind_In3S1[i]>class="num">0) { c2=i; class="kw">break; } } } class="type">class="kw">double a3 = ((ind_In3S1[class="num">0]-ind_In3S1[c2])/c2); for(class="type">int i=class="num">0; i<=class="num">1000; i++) { if(ind_In4S1[class="num">0]>class="num">0) { if(ind_In4S1[i]<class="num">0) { c3=i; class="kw">break; } } if(ind_In4S1[class="num">0]<class="num">0) { if(ind_In4S1[i]>class="num">0) { c3=i; class="kw">break; } } } class="type">class="kw">double a4 = ((ind_In4S1[class="num">0]-ind_In4S1[c3])/c3); Print("a1 = ", a1); Print("a2 = ", a2); Print("a3 = ", a3); Print("a4 = ", a4); Print("Perceptron = ", (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4)); Print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4); } class="macro">#class="kw">property copyright "class="num">2023, Roman Poshtar" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property strict class="macro">#class="kw">property version "class="num">1.0" input class="type">int Candles= class="num">10; input class="type">int x1 = class="num">1; input class="type">int x2 = class="num">1; input class="type">int x3 = class="num">1; input class="type">int x4 = class="num">1;
「双均线句柄与感知机入口的接线方式」
这段逻辑给 EA 接了两个 SMA 句柄:周期 1 和周期 100,都用当前品种、当前周期、收盘价计算。OnInit 里只要任一 handle 返回 INVALID_HANDLE,就直接 INIT_FAILED,避免后续空指针读数组。 OnTick 每次把两个指标数组设为时间序列(ArraySetAsSeries(...,true)),各拉 1010 根到 ind_In1S1 / ind_In2S1。注意这里注释写的是 i-Regr,但实际句柄是 iMA,复制时别被注释误导。 perceptron1 里 w1~w7 全部用 x 减 10.0 做权重偏移,而 x5/x6/x7 在本节 input 中写死为 1,也就是对应权重恒为 -9.0。a1 取两根均线的差值除以 Point(),把价格距转换成点数。 外汇与贵金属杠杆高、滑点随机,这类感知机权重若直接上实盘,信号翻转可能非常频繁,建议先开 MT5 用策略测试器跑 EURUSD 的 M5 观察 1010 根窗口内的触发密度。
input class="type">int x5 = class="num">1; input class="type">int x6 = class="num">1; input class="type">int x7 = class="num">1; class="type">int handle_In1S1; class="type">int handle_In2S1; class="type">class="kw">double ind_In1S1[]; class="type">class="kw">double ind_In2S1[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { handle_In1S1=iMA(Symbol(),PERIOD_CURRENT,class="num">1,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In1S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- handle_In2S1=iMA(Symbol(),PERIOD_CURRENT,class="num">100,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- if the handle is not created if(handle_In2S1==INVALID_HANDLE) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In1S1,true); if(!iGetArray(handle_In1S1,class="num">0,class="num">0,class="num">1010,ind_In1S1)) { class="kw">return; } class=class="str">"cmt">//--- class=class="str">"cmt">//--- get data from the three buffers of the i-Regr indicator ArraySetAsSeries(ind_In2S1,true); if(!iGetArray(handle_In2S1,class="num">0,class="num">0,class="num">1010,ind_In2S1)) { class="kw">return; } class=class="str">"cmt">//--- perceptron1(); } 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">int c1=class="num">0; class="type">class="kw">double sum1 = class="num">0; class="type">class="kw">double sum2 = class="num">0; 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 w5 = x5 - class="num">10.0; class="type">class="kw">double w6 = x6 - class="num">10.0; class="type">class="kw">double w7 = x7 - class="num">10.0; class="type">class="kw">double a1 = ((ind_In1S1[class="num">0]-ind_In2S1[class="num">0])/Point()); for(class="type">int i=class="num">0; i<=Candles; i++)
◍ 双线差值的逐段累加与穿越判定
这段逻辑在算两条指标线(ind_In1S1 与 ind_In2S1)的差值特征,并喂给前面的感知机公式。先是一个未贴出的循环把 sum1 累加出 a1,随后又跑 1000 根 K 线找「当前谁在上、差值何时翻面」的首次穿越点,把穿越前的累计差赋给 a2(sum1 原值)和 a3(sum2)。 具体看第二个 for:若最新值 ind_In1S1[0] 大于 ind_In2S1[0],就从 i=0 向后扫,一旦某根出现 ind_In1S1[i] < ind_In2S1[i] 立即 break,否则 sum2 持续加差值;反过来若短线在下方,则找上穿即停。这意味着 a3 只统计「未穿越段」的累计偏离,最大扫描深度 hardcoded 为 1000。 a4、a5 用 Candles 根数把两条线的整体斜率归一化到 Point 单位((当前- Candles根前)/Point()/Candles);第三个循环复用同样穿越逻辑记下穿越位置 c1,再算 a6、a7 为「到穿越点为止」的每根平均点差。最后 Print 出 a1~a7 与加权求和结果,return 该值。 开 MT5 把这段贴进 EA 的 return 前,用「工具→专家日志」看 a1~a7 输出,能直接验证哪一段差值在驱动信号;外汇与贵金属杠杆高,输出仅作概率参考,实盘须自担风险。
{
sum1+=ind_In1S1[i]-ind_In2S1[i];
}
class="type">class="kw">double a2 = sum1;
for(class="type">int i=class="num">0; i<=class="num">1000; i++)
{
if(ind_In1S1[class="num">0]>ind_In2S1[class="num">0])
{
if(ind_In1S1[i]<ind_In2S1[i])
{
class="kw">break;
};
sum2+=(ind_In1S1[i]-ind_In2S1[i]);
}
if(ind_In1S1[class="num">0]<ind_In2S1[class="num">0])
{
if(ind_In1S1[i]>ind_In2S1[i])
{
class="kw">break;
};
sum2+=(ind_In1S1[i]-ind_In2S1[i]);
}
}
class="type">class="kw">double a3 = sum2;
class="type">class="kw">double a4 = (((ind_In1S1[class="num">0]-ind_In1S1[Candles])/Point())/Candles);
class="type">class="kw">double a5 = (((ind_In2S1[class="num">0]-ind_In2S1[Candles])/Point())/Candles);
for(class="type">int i=class="num">0; i<=class="num">1000; i++)
{
if(ind_In1S1[class="num">0]>ind_In2S1[class="num">0])
{
if(ind_In1S1[i]<ind_In2S1[i])
{
c1=i;
class="kw">break;
}
}
if(ind_In1S1[class="num">0]<ind_In2S1[class="num">0])
{
if(ind_In1S1[i]>ind_In2S1[i])
{
c1=i;
class="kw">break;
}
}
}
class="type">class="kw">double a6 = (((ind_In1S1[class="num">0]-ind_In1S1[c1])/Point())/c1);
class="type">class="kw">double a7 = (((ind_In2S1[class="num">0]-ind_In2S1[c1])/Point())/c1);
Print("a1 = ", a1);
Print("a2 = ", a2);
Print("a3 = ", a3);
Print("a4 = ", a4);
Print("a5 = ", a5);
Print("a6 = ", a6);
Print("a7 = ", a7);
Print("Perceptron = ", (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4 + w5 * a5 + w6 * a6 + w7 * a7));
Print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4 + w5 * a5 + w6 * a6 + w7 * a7);
}双 EA 架构把优化结果直接搬进实盘
把指标间距塞进感知器做特征,是这套系统的核心思路。第一个 EA 只干一件事:在 MT5 里跑参数优化,把欧美观赏 EURUSD H1 上 MA1/MA200/CCI42/StdDev60 的组合扫一遍,输出前 50 个最优解。第二个 EA 不重新算参数,而是把优化导出的 CSV 贴进代码,实时比对新K线特征,命中就开仓。 具体特征维度固定为四个:a1 是 MA1 与最近 8 根蜡烛的距离,a2 是 MA100 同口径距离,a3 是 CCI 距离,a4 是 StdDev 距离。Candles 参数锁死在 8,不参与优化。回测窗口取 2021.10.10–2022.10.10 做优化、2022.10.10–2023.10.10 做正向测试,初始存款 10000、杠杆 1:500,止损 400 点、止盈 830 点,这套配置下优化头名利润 266.45、交易次数 239。 别把正态当圣经 优化用「最大盈利」模式只是实验取向,EURUSD 这类外汇品种受利差与流动性突变影响,1 年样本外的正向表现可能大幅衰减,贵金属更甚,杠杆 1:500 下回撤可能吞噬本金。 第二个 EA 的开仓判定很直白:感知器输出小于 -Param 且空仓、点差达标就卖;大于 Param 就买。下方代码里 perceptron1() 把四个权重 w1~w4 各减 10.0 再乘特征距,EURUSD 数组前几行就是导进来的前 50 优解片段(x1=2,x2=1,x3=9,Param=5000)。复制进 MT5 编译,你能直接验证这套匹配逻辑是否如预期触发。
class=class="str">"cmt">//SELL++++++++++++++++++++++++++++++++++++++++++++++++ if((perceptron1()<-Param) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment)==class="num">0) && (SpreadS1<=MaxSpread)) { OpenSell(symbolS1.Name(), LotsXSell, TP, SL, EAComment); } class=class="str">"cmt">//BUY++++++++++++++++++++++++++++++++++++++++++++++++ if((perceptron1()>Param) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment)==class="num">0) && (SpreadS1<=MaxSpread)) { OpenBuy(symbolS1.Name(), LotsXBuy, TP, SL, EAComment); } 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 a1 = ((ind_In1S1[class="num">0]-ind_In1S1[Candles])/PointS1); class="type">class="kw">double a2 = ((ind_In2S1[class="num">0]-ind_In2S1[Candles])/PointS1); class="type">class="kw">double a3 = (ind_In3S1[class="num">0]-ind_In3S1[Candles]); class="type">class="kw">double a4 = (ind_In4S1[class="num">0]-ind_In4S1[Candles]); class="kw">return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4); } class="type">class="kw">string EURUSD[][class="num">8]= { {"Profit","Trades","x1","x2","x3","x4","Param"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">8","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">13","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">11","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">10","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">8","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">12","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">20","class="num">5000"}, {"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">14","class="num">5000"},
「参数矩阵里的样本密度分布」
上面这段数据阵是某组 EA 优化结果的原始记录,每一行七个字段依次对应:价格阈值、周期参数、开关标志A、开关标志B、小时窗口、随机种子、手数基准。前两条 266.45 阈值下只跑了 2 组随机种子(1、9 各出现两次变体),而后从 259.69 阈值开始,连续 21 条记录全部是 239 周期、双零开关、12 小时窗口,手数锁定 5500,仅随机种子在 1 到 18 之间跳动。 这种排布说明优化器在 259.69 附近做了密集采样:21 个样本里种子 8 和 9 各出现 2 次,种子 17 出现 2 次,其余大多单发。外汇与贵金属品种上跑这类网格,杠杆和滑点会放大样本间差异,回测漂亮不等于实盘能复现,属典型高风险验证场景。 如果你在 MT5 里导出自家 EA 的优化报告,直接比对阈值切换点前后的样本数——当某个参数值突然从 2 组暴涨到 20 组以上,往往意味着该区域存在局部平原,过拟合概率倾向升高,值得手动抽几条种子重跑确认。
{"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">2","class="num">5000"},
{"class="num">266.45","class="num">239","class="num">2","class="num">1","class="num">9","class="num">3","class="num">5000"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">17","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">8","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">1","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">9","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">16","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">18","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">11","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">7","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">15","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">8","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">9","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">17","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">6","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">15","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">4","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">7","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">11","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">14","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">1","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">5","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">3","class="num">5500"},◍ EURUSD 参数表的遍历与字段拆解
上面这段二维字符串数组里,EURUSD 的每一行固定 7 个字段,前两个是 259.69 与 239,最后一个是 5500,中间四个为整数型控制位。从给出的 19 行样本看,第 6 个字段(索引 5)在 0 到 19 之间波动,出现频率最高的是 12(共 2 次),最小为 0,最大为 19,这种离散分布适合直接喂给网格或马丁类逻辑做偏移步进。 循环体用 ArraySize(EURUSD)/8 来推算有效行数,说明数组在内存里按 8 字节或 8 元素对齐填充,实际数据行要比物理长度少一行(i 从 1 开始,到 size/8 - 1 结束)。在 MT5 里把这段粘进脚本,Print 出 i 的上限,就能确认你的 EURUSD 表到底载进了多少组参数。 comm 把前两个字段相加得 498.69,这个和很可能用作手续费或点值阈值;x1~x4 分别把索引 2 到 5 的字符串转成整型,转换前务必确认原字符串不含空格,否则 StringToInteger 会返回 0 导致逻辑偏移。外汇与贵金属杠杆品种波动剧烈,这类参数表回测若不隔离滑点,实盘概率会明显偏离。
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">0","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">12","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">16","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">8","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">10","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">16","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">18","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">13","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">9","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">12","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">11","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">7","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">15","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">14","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">3","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">19","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">0","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">17","class="num">5500"},
{"class="num">259.69","class="num">239","class="num">0","class="num">0","class="num">12","class="num">2","class="num">5500"}
};
for(class="type">int i=class="num">1; i<=(ArraySize(EURUSD)/class="num">8)-class="num">1; i++)
{
comm=(EURUSD[i][class="num">0]+EURUSD[i][class="num">1]);
x1=(class="type">int)StringToInteger(EURUSD[i][class="num">2]);
x2=(class="type">int)StringToInteger(EURUSD[i][class="num">3]);
x3=(class="type">int)StringToInteger(EURUSD[i][class="num">4]);
x4=(class="type">int)StringToInteger(EURUSD[i][class="num">5]);感知机阈值与持仓序列的开仓闸门
这段逻辑把外部字符串参数转成整数阈值:先取 EURUSD[i][6] 的文本值,强转成 int 后赋给 Param,它直接决定感知机输出要多极端才允许下单。 SELL 分支要求 perceptron1() 小于 -Param,且当前魔法码下同注释前缀的卖单数为 0、点差不超过 MaxSpread;BUY 分支对称,要求 perceptron1() 大于 Param。两个条件都受 NewOpen 与 CalculateSeries(Magic)<MaxSeries 的总仓位序列上限约束,外汇与贵金属杠杆高,点差突变时这类硬阈值可能直接屏蔽信号。 CalculateSeries 并非统计总持仓,而是按 Comment 去重计数:遍历 PositionsTotal 所有持仓,筛选同 Magic 且 Comment 变化的条目,total 每遇新注释名才加 1。这意味着同一魔法码下挂不同注释前缀的网格,会被当成独立序列计入上限。 把这段代码贴进 MT5 策略测试器,把 MaxSeries 设为 3、Param 设为 5,观察 EURUSD 在伦敦时段触发卖单的频率,能验证阈值对开仓密度的压制效果。
Param=(class="type">int)StringToInteger(EURUSD[i][class="num">6]); class=class="str">"cmt">//SELL++++++++++++++++++++++++++++++++++++++++++++++++ if((NewOpen==true) && (CalculateSeries(Magic)<MaxSeries) && (perceptron1()<-Param) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_SELL, EAComment+" En_"+comm)==class="num">0) && (SpreadS1<=MaxSpread)) { OpenSell(symbolS1.Name(), LotsXSell, TP, SL, EAComment+" En_"+comm); } class=class="str">"cmt">//BUY++++++++++++++++++++++++++++++++++++++++++++++++ if((NewOpen==true) && (CalculateSeries(Magic)<MaxSeries) && (perceptron1()>Param) && (CalculatePositions(symbolS1.Name(), Magic, POSITION_TYPE_BUY, EAComment+" En_"+comm)==class="num">0) && (SpreadS1<=MaxSpread)) { OpenBuy(symbolS1.Name(), LotsXBuy, TP, SL, EAComment+" En_"+comm); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate Positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CalculateSeries(class="type">class="kw">ulong mag) { class="type">int total=class="num">0; class="type">class="kw">string com=""; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(position.SelectByIndex(i)) { if(position.Magic()==mag) { if(com!=position.Comment()) { com=position.Comment(); total++; } } } } class=class="str">"cmt">//--- class="kw">return(total); }
「把这条线请下神坛」
随文附带的 EA 压缩包约 23.32 KB,里面分了三条传递路线:Distance 1/2/3 把价格距离喂给感知器,Accumulation 1/2 传积累量,Angle 1/2 传指标倾角,Combo 1 做组合输入,另附 Perceptron–opt 与 Perceptron–trade 分别管优化和交易参数。前六个月前向测试里,EA 把余额推上去那段很猛,但外汇和贵金属杠杆高、回撤说翻就翻,别把这路子当圣杯。 想验证就自己下 ZIP 丢进 MT5 回测,重点看 Angle 2 和 Combo 1 在滑点放大后的表现。作者留了论坛和私信通道,真卡住再去问,比瞎调参省时间。