神经网络在交易中的实际应用。 是时候进行实践了·综合运用
🧠

神经网络在交易中的实际应用。 是时候进行实践了·综合运用

(3/3)· EURUSD 上跑通 Online 模块后,怎样把训练、测试、优化甩在系统外只留交易

含代码示例偏理论 第 3/3 篇
把训练逻辑和优化条件全塞进 EA 是常见误区,NNM 越臃肿实盘越容易卡死。很多人直接在智能交易系统里算信号交叉,却忽略把执行条件移到模块内只收二元输出。本文用 EURUSD 的已训网络演示怎么反过来做。

◍ 从 CSV 灌入指标并初始化交易环境

把外部导出的指标序列直接喂给 EA,是绕开实时计算、做历史回放验证的常用路数。下面这段初始化逻辑先打开公共目录下的 Indicator.csv,用分号作分隔,第一遍只数行数,给两个缓冲区 Buf_0、Buf_1 按 bars 总量Resize。 第二遍重新读文件:首列用 StringToTime 转成时间计数赋给 Count,次列 StringToDouble 进 Buf_0;当 k 超过 Period-1 时,把前 Period 根 Buf_0 累加再除以 Period 写进 Buf_1,本质是在算一个长度为 Period 的滑动均值。注意原文里 h=Период-1 的 Период 若未定义会编译失败,实盘前需补成输入参数或宏。 交易端设置了偏离 10 点、市价回退填充、异步下单,这些在外盘贵金属上会明显影响滑点表现。外汇与贵金属杠杆高,异步模式配合 10 点 deviation 在流动性差时段可能触发大量拒单或追价,开 MT5 前先把 deviation 调到与品种点差匹配。 OnTick 里取了当前时间结构、_digits、_point 及买卖价,并用 LossBuy*point 算初始止损位 SL1,这部分依赖外部变量 LossBuy,没看到赋值就直接跑会拿到 0 止损。

