神经网络在交易中的实际应用 (第二部分). 计算机视觉·进阶篇
🧠

神经网络在交易中的实际应用 (第二部分). 计算机视觉·进阶篇

(2/3)· 当十几个指标堆出上百条输入,CNN 把图表当图像吃进去也许更轻

实战向进阶 第 2/3 篇
把几十个指标数值硬塞进全连接网络,输入维度爆炸还难收敛。把图表截成图、交给卷积层提取局部形态,训练负担可能直线下来。这路子不神秘,关键在图像集怎么切、标签怎么打。

◍ 用截图与文件搬运记录高低点突破

这段逻辑在每小时收盘后做一件事:把前一小时创下的新低或新高截进 PNG,并用计数器命名归档。外汇与贵金属波动连续,H1 周期下每天约 24 张图,回看一周就可能累积 120+ 个文件,磁盘占用虽小但命名混乱会拖慢后续分析。 核心动作是先判断 iLow(NULL,PERIOD_H1,i+1) 是否小于 DibMin,若成立则刷新 DibMin、countS 自增并调用 ChartScreenShot 存为 Sell+序号.png;新高分支对称处理 Buy 系列。两个分支互斥执行,意味着一小时若同时吞掉前低和前高,会出两张图。 文件重排那段最容易被忽略:当 day 变更时,代码用 FileCopy 把上一日最后的 Sell/Buy 图互换序号再 FileDelete 旧文件,等于把「最后一张」顺延成「下一日第 0 张」的基底。直接在 MT5 脚本里跑这段,把 WIDTH 设成 500 看终端 Data 文件夹,能确认命名是否如预期滚动。 别把截图当信号源。它只是把价格行为学里的「时段极值突破」可视化留痕,贵金属跳空时 i+1 的 High/Low 可能跨缺口,截图内容与实际成交价偏差偏大,需人工复核。

MQL5 / C++
FileCopy("Sell" + (class="type">class="kw">string)countS + ".png", class="num">0, "Buy" + (class="type">class="kw">string)(countB+class="num">1) + ".png", FILE_REWRITE);
FileDelete("Sell" + (class="type">class="kw">string)countS + ".png", class="num">0);
FileCopy("Buy" + (class="type">class="kw">string)countB + ".png", class="num">0, "Sell" + (class="type">class="kw">string)(countS+class="num">1) + ".png", FILE_REWRITE);
FileDelete("Buy" + (class="type">class="kw">string)countB + ".png", class="num">0);
countB ++;
countS ++;
 }
 day = stm.day;
 if(stm.hour == class="num">0)
  {
  DibMin = iOpen(NULL, PERIOD_H1, i);
  DibMax = iOpen(NULL, PERIOD_H1, i);
  }
 if(iLow(NULL, PERIOD_H1, i+class="num">1) < DibMin)
  {
  DibMin = iLow(NULL, PERIOD_H1, i+class="num">1);
  countS ++;
  ChartScreenShot(class="num">0, "Sell" + (class="type">class="kw">string)countS + ".png", (class="type">int)WIDTH, (class="type">int)WIDTH, ALIGN_LEFT);
  }
 if(iHigh(NULL, PERIOD_H1, i+class="num">1) > DibMax)
  {
  DibMax = iHigh(NULL, PERIOD_H1, i+class="num">1);
  countB ++;
  ChartScreenShot(class="num">0, "Buy" +(class="type">class="kw">string)countB + ".png", (class="type">int)WIDTH, (class="type">int)WIDTH, ALIGN_LEFT);
  }
 Sleep(class="num">20);
 }
   }
  else
   class="kw">break;
  }
 }

搭好GPU环境再喂图给卷积网络

想在 MT5 之外做价格图像的卷积训练,Anaconda + TensorFlow/Keras 是条省心路。若手里有 NVIDIA 显卡,建个独立 conda 环境装 tensorflow-gpu 与 keras-gpu,训练速度会明显不同:同一网络跑 40 个 epoch,纯 CPU 约 13 小时,GPU 上约 1.5 小时,单 epoch 从 20 分钟压到 1–2 分钟。代价是显存卡死网络规模——想堆层就得加显存或砍图像尺寸。 环境命令按顺序走:pip install tensorflow、pip install keras,再 conda create -n PythonGPU python=3.6 tensorflow-gpu 和 conda install -c anaconda keras-gpu,最后补 jupyter、pandas、pillow。CPU 用户同样要装后两个库,只是不用 gpu 后缀。 网络结构本身不复杂:输入 449x449 三通道彩图,首层 96 个 3x3 卷积核接 relu,2x2 最大池化降维;之后 32、16 核各再来一遍卷积+激活+池化。展平后接 64 神经元全连接、Dropout(0.5) 防过拟合,输出层 1 个 sigmoid 神经元做二分类。若把横盘也单分一类,输出改 3 神经元即可。 编译用 binary_crossentropy 配 sgd 优化器,准确率作指标;像素除以 255 归一,用 Keras 生成器从 train/val 目录读图,class_mode='binary' 且 shuffle=False。下面这段是可直接丢进 Train.ipynb 的骨架。 别把显存当无限资源 GPU 提速的前提是网络张量能塞进显存。449x449 彩图加 96 卷积核已经吃紧,真要加层先算显存账单,不然只能退回 CPU 慢熬。外汇与贵金属行情高波动,图像标签错分会带来实盘误判风险,训练集质量比网络深度更关键。

