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

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

第 1/3 篇

◍ 用 CNN 读价格图:MT5 里的计算机视觉入口

MQL5 官方在 2021-03-16 发布过一篇关于神经网络实盘应用的系列文,其中第二部分专门讲计算机视觉(CV)在 MetaTrader 5 中的落地。原文发布后阅读量约 3349、收藏 26,说明这类把截图当特征喂给模型的做法,在零售交易者里已经有真实需求。 传统指标依赖数值序列,而 CV 思路直接把 K 线图当图像处理:卷积层提取局部形态,池化层压缩空间维度,全连接层输出多分类概率。外汇与贵金属杠杆高、滑点随机,任何信号都只是概率倾向,实盘前必须用历史 Tick 回测验证泛化能力。 在 MT5 里跑这套,核心是先通过 ChartScreenShot 把图表存成位图,再用 ONNX 模型推理。下面这段是官方示例里截取的特征预处理骨架,注意输入尺寸必须和训练时严格一致,否则输出会漂移。

「把十几个指标压成图再喂给CNN」

给交易用的神经网络做训练数据,第一道坎就是输入怎么组织。假设你同时挂了十几个指标,每个指标再往历史里回算一定深度,平铺开来很容易突破100个特征值,极端情况下还会更多。 这种高维平铺对全连接网络很不友好,不仅参数量爆炸,指标间的局部空间关系也丢了。 一个能直接在MT5里验证的思路是:把指标阵列当成多通道图像,交给卷积神经网络(CNN)去做分类或识别。CNN本就是为图像局部特征提取设计的,用它替我们压缩和抽取信息,比手工降维更省事。

CNN 逐层尺寸怎么塌下来的

这套用于图像输入的卷积网络,输入固定为 449×449 像素单图。第一卷积层用 3×3 核扫出 96 张 447×447 特征图,紧接着 2×2 子采样把空间压到 223×223,特征图数不变。 第二卷积层降到 32 张 221×221 特征图,再经 2×2 子采样成 110×110;第三卷积层继续收至 16 张 108×108,子采样后剩 54×54(原文图里没画这一级)。 末端接 64 个神经元的全连接层,最后 1 个神经元做二分类输出。整套结构不需要手搭,只要把输入尺寸、各层特征图数和核大小这几个主参数设对,网络就会自动按这个尺寸链生成。 验证时可以拿 449 平方输入跑一遍前向,看每一级输出维度是否严格落到 447→223→221→110→108→54,差一个像素就说明卷积或池化参数配错了。

◍ 用MT5脚本切出CNN训练图集