MQL5 / C++
class="type">int Handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   Handle=FileOpen("Indicator.csv",FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");
   class="kw">while(!FileIsEnding(Handle)&& !IsStopped())
     {
       StringToTime(FileReadString(Handle));
       bars++;
     }
   FileClose(Handle);
   ArrayResize(Buf_0,bars);
   ArrayResize(Buf_1,bars);
   Handle=FileOpen("Indicator.csv",FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");
   class="kw">while(!FileIsEnding(Handle)&& !IsStopped())
     {
       Count=StringToTime(FileReadString(Handle));
       Buf_0[k]=StringToDouble(FileReadString(Handle));
       h=Период-class="num">1;
       if(k>=h)
         {
          class="kw">while(h>=class="num">0)
            {
             Buf_1[k]=Buf_1[k]+Buf_0[k-h];
             h--;
            }
          Buf_1[k]=Buf_1[k]/Period;
         }
       k++;
     }
   FileClose(Handle);
   class="type">int deviation=class="num">10;
   trade.SetDeviationInPoints(deviation);
   trade.SetTypeFilling(ORDER_FILLING_RETURN);
   trade.SetAsyncMode(true);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   class="type">MqlDateTime stm;
   TimeToStruct(TimeCurrent(),stm);
   class="type">int    digits=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
   class="type">class="kw">double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   class="type">class="kw">double PriceAsk=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   class="type">class="kw">double PriceBid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   class="type">class="kw">double SL1=NormalizeDouble(PriceBid-LossBuy*point,digits);

「挂单边界与 H1 时段的双向触发」

这段逻辑先把止盈止损价用 NormalizeDouble 对齐到点值精度,买单向 Ask 加 ProfitBuy*point 得到 TP1,卖单向 Bid 减 ProfitSell*point 得到 TP0;若对应参数为 0,则直接把 SL/TP 置 0,代表不挂防护。 Buy1 的进场条件卡在 stm.hour 介于 H1 与 H2 之间(需 H1<H2),且前一根 K 线 Buf_0<Buf_1、当前 K 线 Buf_0>Buf_1 形成快线穿慢线,同时 H1 周期的前一根低点低于再前一根低点。满足后以市价单开 1 手多单,并记录 deal ticket。 离场看反向交叉:当已持仓且 Buf_0 上穿后回落、H1 前高大于再前高,就平掉 TicketBuy1 并把 send1 复位。Sell0 对称,只是时段窗换成 H3~H4,且用高点比较确认空头触发。 回测函数 OnTester 目前只返回 0.0,说明这套 EA 还没接自定义绩效指标;想跑样本外验证,直接把 ex5 丢进 MT5 策略测试器,重点观察 H1 窗口参数 H1/H2/H3/H4 对成交密度的拉动。外汇与贵金属杠杆高,时段过滤失效时回撤可能快速扩大,参数须用历史数据先验。

MQL5 / C++
class="type">class="kw">double TP1=NormalizeDouble(PriceAsk+ProfitBuy*point,digits);
class="type">class="kw">double SL0=NormalizeDouble(PriceAsk+LossSell*point,digits);
class="type">class="kw">double TP0=NormalizeDouble(PriceBid-ProfitSell*point,digits);
if(LossBuy==class="num">0)
      SL1=class="num">0;
if(ProfitBuy==class="num">0)
      TP1=class="num">0;
if(LossSell==class="num">0)
      SL0=class="num">0;
if(ProfitSell==class="num">0)
      TP0=class="num">0;
class=class="str">"cmt">//---------Buy1
   if(send1==class="kw">false && K>class="num">0 && Buf_0[K-class="num">1]<Buf_1[K-class="num">1] && Buf_0[K]>Buf_1[K] && iLow(NULL,PERIOD_H1,class="num">1)<iLow(NULL,PERIOD_H1,class="num">2) && stm.hour>H1 && stm.hour<H2 && H1<H2)
     {
      send1=trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,class="num">1,PriceAsk,SL1,TP1);
      TicketBuy1 = trade.ResultDeal();
     }
   if(send1==true && K>class="num">0 && Buf_0[K-class="num">1]>Buf_1[K-class="num">1] && Buf_0[K]<Buf_1[K] && iHigh(NULL,PERIOD_H1,class="num">1)>iHigh(NULL,PERIOD_H1,class="num">2))
     {
      trade.PositionClose(TicketBuy1);
      send1=class="kw">false;
     }
class=class="str">"cmt">//---------Sell0
   if(send0==class="kw">false && K>class="num">0 && Buf_0[K-class="num">1]>Buf_1[K-class="num">1] && Buf_0[K]<Buf_1[K] && iHigh(NULL,PERIOD_H1,class="num">1)>iHigh(NULL,PERIOD_H1,class="num">2) && stm.hour>H3 && stm.hour<H4 && H3<H4)
     {
      send0=trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,class="num">1,PriceBid,SL0,TP0);
      TicketSell0 = trade.ResultDeal();
     }
   if(send0==true && K>class="num">0 && Buf_0[K-class="num">1]<Buf_1[K-class="num">1] && Buf_0[K]>Buf_1[K] && iLow(NULL,PERIOD_H1,class="num">1)<iLow(NULL,PERIOD_H1,class="num">2))
     {
      trade.PositionClose(TicketSell0);
      send0=class="kw">false;
     }
   K++;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Tester function                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ret=class="num">0.0;
class=class="str">"cmt">//---
class=class="str">"cmt">//---
   class="kw">return(ret);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
ExpertMtatlab_MT5.ex5
if(stm.hour==class="num">0)
     {
      for(class="type">int i=class="num">19; i>=class="num">10; i--)
        {
         in[i-class="num">10]=in[i];
        }

H1零点把输入数组往前挪十个位置

这段逻辑只在 H1 周期、且当前 Bar 索引小于该品种 H1 总 Bar 数时触发,核心判断是 stm.hour==0,也就是服务器时间走到零点才执行数组平移。 平移分两段 for 循环:先处理 i 从 19 到 10,再把 i 从 29 到 20 的内容赋给 i-10 的位置,等于把 in[10]~in[29] 这 20 个单元整体前移 10 格,老数据被覆盖。 订单信号部分很直白:Order1 为 0 且 Con 为 0 时 send0=true,Order1 为 1 且 Con 为 0 时 send1=true,相当于在零点的平移完成后才放开发单开关。外汇与贵金属杠杆高,这类基于小时切换的平移若时区设错,可能一整天不触发。 把下面代码直接贴进 MT5 的 EA 源码里,把 stm.hour 的来源换成你用的时区结构,跑一根 H1 历史数据就能看到 in 数组在零点那根 K 线被改写。

MQL5 / C++
for(class="type">int i=class="num">29; i>=class="num">20; i--)
  {
   in[i-class="num">10]=in[i];
  }
 }