MQL5 / C++
from tensorflow.python.keras.preprocessing.image class="kw">import ImageDataGenerator
from tensorflow.python.keras.models class="kw">import Sequential
from tensorflow.python.keras.layers class="kw">import Conv2D, MaxPooling2D
from tensorflow.python.keras.layers class="kw">import Activation, Dropout, Flatten, Dense
from tensorflow.keras.callbacks class="kw">import ModelCheckpoint
from tensorflow.keras.models class="kw">import load_model
# Directory with data for training
train_dir = &class="macro">#x27;train&class="macro">#x27;
# Directory with data for validation
val_dir = &class="macro">#x27;val&class="macro">#x27;
# Image dimensions
img_width, img_height = class="num">449, class="num">449
# Image-based tensor dimension for class="kw">input to the neural network
# backend Tensorflow, channels_last
input_shape = (img_width, img_height, class="num">3)
# Number of epochs
epochs = class="num">20
# Mini-sample size
batch_size = class="num">7
# Number of images for training
nb_train_samples = class="num">4289
# Number of images for validation
nb_validation_samples = class="num">1836
# Number of images for testing
class="macro">#nb_test_samples = class="num">3736
model = Sequential()
model.add(Conv2D(class="num">96, (class="num">3, class="num">3), input_shape=input_shape))
model.add(Activation(&class="macro">#x27;relu&class="macro">#x27;))
model.add(MaxPooling2D(pool_size=(class="num">2, class="num">2)))
model.add(Conv2D(class="num">32, (class="num">3, class="num">3)))
model.add(Activation(&class="macro">#x27;relu&class="macro">#x27;))
model.add(MaxPooling2D(pool_size=(class="num">2, class="num">2)))
model.add(Conv2D(class="num">16, (class="num">3, class="num">3)))
model.add(Activation(&class="macro">#x27;relu&class="macro">#x27;))
model.add(MaxPooling2D(pool_size=(class="num">2, class="num">2)))
model.add(Flatten())
model.add(Dense(class="num">64))
model.add(Activation(&class="macro">#x27;relu&class="macro">#x27;))
model.add(Dropout(class="num">0.5))
model.add(Dense(class="num">1))
model.add(Activation(&class="macro">#x27;sigmoid&class="macro">#x27;))
model.compile(loss=&class="macro">#x27;binary_crossentropy&class="macro">#x27;,
              optimizer=&class="macro">#x27;sgd&class="macro">#x27;,
              metrics=[&class="macro">#x27;accuracy&class="macro">#x27;])
datagen = ImageDataGenerator(rescale=class="num">1. / class="num">255)

「按验证集挑第11个epoch的权重」

用 ModelCheckpoint 在每个 epoch 结束后落盘一份 hdf5 权重,训练完 CNN 文件夹下会多出 20 个网络文件,后面靠验证指标反选而不是看训练集甜区。 代码里 callbacks 只挂了保存钩子,fit 时 train_generator 和 val_generator 都设了 shuffle=False,保证样本顺序固定,才能把第 N 个 epoch 的权重和验证曲线对上号。

MQL5 / C++
callbacks = [ModelCheckpoint(&class="macro">#x27;cnn_Open{epoch:1d}.hdf5&class="macro">#x27;)]
model.fit(
    train_generator,
    steps_per_epoch=nb_train_samples class=class="str">"cmt">// batch_size,
    epochs=epochs,
    validation_data=val_generator,
    validation_steps=nb_validation_samples class=class="str">"cmt">// batch_size,
    callbacks=callbacks)
逐行拆解:第1行定义回调,每轮存成 cnn_Open0.hdf5、cnn_Open1.hdf5…;fit 第2行喂训练生成器,第3行用样本总数整除 batch 算步数,第4行跑满 epochs(此处为20),第5行挂验证集,第6行验证步数同理整除,第7行把回调注入训练循环。 训练曲线看,第18个 epoch 训练错误率约30%、正确率85%,但丢到验证集错误反升、正确率掉下来;第11个 epoch 的 val_loss=0.6607、val_accuracy=0.6129,是验证集上最稳的点。经验阈值:验证误差不超35–40%、精度不低于55–60% 就能直接用,不必再调参。 外汇与贵金属属高风险品种,即便用这版权重搭系统,也只在概率上倾向改善交易质量,实盘前务必用 MT5 历史数据重跑验证。

MQL5 / C++
callbacks = [ModelCheckpoint(&class="macro">#x27;cnn_Open{epoch:1d}.hdf5&class="macro">#x27;)]
model.fit(
    train_generator,
    steps_per_epoch=nb_train_samples class=class="str">"cmt">// batch_size,
    epochs=epochs,
    validation_data=val_generator,
    validation_steps=nb_validation_samples class=class="str">"cmt">// batch_size,
    callbacks=callbacks)

◍ 把CNN响应灌进MT5看交叉信号

CNN在CPU环境下跑完Test.jpynb后,会在CNN目录生成Indicator.csv,里面是12558张测试图对应的网络输出值。把这个文件搬到C:\Users\...\AppData\Roaming\MetaQuotes\Terminal\Common\Files,EURUSD H1图表才能用NWI指标读到神经网络的反应。 实操上用主指示线(红)和简单平均线(DodgerBlue)的交叉来解释网络响应最直观。配合TestCNN专家顾问,可双向优化信号线周期、时间与止损,先在垂直红线前那段训练网络、再对红线后做测试,优化器挑出的高优先级结果往往只是随机正例,不能当必然方向。 下面这段Python负责加载第11 epoch的cnn_Open11.hdf5,用ImageDataGenerator把图归一化到1/255,再flow_from_directory读盘、model.predict吐出indicator并写回csv;MT5侧的NWI.mq5则用FileOpen共享读同一个csv,绑两个DRAW_LINE缓冲。外汇与贵金属杠杆高,这类信号失效概率不低,上实盘前先在策略测试器跑样本外。

MQL5 / C++
from tensorflow.python.keras.preprocessing.image class="kw">import ImageDataGenerator
from tensorflow.python.keras.models class="kw">import Sequential
from tensorflow.python.keras.layers class="kw">import Conv2D, MaxPooling2D
from tensorflow.python.keras.layers class="kw">import Activation, Dropout, Flatten, Dense
from tensorflow.keras.callbacks class="kw">import ModelCheckpoint
from tensorflow.keras.models class="kw">import load_model
class="kw">import pandas as pd
predict_dir = &class="macro">#x27;Test&class="macro">#x27;
img_width, img_height = class="num">449, class="num">449
nb_predict_samples = class="num">12558
Date=pd.read_csv(&class="macro">#x27;EURUSDDate.csv&class="macro">#x27;, delimiter=&class="macro">#x27;;&class="macro">#x27;,header=None)
model=load_model(&class="macro">#x27;cnn_Open11.hdf5&class="macro">#x27;)
datagen = ImageDataGenerator(rescale=class="num">1. / class="num">255)
predict_generator = datagen.flow_from_directory(
    predict_dir,
    target_size=(img_width, img_height),
    shuffle=False)
indicator=model.predict(predict_generator, nb_predict_samples )
print(indicator)
Date=pd.DataFrame(Date)
Date[&class="macro">#x27;class="num">0&class="macro">#x27;] =indicator
Date.to_csv(&class="macro">#x27;Indicator.csv&class="macro">#x27;,index=False, header=False,sep=&class="macro">#x27;;&class="macro">#x27;)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 NWI.mq5 |
class=class="str">"cmt">//|                         Copyright © class="num">2019, Andrey Dibrov. |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright © class="num">2019, Andrey Dibrov."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots   class="num">2
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_type2   DRAW_LINE
class="macro">#class="kw">property indicator_color1  Red
class="macro">#class="kw">property indicator_color2  DodgerBlue
class="type">int Handle;
class="type">int i;
class="type">int h;
class="kw">input class="type">int Period=class="num">5;
class="type">class="kw">double    ExtBuffer[];
class="type">class="kw">double    SignBuffer[];
class="type">class="kw">datetime Date1;
class="type">class="kw">datetime Date0;
class="type">class="kw">string File_Name="Indicator.csv";
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   SetIndexBuffer(class="num">0,ExtBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,SignBuffer,INDICATOR_DATA);
   IndicatorSetInteger(INDICATOR_DIGITS,class="num">5);
   Handle=FileOpen(File_Name,FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");
   class=class="str">"cmt">//FileClose(Handle);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

把外部信号灌进指标的回灌写法

这段逻辑干的事很直接:从公共目录的 CSV 里读时间点和数值,再把它们按 H1 周期对齐到指标缓冲区。外汇与贵金属市场高杠杆、滑点随机,这种回灌只用于离线验证,实盘信号延迟可能让结论偏移。 OnCalculate 里先用 FileReadString 拿到首行时间,iBarShift 在 NULL 品种 PERIOD_H1 上反查出柱索引 i,随后重新以 FILE_COMMON 共享方式打开同一文件,保证多程序读同一份信号不互斥。 循环体每次读一行时间 Date1 和一行数值写进 ExtBuffer[i],再用内层 while 把前 Period-1 根(默认 Period=5,即前 4 根)的 ExtBuffer 累加进 SignBuffer[i],除以 Period 得到平滑值。注意 i 从文件首行对应的柱向下递减,因此越晚的信号贴在越近的柱上。 TestCNN.mq5 那一段暴露了下游用法:它用 CTrade 下单,输入参数含 H1~H4 四组阈值与 LossBuy/ProfitBuy/LossSell/ProfitSell 四组止盈止损点数。也就是说,SignBuffer 算完之后,若其值穿越某 Hn 边界,就可能触发对应方向市价单,盈亏空间完全由这几个 input 点数框定。 在 MT5 里把 Period 从 5 改成 20 重跑,SignBuffer 的斜率会明显变钝,H1~H4 的触发频率可能下降 30%~50%,具体看品种波动。调参前先确认 CSV 时间精度是秒级还是分根级,否则 iBarShift 会偏柱。

MQL5 / C++
class=class="str">"cmt">//| Relative Strength Index                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">int begin,
                class="kw">const class="type">class="kw">double &price[])
  {
   class="type">MqlDateTime stm;
   Date0=StringToTime(FileReadString(Handle));
   i=iBarShift(NULL,PERIOD_H1,Date0,class="kw">false);
   Handle=FileOpen(File_Name,FILE_CSV|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";");
   ArraySetAsSeries(ExtBuffer,true);
   ArraySetAsSeries(SignBuffer,true);
   class="kw">while(!FileIsEnding(Handle) && !IsStopped())
     {
      Date1=StringToTime(FileReadString(Handle));
      ExtBuffer[i]=StringToDouble(FileReadString(Handle));
      h=Period-class="num">1;
      if(i>=class="num">0)
        {
         class="kw">while(h>=class="num">0)
           {
            SignBuffer[i]=SignBuffer[i]+ExtBuffer[i+h];
            h--;
           }
        }
      SignBuffer[i]=SignBuffer[i]/Period;
      TimeToStruct(Date1,stm);
      i--;
     }
   FileClose(Handle);
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 TestCNN.mq5 |
class=class="str">"cmt">//|                                               Copyright © class="num">2019, Andrey Dibrov. |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright " Copyright © class="num">2019, 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">#include<Trade\Trade.mqh>
CTrade  trade;
class="kw">input class="type">int Period=class="num">5;
class="kw">input class="type">int H1;
class="kw">input class="type">int H2;
class="kw">input class="type">int H3;
class="kw">input class="type">int H4;
class="kw">input class="type">int LossBuy;
class="kw">input class="type">int ProfitBuy;
class="kw">input class="type">int LossSell;
class="kw">input class="type">int ProfitSell;
class="type">class="kw">ulong TicketBuy1;
class="type">class="kw">ulong TicketSell0;
class="type">class="kw">datetime Count;
class="type">class="kw">double Per;
class="type">class="kw">double Buf_0[];
class="type">class="kw">double Buf_1[];
把截图标注交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到;把重复劳动交给小布,你专注决策——比如按日高日低自动截图表、贴买卖标签这套脏活。

常见问题

尺寸是作者实验设定,并非铁律。缩小到 224 左右通常仍能保留 K 线轮廓,但极值柱细节可能变糊,分类边界会偏软,建议先做对照集测准确率。
反转截图归入对应方向的新极值标签里,本质是让网络学到拐点附近的图样;连续序列上的响应仍需后处理优化,不能直接当信号。
小布盯盘内置的 AIGC 看盘口可辅助做品种页截图与日高日低标记,训练部分仍需你在本地用 Python 侧跑,它不替你训模型。
验证集关 shuffle 是为了对齐样本顺序方便排查;训练集一般要开 shuffle,原文片段仅展示数据流向,实际训练请分开设置。
高杠杆下价格行为受新闻扰动大,图分类仅给概率倾向,单模型直接跟单风险极高,务必过样本外与多周期验证。