做图像分类之前先定用途:本实验不碰轴枢点截图那种没实战价值的样本,只聚焦两类——价格上涨或触日低判为买入类,下跌或触日高判为卖出类。趋势方向上出新极值、以及日高日低反转瞬间,才是截图触发点。 图表先套 CNN.tpl 模板,丢进 Terminal 目录下 MQL5\Profiles\Templates,图表属性里把文字设成 White,再按自己显卡能力定最佳像素。作者实机用的是 449x449。 脚本分两种模式:Train 造训练集,Test 造连续每小时截图供网络出响应。以 Train 跑完,Files 目录落了 6125 张图,其中 Buy 类 3139、Sell 类 2986。桌面建 CNN\Train、Val、Test,Train/Buy 与 Train/Sell 各塞满,再从尾部剔 30% 进 Val——最终 Train\Buy 2198、Val\Buy 941、Train\Sell 2091、Val\Sell 895。 切到 Test 模式再跑,Files 里生成 12558 张连续每小时图,不分组直接整包挪到 CNN\Test\Resp;网络自己会给每张图打「贴合买入或卖出条件」的概率。EURUSDDate 文件留在原目录,另行移进 CNN 根目录。 提一句 MT5 的坑:用策略测试器里的 EA 去备图更稳,但 MT5 不支持在测试器内调 ChartScreenshot,所以只能脚本现截。外汇与贵金属自带高杠杆风险,图集只是分类输入,不预示任何方向盈亏。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 CNNet.mq5 |
class=class="str">"cmt">//|                                                    Copyright class="num">2021, Andrey Dibrov. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2021, 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">2017.01.class="num">02 class="num">00:class="num">00";
class="kw">input class="type">class="kw">string DateOut="class="num">2018.12.class="num">13 class="num">23:class="num">00";
class="kw">input class="type">class="kw">string DateTest="class="num">2019.01.class="num">02 class="num">00:class="num">00";
class="kw">input class="type">class="kw">string Dataset="Train";
class="type">class="kw">string Date1;
class="type">int count,countB,countS;
class="type">int day;
class="type">class="kw">double DibMin;
class="type">class="kw">double DibMax;
class="type">int HandleDate;
class="type">long WIDTH;
class="type">long HEIGHT;
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">MqlDateTime stm;
   ChartSetInteger(class="num">0,CHART_SHIFT,class="kw">false);
   ChartSetInteger(class="num">0,CHART_AUTOSCROLL,class="kw">false);
   ChartSetInteger(class="num">0,CHART_SHOW_OBJECT_DESCR,class="kw">false);
   WIDTH=ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS);
   ChartSetInteger(class="num">0,CHART_SHOW_PRICE_SCALE,class="kw">false);
   if(Dataset=="Test")
     {

「用脚本把历史K线批量截成训练图」

把 MT5 图表按 H1 一根根回拨并截图,是做价格行为数据集最直接的办法。下面这段逻辑先开一个以品种名+Date.csv 命名的 ANSI 文件,用来记录每帧对应的时间,再跳到指定日期的 K 线位置。

HandleDate=FileOpen(Symbol()+"Date.csv",FILE_CSVFILE_READFILE_WRITEFILE_ANSI,";");

ChartNavigate(0,CHART_END,-(iBarShift(NULL,PERIOD_H1,StringToTime(DateTest)))); Sleep(1000); for(int i=iBarShift(NULL,PERIOD_H1,StringToTime(DateTest)); i>0; i--) { Date1=TimeToString(iTime(NULL,PERIOD_H1,i)); if(DateTest<=Date1) { if(ChartNavigate(0,CHART_END,-i)) { Sleep(20); if(ChartScreenShot(0, (string)count + ".png", (int)WIDTH, (int)WIDTH, ALIGN_LEFT)) { FileWrite(HandleDate,TimeToString(iTime(NULL,PERIOD_H1,i))); count++; Sleep(20); } } } } 第 1 行:FileOpen 以 CSV 读写+ANSI 打开「品种Date.csv」,分隔符用分号。第 2 行:ChartNavigate 把 0 号图表滚到 DateTest 那根 H1 棒(iBarShift 算偏移)。第 4 行起:从那根棒往前遍历到最新,每帧 ChartNavigate 定位、Sleep(20) 防卡顿,ChartScreenShot 按 WIDTH×WIDTH 截左对齐 PNG,成功就写时间进 CSV 并 count++。 如果是 Train 模式,则改为从 Date 扫到 DateOut:ChartNavigate(0,CHART_END,-iBarShift(NULL,PERIOD_H1,StringToTime(Date))); 之后 for 里用 i>=iBarShift(...DateOut) 控制区间,并用 TimeToStruct 取 stm.day 判断换日。实盘品种波动剧烈、杠杆风险高,截图仅供离线模型训练,不等于任何方向预判。 开 MT5 把 WIDTH 设成 224 或 256,接自己的 DateTest 跑一遍,数一下 count 最终落在几千张,就能知道这段循环在你机器上的吞吐。

MQL5 / C++
HandleDate=FileOpen(Symbol()+"Date.csv",FILE_CSV|FILE_READ|FILE_WRITE|FILE_ANSI,";");
ChartNavigate(class="num">0,CHART_END,-(iBarShift(NULL,PERIOD_H1,StringToTime(DateTest))));
Sleep(class="num">1000);
for(class="type">int i=iBarShift(NULL,PERIOD_H1,StringToTime(DateTest)); i>class="num">0; i--)
  {
   Date1=TimeToString(iTime(NULL,PERIOD_H1,i));
   if(DateTest<=Date1)
     {
      if(ChartNavigate(class="num">0,CHART_END,-i))
        {
         Sleep(class="num">20);
         if(ChartScreenShot(class="num">0, (class="type">class="kw">string)count + ".png", (class="type">int)WIDTH, (class="type">int)WIDTH, ALIGN_LEFT))
           {
            FileWrite(HandleDate,TimeToString(iTime(NULL,PERIOD_H1,i)));
            count++;
            Sleep(class="num">20);
           }
        }
     }
  }
if(Dataset=="Train")
  {
   ChartNavigate(class="num">0,CHART_END,-iBarShift(NULL,PERIOD_H1,StringToTime(Date)));
   Sleep(class="num">1000);
   for(class="type">int i=iBarShift(NULL,PERIOD_H1,StringToTime(Date)); i>=iBarShift(NULL,PERIOD_H1,StringToTime(DateOut)); i--)
    {
     TimeToStruct(iTime(NULL,PERIOD_H1,i),stm);
     Date1=TimeToString(iTime(NULL,PERIOD_H1,i));
     if(DateOut>=Date1 && Date<=Date1)
       {
        if(ChartNavigate(class="num">0,CHART_END,-i))
          {
           Sleep(class="num">20);
           if(day != stm.day)
             {

常见问题

CNN能直接吃下把十几个指标压成的一张图,捕捉人眼难觉的空间形态,省去手动判断指标背离的麻烦,但准确率仍受样本和行情结构影响。
写个脚本按固定窗口滑动截取图表区域存为图片,设定好分辨率与标注规则即可批量产出训练集,注意覆盖多品种多周期。
小布可自动按你的品种和周期切出历史图表并标注形态,把重复截图劳动交给他,你只调模型结构。
池化会让图变小但保留显著特征,若担心丢失可在浅层加跳跃连接或减小步长,实盘前用验证集查召回率。
用统一归一化灰度或固定色表表达指标强弱,避免随机配色引入噪声,训练集和实盘图必须同款映射。