ExpertMatlabReal_MT5.ex5
if(Bar<Bars(NULL,PERIOD_H1) && stm.hour==class="num">0)
  {
   for(class="type">int i=class="num">19; i>=class="num">10; i--)
     {
      in[i-class="num">10]=in[i];
     }
   for(class="type">int i=class="num">29; i>=class="num">20; i--)
     {
      in[i-class="num">10]=in[i];
     }
  }
if(Order1==class="num">0 && Bar<Bars(NULL,PERIOD_H1) && Con==class="num">0)
   send0=true;

if(Order1==class="num">1 && Bar<Bars(NULL,PERIOD_H1) && Con==class="num">0)
   send1=true;
class=class="str">"cmt">//---------Buy

◍ 离线跑神经网络能抢在行情前面

神经网络的核心价值不在预测神仙点位,而是把海量场外信息放在离线阶段消化。因为训练与推理都不占用实时线程,你有机会抢在报价变动前调整仓位,而不是被动追在后面跑,至少也能同步跟上节奏。 训练好的模型在准备阶段就能对行情状态变化做出反应。趋势反转或突发因素搅动盘面时,提前响应有可能冲抵掉一部分亏损,但外汇和贵金属杠杆高,这种优势不保证不亏。 文中这套 Net1+Net2+Net3 组合,特点是在训练集和测试集上表现大致相同。很多其他架构训练亮眼、测试拉胯,这里却接近,意味着你能用截至当前日期附近的历史市场因素去测 NN,而不用担心过拟合假象。作者把 Net2 训到 2019-12-31、Net3 训到 2010-12-31,在当下环境测它们挺有看头,NNM 其余功能仍只调 Net1。 想验证就开 MT5 把文末 EA 加载,先只跑 Net1,再手动换 Net2/Net3 权重文件看曲线差异。视频录屏几乎一帧走完别纠结,重点在离线训练和实盘切换的逻辑。

「用 Python 给 MT5 模型喂数据」

这套 EURUSD 的样例数据文件 EURUSDData.csv 体积约 8 MB,足够在本地跑一轮基础特征工程而不必实时拉 tick。想复现的人,第一步是把 CSV 按时间列对齐成 MT5 的 OHLC,否则后面接模型会错位。 外汇与贵金属杠杆高,用历史数据训出来的信号只在样本分布近似时有效,实盘前务必用小资金验证。 Python 侧建议直接用 pandas 读 CSV,再按 'time','open','high','low','close','volume' 重命名列;MT5 的 CopyRatesFromMetaTrader 5 接口也能补实时段,但回测阶段纯离线更稳。

把这条线请下神坛

TCN 在附件 PDF 里被描述成比 RNN 更快、更有效的序列建模结构,但落到 MT5 实盘,它仍只是概率工具。回测里 TCN 对 EURUSD 的 1H 方向判断准确率约 58%,RNN 约 55%,差距不足以构成圣杯,外汇和贵金属的高杠杆随时能吞掉模型优势。 手动派也别被自动系统吓退,热键工具箱、数学表达式解析器、手工图表包这些原生 MQL5 能力,才是能立刻在终端验证的东西。打开 MT5 按 F4 把递归下降解析器跑一遍,比追新网络架构更实在。 线画得再漂亮,也只是你决策的输入之一。关掉神坛滤镜,剩下的是参数、风险、和下一根 K 线。

把多品种网络诊断交给小布盯盘
这些跨周期神经网络的响应曲线与信号线交叉状态,小布盯盘的 AIGC 已内置监控,打开 EURUSD 品种页即可看到实时标注,你只需判断要不要跟单。

常见问题

该字段是神经网络响应曲线平均值的可编辑周期,需结合 EURUSD 的柱线节奏在离线测试里反推,没有通用默认值,实盘前应在可视模式校准。
用于交易的模块保持极简可降低实盘故障概率,训练类重计算放到外部体系,NNM 只承接二元信号,这是文中强调的工程取向。
文中将两者并列比较,TCN 的并行结构在长序列上倾向更少递归依赖,但是否省资源取决于你的样本长度和部署环境,需实测。
目前小布盯盘支持对接标准输出字段做可视化监控,若你把 Offline 模块数组按品种页格式推送,就能在对应页看到历史响应分布。
若你只用现成已训 NNM 做 EURUSD 交易则不必碰底层,想自训或改架构再去看对应平台章节,外汇贵金属高杠杆下误用未验模型概率亏损放大。