神经网络变得轻松(第二部分):网络训练和测试·综合运用
📘

神经网络变得轻松(第二部分):网络训练和测试·综合运用

第 3/3 篇

「回放历史K线喂给神经网」

这段循环把指定数量的 Historical Bars 逐根回放,用过去价格差训练一个小神经网络,顺便在图表左上角打印训练进度。外汇与贵金属杠杆高、滑点随机,回测吻合不代表实盘概率等同,务必先在 MT5 策略测试器跑样本外数据。 外层 for 从倒数第 HistoryBars+ 1 根 bar 往前扫,遇到 i==0 就置 add_loop=true,表示下一 tick 要追加新样本。内层先拼一段 StringFormat 文本,包含 Era 计数、误差 dError、未定义占比 dUndefine、预测命中率 dForecast 以及网络近期平均误差 Net.getRecentAverageError(),再用 Comment() 直接覆盖图表注释。 当 i 落在可用区间且 Time.GetData(i) 晚于已学习时间戳、前一根信号不是 -2 时,代码用 i+1 与 i+2 的高低点比较判定局部 sell/buy:若高点两连降且当前更低判 sell,低点两连抬且当前更高判 buy。判定结果转成 1 / -1 / 0 送入 TempData 做 backProp 反向传播;若上一次信号已明确,命中则 dForecast 按 smoothing 因子上调、否则下调,同时压缩 dUndefine。 真正构造输入向量在内层 b 循环:以 r = i + HistoryBars 为窗口起点,越界就 continue;对每个 bar_t 取 open 做基准,把 Close/High/Low 与 open 的差依次 Add 进 TempData。若 open 为 EMPTY_VALUE 或任意 Add 失败,则这次样本作废。

MQL5 / C++
for(class="type">int i=(class="type">int)(bars-MathMax(HistoryBars,class="num">0)-class="num">1); i>=class="num">0 && !stop; i--)
      {
       if(i==class="num">0)
          add_loop=true;
       class="type">class="kw">string s=StringFormat("Study -> Era %d -> %.2f -> Undefine %.2f%% foracast %.2f%%\n %d of %d -> %.2f%% \nError %.2f\n%s -> %.2f",count,dError,dUndefine,dForecast,bars-i+class="num">1,bars,(class="type">class="kw">double)(bars-i+class="num">1.0)/bars*class="num">100,Net.getRecentAverageError(),EnumToString(DoubleToSignal(dPrevSignal)),dPrevSignal);
       Comment(s);
       if(i<(class="type">int)(bars-MathMax(HistoryBars,class="num">0)-class="num">1) && i>class="num">1 && Time.GetData(i)>dtStudied && dPrevSignal!=-class="num">2)
         {
          TempData.Clear();
          class="type">bool sell=(High.GetData(i+class="num">2)<High.GetData(i+class="num">1) && High.GetData(i)<High.GetData(i+class="num">1));
          class="type">bool buy=(Low.GetData(i+class="num">2)<Low.GetData(i+class="num">1) && Low.GetData(i)<Low.GetData(i+class="num">1));
          TempData.Add(buy && !sell ? class="num">1 : !buy && sell ? -class="num">1 : class="num">0);
          Net.backProp(TempData);
          if(DoubleToSignal(dPrevSignal)!=Undefine)
            {
             if(DoubleToSignal(dPrevSignal)==DoubleToSignal(TempData.At(class="num">0)))
                dForecast+=(class="num">100-dForecast)/Net.recentAverageSmoothingFactor;
             else
                dForecast-=dForecast/Net.recentAverageSmoothingFactor;
             dUndefine-=dUndefine/Net.recentAverageSmoothingFactor;
            }
          else
            {
             if(sell || buy)
                dUndefine+=(class="num">100-dUndefine)/Net.recentAverageSmoothingFactor;
            }
         }
       TempData.Clear();
       class="type">int r=i+(class="type">int)HistoryBars;
       if(r>bars)
          class="kw">continue;
class=class="str">"cmt">//---
       for(class="type">int b=class="num">0; b<(class="type">int)HistoryBars; b++)
         {
          class="type">int bar_t=r+b;
          class="type">class="kw">double open=Open.GetData(bar_t);
          TimeToStruct(Time.GetData(bar_t),sTime);
          if(open==EMPTY_VALUE || !TempData.Add(Close.GetData(bar_t)-open) || !TempData.Add(High.GetData(bar_t)-open) || !TempData.Add(Low.GetData(bar_t)-open) ||

◍ 训练循环里的退出条件与信号绘制

上面这段把神经网络在 MT5 历史数据上的训练收口逻辑摊开了。核心是一个 do-while:只要「上一根信号仍为 Undefine 且预测概率不大于 70」或者「平均误差没压到 0.1 以下、且与上轮误差差值的绝对值不小于 0.01」就继续跑,直到手动停止或达标。 TempData 每根 bar 塞了 12 个字段——成交量(除以1000)、月/时/星期几、RSI、CCI、ATR、MACD 主线与信号线,所以判断 TempData.Total() < (int)HistoryBars*12 就是确认数据没凑满设定长度就 break。 i<200 那段才是肉眼可见的落点:信号转成 Undefine 就删掉对应时间的对象,否则在 High/Low 位置画对象。你可以直接把 if(i<200) 改成更大的数,看前 200 根之外的神经网信号长什么样。 calcOutputGradients 里把目标值夹在 [-1,1],delta 为零时梯度直接置 0,避免无谓反向传播。外汇与贵金属波动剧烈,这类模型输出仅作概率参考,实盘前务必用策略测试器跑足样本。

MQL5 / C++
if(!TempData.Add(Volumes.Main(bar_t)/class="num">1000) || !TempData.Add(sTime.mon) || !TempData.Add(sTime.hour) || !TempData.Add(sTime.day_of_week) ||
!TempData.Add(RSI.Main(bar_t)) ||
!TempData.Add(CCI.Main(bar_t)) || !TempData.Add(ATR.Main(bar_t)) || !TempData.Add(MACD.Main(bar_t)) || !TempData.Add(MACD.Signal(bar_t)))
class="kw">break;
}
if(TempData.Total()<(class="type">int)HistoryBars*class="num">12)
class="kw">break;
Net.feedForward(TempData);
Net.getResults(TempData);
dPrevSignal=TempData[class="num">0];
bar_time=Time.GetData(i);
if(i<class="num">200)
{
if(DoubleToSignal(dPrevSignal)==Undefine)
DeleteObject(bar_time);
else
DrawObject(bar_time,dPrevSignal,High.GetData(i),Low.GetData(i));
}
stop=IsStopped();
}
if(add_loop)
count++;
if(!stop)
{
dError=Net.getRecentAverageError();
if(add_loop)
{
Net.Save(FileName+".nnw",dError,dUndefine,dForecast,dtStudied,class="kw">false);
printf("Era %d -> error %.2f %% forecast %.2f",count,dError,dForecast);
}
}
}
class="kw">while((!(DoubleToSignal(dPrevSignal)!=Undefine || dForecast>class="num">70) || !(dError<class="num">0.1 && MathAbs(dError-prev_er)<class="num">0.01 && MathAbs(dUndefine-prev_up)<class="num">0.1 && MathAbs(dForecast-prev_for)<class="num">0.1)) && !stop);
if(count>class="num">0)
{
dtStudied=bar_time;
}
}
class="type">void CNeuron::calcOutputGradients(class="type">class="kw">double targetVals)
{
class="type">class="kw">double delta=(targetVals>class="num">1 ? class="num">1 : targetVals<-class="num">1 ? -class="num">1 : targetVals)-outputVal;
gradient=(delta!=class="num">0 ? delta*CNeuron::activationFunctionDerivative(targetVals) : class="num">0);
}

