神经网络在交易中的实际应用 Python (第一部分)(基础篇)
📘

神经网络在交易中的实际应用 Python (第一部分)(基础篇)

第 1/3 篇

在 MT5 里跑通 Python 神经网络的起点

很多交易者以为神经网络必须脱离交易平台单独训,其实从 MetaTrader 5 build 2085 起,终端原生支持 Python 3.8+ 通过 MetaTrader5 包直连,省掉了中间文件转储的麻烦。 一个最基础的验证动作:在 MT5 终端的「工具—选项—Python」里勾选「允许 Python 脚本导入」,再在系统装好对应版本的 MetaTrader5 库,就能用几行代码把 EURUSD 的收盘价拉进 NumPy 数组。 外汇与贵金属带杠杆,神经网络给出的信号只代表历史样本里的统计倾向,实盘前务必用策略测试器跑至少 3 个月 tick 数据复核,爆仓风险始终存在。

◍ 为什么换 Python 和 TensorFlow 重做一遍

上篇用 Matlab 神经网络模块跑了实盘可用的推导,但刻意略过了输入数据准备与网络训练的细节,那部分才是复现的门槛。本文把镜头转回数据侧:用 Python 生态重写,借 Google 的 TensorFlow 做底层计算,再叠 Keras 描述网络结构,思路与上篇 Matlab 版并不一致。 基础篇第 5 段曾简短提过一个变体实现,本系列就接着那个方向落地——交易系统的构建原则变了,不再依赖现成神经网络工具箱,而是从张量流图搭起。外汇与贵金属品种波动受宏观与流动性扰动大,用神经网络拟合属高风险尝试,回测亮眼不等于实盘稳健。 打开 MT5 没用,这套代码跑在 Python 端;你能立刻做的一件事是装好 TensorFlow+Keras,对照基础篇第 5 段把网络骨架先描出来,再回来谈 MT5 信号接入。

「把 EURUSD H1 切成四套神经网络的喂料」

做方向决策时,用两个神经网络分别管一个开仓方向,所以训练数据要按多空拆成两组。第一套网络学出自写指标的值,避免在 EA 里重复计算拖慢终端;第二套网络吃这些指标输出,再生成交易信号。EURUSD H1 上跑这套逻辑,最终系统是四个网络同时跑:买方向 Net1+Net2、卖方向 Net1+Net2。 数据准备靠两个脚本:PythonPrices.mq5 与 PythonIndicators.mq5。样本切法很硬——从工作日开盘到当天第一次触底算一段,到第一次触顶算另一段,依赖 DibMin1-1 和 DibMax1-1 两个指标把极值点标成 -1。在 EURUSD H1 跑完脚本,\Common\files 里会落六个 CSV:价格类两个、指标类两个、Net2 输出类两个。 Net2 的训练目标不是价格本身,而是小时开盘与日开盘(或日收盘与小时开盘)的价差。用「已实现价差」而非「应发生事件」去训网络,逻辑上更稳,因为评估过去比预测未来容易,也平滑了模块误判概率。外汇与贵金属杠杆高,这种数据切分只是概率倾向,实盘前务必在 MT5 用历史数据复跑验证。 下面这段是 PythonPrices.mq5 的头部与启动片段,注意 input 里历史起止写死为 2004.07.01 至 2010.12.31,数组 inB[22] 预留 22 维输入,OnStart 里先抓 H1 总柱数 k,再挂两个 iCustom 句柄把极值指标拷进序列数组,最后开 InputNet1Min.csv 准备写盘。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                    PythonPrices.mq5 |
class=class="str">"cmt">//|                         Copyright class="num">2020, Andrey Dibrov. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2020, Andrey Dibrov."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#class="kw">property script_show_inputs
class="kw">input class="type">class="kw">string Date="class="num">2004.07.class="num">01 class="num">00:class="num">00";
class="kw">input class="type">class="kw">string DateOut="class="num">2010.12.class="num">31 class="num">23:class="num">00";
class="kw">input class="type">int History=class="num">0;
class="type">class="kw">double inB[class="num">22];
class="type">class="kw">string Date1;
class="type">int HandleInpuNet1Min;
class="type">int HandleInpuNet1Max;
class="type">class="kw">double DibMin1_1[];
class="type">class="kw">double DibMax1_1 [];
class="type">int DibMin1_1Handle;
class="type">int DibMax1_1Handle;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   class="type">int k=iBars(NULL,PERIOD_H1)-class="num">1;
   DibMin1_1Handle=iCustom(NULL,PERIOD_H1,"DibMin1-class="num">1",History);
   CopyBuffer(DibMin1_1Handle,class="num">0,class="num">0,k,DibMin1_1);
   ArraySetAsSeries(DibMin1_1,true);
   DibMax1_1Handle=iCustom(NULL,PERIOD_H1,"DibMax1-class="num">1",History);
   CopyBuffer(DibMax1_1Handle,class="num">0,class="num">0,k,DibMax1_1);
   ArraySetAsSeries(DibMax1_1,true);
   HandleInpuNet1Min=FileOpen(Symbol()+"InputNet1Min.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");

