神经网络在交易中的实际应用。 是时候进行实践了·综合运用
(3/3)· EURUSD 上跑通 Online 模块后,怎样把训练、测试、优化甩在系统外只留交易
◍ 从 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 止损。
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 对成交密度的拉动。外汇与贵金属杠杆高,时段过滤失效时回撤可能快速扩大,参数须用历史数据先验。
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 线被改写。
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 线。