H1 上 EURUSD 的两类网络实测对照

在 MT5 的 H1 周期对 EURUSD 跑了神经网络训练测试,输入窗口取 20 根烛条,回看最近两年数据。为做对照,在同一终端并排挂了两个 EA:一个用回归网络(分形结构、输出层 1 神经元),另一个用分类网络(Fractal_2、输出层 3 神经元)。 首期训练耗了 12432 根柱线、实机用时 2 小时 20 分,两 EA 命中率都只略超 6%,且结果强烈受初始随机权重影响,这种抖动在外汇模型里属于高风险信号,别拿首期当结论。 跑满 35 期后差距才拉开:回归网络根均方误差 0.68、命中率 12.68%、未识别分形 20.22%;分类网络对应为 0.78、11.22%、24.65%。回归变体在误差与识别率上略占优,但两者仍属同一量级。 实测说明两类组织在训练耗时和预测精度上结果相近,但都吃算力和时间。贵金属与外汇品种叠加此类重训练成本,实盘前务必用策略测试器先跑通本地回测再谈部署。

「随包附带的代码与文件清单」

这一篇里给出的程序都集中在 Experts\NeuroNet_DNG\ 和 Files\ 两个目录下。智能交易系统有两只:Fractal.mq5 用回归神经网络(输出层 1 个神经元)做分形预测,Fractal_2.mq5 用分类神经网络(输出层 3 个神经元)做高低点分类;NeuroNet.mqh 是构建感知器的类库,整体规模在 500 行左右,适合直接读源码改参数。 Files 里的 Fractal 与 Fractal_2 两个目录分别存了两类网络的测试截图,打开 MT5 按路径放好就能在策略测试器里复现。有读者反馈编译 Fractal_2.mq5 时报错,把 NeuroNet.mqh 里 void feedForward(const CArrayObj *&prevLayer); 改成 void feedForward(const CArrayObj *prevLayer); 即可通过,作者已确认这种改法可用。 另有个值得注意的点:回归版 Fractal.mq5 的标签逻辑里,测试计数被当成 Era 数使用,导致图表上不只标最近 200 根,而是把相关蜡烛全打上标。若你跑回归网络发现标签铺满图表,先去查这个计数变量,外汇与贵金属波动剧烈,误标会干扰入场判断,属高风险环境。

常见问题

按时间窗口逐根或逐段读取已收盘K线,循环送入网络输入层;每次更新权重后再推进窗口,避免未来数据泄漏。
可设最大迭代次数、连续N代误差无改善早停,或在信号绘制后人工中断;防止过拟合和无效计算。
可以,小布能按你给的窗口和退出条件回放历史并绘制两类网络信号,省去手动盯训练的过程。
一类倾向平滑跟随、信号少而稳;另一类反应快但杂讯多,需结合退出条件看胜率倾向。
网络定义、训练脚本与数据读取模块需同目录;缺任一会导致编译或运行时找不到函数而中断。