把日线波动塞进小时线训练集

做外汇或贵金属的 EA 训练数据,常遇到一个尴尬:神经网络输入维度要固定,但跨周期特征得自己拼。下面这段逻辑就是把 D1 的实体、影线信息折算后,写进以当前品种命名的 CSV,供后续 Net1 训练用。 代码先以 FILE_COMMON 共享模式打开两个文件句柄(Min/Max 各一),并把写指针 Seek 到末尾,避免覆盖历史样本。注意 Symbol() 直接拼文件名,多品种跑的时候文件会自然隔离。 核心循环遍历 H1 的全部 K 线(iBars(NULL,PERIOD_H1)-1 到 0)。当 DibMin1_1 与 DibMax1_1 满足特定转向组合(例如前一根为 1 后一根为 -1 且最大值标记成立),才提取特征:把 inB[5..19] 整体前移 5 位,再把当日 Open 与 Low 差乘 100000 填入 inB[15],High-Open 乘 100000 填 inB[16],其余两个日线幅度分别乘 10000 填 inB[17]、inB[18]。 这种乘 10 万/1 万的做法,本质是把价格差归一化到整数域,减少浮点传递误差。外汇和贵金属杠杆高、跳空频繁,直接拿原始价差进网络可能让梯度发散,手动缩放是务实手段。开 MT5 把这段塞进 OnStart,跑完用 Excel 打开 InputNet1Min.csv,看 inB[15] 列是否集中在几万到十几万区间,就能验证缩放有没有写错。

MQL5 / C++
  HandleInpuNet1Max=FileOpen(Symbol()+"InputNet1Max.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");
  FileSeek(HandleInpuNet1Min,class="num">0,SEEK_END);
  FileSeek(HandleInpuNet1Max,class="num">0,SEEK_END);
  if(HandleInpuNet1Min>class="num">0)
    {
      Alert("Writing to the file InputNet1Min");
      for(class="type">int i=iBars(NULL,PERIOD_H1)-class="num">1; i>=class="num">0; i--)
        {
         Date1=TimeToString(iTime(NULL,PERIOD_H1,i));
         if(DateOut>=Date1 && Date<=Date1)
           {
            if((DibMin1_1[i]==-class="num">1 && DibMin1_1[i+class="num">1]==class="num">1 && DibMax1_1[i]==class="num">1) || (DibMin1_1[i]==class="num">1 && DibMax1_1[i]==class="num">1))
              {
               for(class="type">int m=class="num">0; m<=class="num">14; m++)
                 {
                  inB[m]=inB[m+class="num">5];
                 }
               inB[class="num">15]=(iOpen(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">100000;
               inB[class="num">16]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">100000;
               inB[class="num">17]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
               inB[class="num">18]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_H1,i+class="num">1))*class="num">10000;

◍ 把日线边界折算进小时特征向量

这段逻辑在给训练样本补最后几个输入维度:用 H1 开盘价相对 D1 最低价、D1 最高价的差值乘 10000,把跨周期空间压缩成整数点差特征。inB[19] 到 inB[21] 只算三个值,但决定了样本里「日内相对日波幅的位置」是否被神经网络看到。 代码里 iBarShift 配合 iTime 把 H1 柱对齐到所属 D1 柱,避免直接用 D1 序列下标导致的错位。乘 10000 是多数外汇品种把小数价转点的习惯做法,XAUUSD 这类三位数报价若直接复用,数值会偏大一个量级,需要单独处理。 FileWrite 一次性吐出 inB[0]~inB[21] 共 22 列,后面 InputNet1Max 分支用 DibMax1_1 / DibMin1_1 的拐点组合(-1 翻 1 且 Min 为 1,或双 1)来筛「极值附近」样本。开 MT5 把这段贴进 EA,改 DateOut/Date 区间跑一遍,能直接看 csv 里第 20~22 列是否按日线边界跳动。

MQL5 / C++
inB[class="num">19]=(iOpen(NULL,PERIOD_H1,i+class="num">1)-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
inB[class="num">20]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_H1,i))*class="num">10000;
inB[class="num">21]=(iOpen(NULL,PERIOD_H1,i)-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
FileWrite(HandleInpuNet1Min,
 inB[class="num">0],inB[class="num">1],inB[class="num">2],inB[class="num">3],inB[class="num">4],inB[class="num">5],inB[class="num">6],inB[class="num">7],inB[class="num">8],inB[class="num">9],inB[class="num">10],inB[class="num">11],inB[class="num">12],inB[class="num">13],inB[class="num">14],inB[class="num">15],
 inB[class="num">16],inB[class="num">17],inB[class="num">18],inB[class="num">19],inB[class="num">20],inB[class="num">21]);
 }
 }
 }
FileClose(HandleInpuNet1Min);
 }
class=class="str">"cmt">//------------------------------------------------------------------------------------------------------------------------------------------------
 if(HandleInpuNet1Max>class="num">0)
 {
 Alert("Writing the file InputNet1Max");
 for(class="type">int i=iBars(NULL,PERIOD_H1)-class="num">1; i>=class="num">0; i--)
 {
 Date1=TimeToString(iTime(NULL,PERIOD_H1,i));
 if(DateOut>=Date1 && Date<=Date1)
 {
 if((DibMax1_1[i]==-class="num">1 && DibMax1_1[i+class="num">1]==class="num">1 && DibMin1_1[i]==class="num">1)|| (DibMin1_1[i]==class="num">1 && DibMax1_1[i]==class="num">1))
 {
 for(class="type">int m=class="num">0; m<=class="num">14; m++)
 {
 inB[m]=inB[m+class="num">5];
 }

「把日线结构压进小时线特征向量」

上面几行在做一件事:把当前 H1 棒对应的 D1 开盘、最高、最低价,按相对位置换算成整数特征,塞进 inB[15]~inB[21] 这段数组。乘 100000 或 10000 是为了把小数价差拉成整数,方便后面写文件喂给模型,EURUSD 这种五位数报价对直接乘 100000 就得到点值整数。 注意 inB[18] 和 inB[20] 用了 iOpen(NULL,PERIOD_H1,i+1)、i+1 是下一根 H1 开盘,而 inB[15] 用的是 D1 开盘减 D1 最低,两者量纲一致但参照系不同,混用时得清楚自己是在比跨周期还是跨棒。 最后 FileWrite 把 inB[0] 到 inB[15] 先落盘,特征拼接顺序错了模型输入维度就会偏移,建议在 MT5 里先 Print 前 22 个值对照日线窗口手算一遍。外汇与贵金属杠杆高,这类特征仅用于辅助概率判断,不构成方向保证。

MQL5 / C++
inB[class="num">15]=(iOpen(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">100000;
inB[class="num">16]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">100000;
inB[class="num">17]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
inB[class="num">18]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_H1,i+class="num">1))*class="num">10000;
inB[class="num">19]=(iOpen(NULL,PERIOD_H1,i+class="num">1)-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
inB[class="num">20]=(iHigh(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_H1,i))*class="num">10000;
inB[class="num">21]=(iOpen(NULL,PERIOD_H1,i)-iLow(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i))))*class="num">10000;
FileWrite(HandleInpuNet1Max,
  inB[class="num">0],inB[class="num">1],inB[class="num">2],inB[class="num">3],inB[class="num">4],inB[class="num">5],inB[class="num">6],inB[class="num">7],inB[class="num">8],inB[class="num">9],inB[class="num">10],inB[class="num">11],inB[class="num">12],inB[class="num">13],inB[class="num">14],inB[class="num">15],

常见问题

先把EURUSD的H1 K线导出,按时间切成训练、验证、测试几份喂料,避免用未来数据泄漏到训练集。
Python生态的TensorFlow能搭更灵活的网络结构,方便把日线波动等跨周期特征塞进小时线训练集做联合训练。
小布可自动拉取对应品种多周期数据,帮你把日线边界折算进小时特征向量并跑初步回测,省去手动拼数据的麻烦。
用当日日线高低点减去小时收盘价做归一化边界差,再作为额外输入维度拼到原有H1特征后,训练时锁随机种子保稳定。
分四套容易在小样本上过拟合,建议每套加 dropout 并拿独立测试集看概率表现,别直接当方向信号用。