第卅四章 机器学习中的ONNX模型

ONNX (Open Neural Network Exchange)是机器学习模型的开源格式。这个项目有几个主要优势:

  • ONNX得到Microsoft、Facebook、Amazon等大公司的支持。
  • 它的开放格式支持不同机器学习工具包之间的格式转换,而Microsoft(微软) ONNXMLTools允许将模型转换为ONNX格式。
  • 如果传递的参数类型与模型不匹配,MQL5会为模型输入和输出提供自动数据类型转换。
  • ONNX模型可以使用各种机器学习工具创建。目前在Caffe2、Microsoft Cognitive Toolkit、MXNet、PyTorch和OpenCV中得到支持。还可以使用其他流行框架和程序库的接口。
  • 借助MQL5语言,您可以在交易策略中实施ONNX模型,并将其与MetaTrader 5平台的所有优势结合使用,从而在金融市场中实现高效操作。
  • 在为实时交易调整模型之前,您可以在策略测试中根据历史数据测试模型行为,而无需使用第三方工具。

MQL5为使用ONNX提供以下函数:

函数 操作
OnnxCreate 创建ONNX会话,从 *.onnx文件中加载模型
OnnxCreateFromBuffer 创建ONNX会话,从数据数组加载模型
OnnxRelease 关闭ONNX会话
OnnxRun 运行ONNX模型
OnnxGetInputCount 获取ONNX模型中的输入数
OnnxGetOutputCount 获取ONNX模型中的输出数
OnnxGetInputName 通过索引获取模型输入名称
OnnxGetOutputName 通过索引获取模型输出名称
OnnxGetInputTypeInfo 从模型获取输入类型的描述
OnnxGetOutputTypeInfo 从模型获取输出类型的描述
OnnxSetInputShape 通过索引设置模型输入数据的形状
OnnxSetOutputShape 通过索引设置模型输出数据的形状

# 34.1 MQL5中的ONNX支持

ONNX是一种用于表示机器学习模型的开放格式构建。该标准定义了一组通用的运算符和通用的文件格式,使开发人员能够使用不同框架、工具、运行时间和编译器的模型。

因此,ONNX开放格式允许您在不同的平台和机器学习工具包之间接收和传输机器学习模型。ONNX以MQL5语言实施,使AI开发人员能够在MetaTrader 5平台提供的高性能执行环境中运行创建的模型。

MQL5执行速度与C++应用程序的执行速度相当。MQL5和C++标准测试的执行结果证明了这一点。柱形图越低,执行所花费的时间越少(以毫秒为单位),结果越好。测试执行在Windows 10(build 17763) x64、Xeon E5-2630 v4 @ 2.20GHz、内存:65457 Mb。

MQL5与MQL4的与C++ 速度比较

fpr-tpr

新异步交易操作和本地ONNX支持为您提供了以前只有少数专业AI开发人员和机构交易者才能获得的新机会。MQL5中的ONNX支持使交易者能够在他们喜欢的开发环境中训练金融市场交易模型,然后以低网络成本、高订单簿更新速度和异步订单提交进行交易。

目前,ONNX正在由Microsoft(微软)、Facebook、Amazon(亚马逊)等合作伙伴公司共同开发和维护,为这个开放项目的进一步发展提供了保障。

# 34.2 Format Conversion(格式转换)

ONNX是一个开放格式,允许使用来自不同机器学习工具包的模型。许多框架都支持该格式,包括Chainer、 Caffee2和PyTorch。

将模型转换为ONNX格式的最受欢迎的工具之一是微软ONNXMLTools。

ONNXMLTools安装和使用说明可在 GitHub repo中获得。目前支持以下工具包:

  • Keras (a wrapper of keras2onnx converter)
  • Tensorflow (a wrapper of tf2onnx converter)
  • scikit-learn (a wrapper of skl2onnx converter)
  • Apple Core ML
  • Spark ML (experimental)
  • LightGBM
  • libscm;
  • XGBoost;
  • H2O
  • CatBoost

ONNXMLTools可以轻松安装。有关安装细节和模型转换示例,请参阅项目页https://github.com/onnx/onnxmltools#install。

# 34.3 运行ONNX模型时自动转换输入和输出值

MQL5中当前ONNX版本仅支持输入/输出值的张量。张量是包含以下数据类型元素的数据数组:

ONNX类型 对应于MQL5类型
ONNX_DATA_TYPE_BOOL 布尔型
ONNX_DATA_TYPE_FLOAT 浮点型
ONNX_DATA_TYPE_UINT8 无字符型(uchar)<
ONNX_DATA_TYPE_INT8 字符型(char)
ONNX_DATA_TYPE_UINT16 无符短型(ushort)
ONNX_DATA_TYPE_INT16 短整型(short)
ONNX_DATA_TYPE_INT32 整型(int)
ONNX_DATA_TYPE_INT64 长整型(long)
ONNX_DATA_TYPE_FLOAT16
ONNX_DATA_TYPE_DOUBLE 双精度
ONNX_DATA_TYPE_UINT32 无符号整型(uint)
ONNX_DATA_TYPE_UINT64 无符长型(ulong)
ONNX_DATA_TYPE_COMPLEX64
ONNX_DATA_TYPE_COMPLEX128 复杂型
ONNX_DATA_TYPE_BFLOAT16
ONNX_DATA_TYPE_STRING

只有数组、向量和矩阵(我们将它们称为数据)可作为输入/输出值进入ONNX模型。

如果参数类型与ONNX模型的参数类型不匹配,并且在没有指定ONNX_NO_CONVERSION标识的情况下调用OnnxRun,将应用自动数据转换。自动转换意味着在运行ONNX模型之前,用户数据将通过相关转换被复制到ONNX张量中。

当ONNX模型在没有自动转换的情况下运行时,将使用数据计算模型,而无需任何额外的复制。

重要!自动转换不控制过载(截断),因此您应该仔细监控输入到ONNX模型中的数据和数据类型。

自动转换支持以下ONNX类型:

  • ONNX_DATA_TYPE_BOOL
  • ONNX_DATA_TYPE_FLOAT
  • ONNX_DATA_TYPE_UINT8
  • ONNX_DATA_TYPE_INT8
  • ONNX_DATA_TYPE_UINT16
  • ONNX_DATA_TYPE_INT16
  • ONNX_DATA_TYPE_INT32
  • ONNX_DATA_TYPE_INT64
  • ONNX_DATA_TYPE_FLOAT16
  • ONNX_DATA_TYPE_DOUBLE
  • ONNX_DATA_TYPE_UINT32
  • ONNX_DATA_TYPE_UINT64
  • ONNX_DATA_TYPE_COMPLEX64
  • ONNX_DATA_TYPE_COMPLEX128

不支持的类型:

  • ONNX_DATA_TYPE_BFLOAT16
  • ONNX_DATA_TYPE_STRING

张量类型的自动转换规则

如果MQL5类型未包含在模型支持的类型列表中,运行ONNX模型将返回ERR_ONNX_NOT_SUPPORTED错误(错误代码5802)。

注意:自动转换时,color类型被处理为uint,而datetime被处理为long。

输入值的自动转换

ONNX类型(张量项目类型) 自动转换支持的MQL5类型
ONNX_DATA_TYPE_BOOL bool, char, uchar, short, ushort, int, color, uint, datetime, long, folat, double, complex
在转换过程中,通过与0的简单比较来检查Data元素
ONNX_DATA_TYPE_FLOAT16 float, double
ONNX_DATA_TYPE_FLOAT char, uchar, short, ushort, int, color, uint, datetime, long, ulong, float, double
ONNX_DATA_TYPE_UINT8 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT8 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT16 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT16 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT32 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT64 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_DOUBLE 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT32 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT64 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_COMPLEX64 复杂型
ONNX_DATA_TYPE_COMPLEX128 复杂型

输出值的自动转换

ONNX类型(张量项目类型) 自动转换支持的MQL5类型
ONNX_DATA_TYPE_BOOL bool, char, uchar, short, ushort, int, color, uint, datetime, long, folat, double, complex
如果张量元素为零,则Data元素设为0;否则,该值为1
ONNX_DATA_TYPE_FLOAT16 float, double
ONNX_DATA_TYPE_FLOAT char, uchar, short, ushort, int, color, uint, datetime, long, ulong, float, double
ONNX_DATA_TYPE_UINT8 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT8 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT16 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT16 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT32 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_INT64 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_DOUBLE 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT32 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_UINT64 请见ONNX_DATA_TYPE_FLOAT
ONNX_DATA_TYPE_COMPLEX64 复杂型
ONNX_DATA_TYPE_COMPLEX128 复杂型

# 34.4 创建模型

有多种方法可以获得ONNX格式的现有模型。受欢迎的ONNX Model Zoo程序库包含针对不同任务类型的几个预训练ONNX模型。这个集合的优势是每个模型的笔记本都包含训练数据集的链接和对描述模型体系结构的原始论文的引用。

大部分机器学习框架使用Python。要对Python安装ONNX runtime,请在以下命令中选择一个:

pip install onnxruntime       # CPU build
pip install onnxruntime-gpu   # GPU build
1
2

要在Python中调用ONNX runtime,请使用以下命令

import onnxruntime
session = onnxruntime.InferenceSession("path to model")
1
2

对于模型输入和输出, 请检查相关模型的文档。您还可以使用可视化工具来查看模型,例如Netron或WinML Dashboard。在ONNX runtime中, 您还可以查询模型的元数据及其输入和输出:

results = session.run(["output1", "output2"], {
                      "input1": indata1, "input2": indata2})
results = session.run([], {"input1": indata1, "input2": indata2})
1
2
3

您可以使用Python直接在MetaTrader 5程序端或MetaEditor中创建ONNX模型。

MetaTrader 5中的Python

MetaTrader 5为Python脚本提供了即时可用的支持。若要启用这些操作,程序端开发人员提供了MetaTrader5 Python模块:https://pypi.org/project/MetaTrader5。

在MetaEditor集成开发环境中,除了用MQL5创建应用程序外,还可以直接从编辑器中运行Python脚本。为此,请在MetaEditor设置中指定可执行文件的路径:

metaeditor settings

在MetaEditor设置中设置Python可执行文件的路径 如果您的计算机上没有安装Python,请点击“安装”并下载安装文件。

您可以在MetaEdtior中创建Python脚本,或者将其上传到程序端的数据文件夹中,并使用F7 (Compile)键立即运行。这将打开MetaTrader 5程序端,脚本在当前图表上运行。来自Python控制板(stdout, stderr)的消息将显示在错误部分。

MetaTrader 5中的模型操作 MQL5语言允许您直接在MetaTrader 5程序端中运行ONNX模型。分为三个步骤:

  1. 在第三方平台(例如 Python)中训练模型。
  2. 将模型转换为ONNX。
  3. 使用ONNX函数将ONNX模型加入到EA交易中,并在MetaTrader 5程序端中运行。

MQL5中的Python集成允许运行python脚本并在MetaEditor中保存ONNX模型或直接在MetaTrader 5图表上运行。您可以在程序端中根据需要随时使用预先编写的Python脚本来训练模型。该程序库包含用于获取价格数据的现有函数,这些数据可以输入到ONNX模型中:

  • copy_rates_from - 获取从指定日期开始的柱形图
  • copy_rates_from_pos - 获取从指定索引开始的柱形图
  • copy_ticks_range - 获取指定日期范围的柱形图
  • copy_ticks_from - 获取从指定日期开始的报价
  • copy_ticks_range - 获取指定日期范围的报价

模型示例 #

完整的ONNX模型示例可在公共项目中获得。您应该首先在导航器中通过在MetaEditor设置中指定您的MQL5登录名(区分大小写)激活MQL5存储。

activate_storage

激活后,找到ONNX.Price.Prediction项目并通过快捷菜单命令加入。

project join

接下来,从MQL5存储更新项目。

update_project

该项目包含一个ONNX模型、两个python脚本、一个用于项目操作的MQL5脚本和一个MQL5项目文件 (ONNX.Price.Prediction.mqproj)。

python_script

您可以使用项目中包含的PricePredictionTraining.py脚本自行创建ONNX模型。为此,您应该首先从命令行安装所需的模块。

python.exe -m pip install --upgrade pip
python -m pip install --upgrade tensorflow
python -m pip install --upgrade pandas
python -m pip install --upgrade scikit-learn
python -m pip install --upgrade matplotlib
python -m pip install --upgrade tqdm
python -m pip install --upgrade metatrader5
python -m pip install --upgrade onnx==1.12
python -m pip install --upgrade tf2onnx
python -m pip install --upgrade numpy
python -m pip install onnxruntime
1
2
3
4
5
6
7
8
9
10
11

安装模块后,在MetaEditor中打开PricePredictionTraining.py脚本,并使用编译按键或F7键运行它。

python_script_compile

在运行Python脚本之前,请确保MetaTrader 5程序端已连接到具有EURUSD交易品种可用的服务器。例如,连接到MetaQuotes-Demo服务器,并在程序端设置中选中“与Python集成”。

terminal_py_integration_check_box

在训练网络时,MetaEditor将打印来自Python脚本的消息,直到训练完成。

onnx_model_ready

当结果为100%时,ONNX模型准备就绪,它被保存到位于

<terminal data directory>\MQL5\Shared Projects\ONNX.Price.Prediction\Python

的项目文件夹中。

您可以通过运行第二个脚本PricePrediction.py,按 F7来检查生成的模型。

prediction result

# 34.5 运行模型

要在MQL5中运行ONNX模型,请完成以下3个步骤:

  1. 使用OnnxCreate函数从*.onnx文件中加载模型或使用OnnxCreateFromBuffer函数从数组中加载模型。
  2. 使用OnnxSetInputShape和OnnxSetOutputShape函数指定输入和输出数据形状。
  3. 使用OnnxRun函数运行模型,并将相关的输入和输出参数传递到这里。
  4. 当需要时,您可以使用OnnxRelease函数终止模型操作。

在创建ONNX模型时,您应该考虑现有的范围和限制,请参阅https://github.com/microsoft/onnxruntime/blob/rel-1.14.0/docs/OperatorKernels.md

这类限制的一些示例如下:

Operation 支持的数据类型
ReduceSum tensor(double), tensor(float), tensor(int32), tensor(int64)
Mul tensor(bfloat16), tensor(double), tensor(float), tensor(float16), tensor(int32), tensor(int64), tensor(uint32), tensor(uint64)

下面是来自公共项目ONNX.Price.Prediction的MQL5代码示例。


const long   ExtOutputShape[] = {1,1};    // 模型输出形状
const long   ExtInputShape [] = {1,10,4}; // 模型输入形状
#resource "Python/model.onnx" as uchar ExtModel[]// 作为资源的模型
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                   |
//+------------------------------------------------------------------+
int OnStart(void)
  {
   matrix rates;
//--- 获取10个柱形图
   if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,2,10))
      return(-1);
//--- 输入一组OHLC向量
   matrix x_norm=rates.Transpose();
   vector m=x_norm.Mean(0);               
   vector s=x_norm.Std(0);
   matrix mm(10,4);
   matrix ms(10,4);
//--- 填充归一化矩阵
   for(int i=0; i<10; i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
//--- 标准化输入数据
   x_norm-=mm;
   x_norm/=ms;
//--- 创建模型
   long handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- 指定输入数据形状
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("OnnxSetInputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 指定输出数据形状
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("OnnxSetOutputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 将标准化输入数据转换为浮点类型
   matrixf x_normf;
   x_normf.Assign(x_norm);
//--- 在这里获取模型的输出数据,即价格预测
   vectorf y_norm(1);
//--- 运行模型
   if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
     {
      Print("OnnxRun failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 将模型的输出值打印到日志中
   Print(y_norm);
//--- 进行反向转换,以获得预测价格
   double y_pred=y_norm[0]*s[3]+m[3];
   Print("price predicted:",y_pred);
//--- 完成操作
   OnnxRelease(handle);
   return(0);
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

脚本运行示例:

ONNX: Creating and using per session threadpools since use_per_session_threads_ is true
ONNX: Dynamic block base set to 0
ONNX: Initializing session.
ONNX: Adding default CPU execution provider.
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Use DeviceBasedPartition as default
ONNX: Saving initialized tensors.
ONNX: Done saving initialized tensors
ONNX: Session successfully initialized.
[0.28188983]
predicted 1.0559258806393044
1
2
3
4
5
6
7
8
9
10
11
12
13
14

MetaTrader 5 终端为计算选择了最佳执行器 - ONNX Runtime Execution Provider。在本示例中,模型在 CPU 上执行。

让我们修改脚本,根据前 10 个条形图的值计算成功预测收盘价的百分比。

#resource "Python/model.onnx" as uchar ExtModel[]// 作为资源的模型
 
#define TESTS 10000  // number of test datasets
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                   |
//+------------------------------------------------------------------+
int OnStart()
  {
//--- 创建模型
   long session_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
   if(session_handle==INVALID_HANDLE)
     {
      Print("Cannot create model. Error ",GetLastError());
      return(-1);
     }
 
//--- 由于没有为模型定义输入张量大小,请明确指定
//--- 第一个指数是批量大小,第二个指数是系列大小,第三个指数是系列数(OHLC)
   const long input_shape[]={1,10,4};
   if(!OnnxSetInputShape(session_handle,0,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      return(-2);
     }
 
//--- 由于没有为模型定义输出张量大小,请明确指定
//--- 第一个指数是批量大小,必须匹配输入张量的批量大小
//--- 第二个指数是预测价格的数量( 这里只预测Close)
   const long output_shape[]={1,1};
   if(!OnnxSetOutputShape(session_handle,0,output_shape))
     {
      Print("OnnxSetOutputShape error ",GetLastError());
      return(-3);
     }
//--- 运行测试
   vector closes(TESTS);      // 存储验证价格的向量
   vector predicts(TESTS);    // 存储所得预测的向量
   vector prev_closes(TESTS); // 存储之前价格的向量
 
   matrix rates;              // 获得OHLC系列的矩阵
   matrix splitted[2];        // 将系列分为测试和验证的两个子矩阵
   ulong  parts[]={10,1};     // 划分子矩阵的大小
 
//--- 从前一柱形图开始
   for(int i=1; i<=TESTS; i++)
     {
      //--- 获取11个柱形图
      rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,i,11);
      //--- 将矩阵分为测试和验证
      rates.Vsplit(parts,splitted);
      //--- 从验证矩阵得到收盘价
      closes[i-1]=splitted[1][3][0];
      //--- 测试系列中的最后收盘价
      prev_closes[i-1]=splitted[0][3][9];
 
      //--- 提交10个柱形图测试矩阵进行测试
      predicts[i-1]=PricePredictionTest(session_handle,splitted[0]);
      //--- 运行时间错误
      if(predicts[i-1]<=0)
        {
         OnnxRelease(session_handle);
         return(-4);
        }
     }
//--- 完成操作
   OnnxRelease(session_handle);
//--- 评估价格移动预测是否正确
   int    right_directions=0;
   vector delta_predicts=prev_closes-predicts;
   vector delta_actuals=prev_closes-closes;
 
   for(int i=0; i<TESTS; i++)
      if((delta_predicts[i]>0 && delta_actuals[i]>0) || (delta_predicts[i]<0 && delta_actuals[i]<0))
         right_directions++;
   PrintFormat("right direction predictions = %.2f%%",(right_directions*100.0)/double(TESTS));
//--- 
   return(0);
  }
//+------------------------------------------------------------------+
//|  准备数据并运行模型                                                |
//+------------------------------------------------------------------+
double PricePredictionTest(const long session_handle,matrix& rates)
  {
   static matrixf input_data(10,4); // 用于转换输入的矩阵
   static vectorf output_data(1);   // 接收结果的向量
   static matrix mm(10,4);          // 水平向量矩阵Mean
   static matrix ms(10,4);          // 水平向量矩阵Std
 
//--- 一组OHLC垂直向量必须输入到模型中
   matrix x_norm=rates.Transpose();
//--- 标准价格
   vector m=x_norm.Mean(0);
   vector s=x_norm.Std(0);
   for(int i=0; i<10; i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
   x_norm-=mm;
   x_norm/=ms;
 
//--- 运行模型
   input_data.Assign(x_norm);
   if(!OnnxRun(session_handle,ONNX_DEBUG_LOGS,input_data,output_data))
     {
      Print("OnnxRun error ",GetLastError());
      return(0);
     }
//--- 将输出值中的价格非标准化
   double y_pred=output_data[0]*s[3]+m[3];
 
   return(y_pred);
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

运行脚本:预测准确率约为51%

ONNX: Creating and using per session threadpools since use_per_session_threads_ is true
ONNX: Dynamic block base set to 0
ONNX: Initializing session.
ONNX: Adding default CPU execution provider.
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Use DeviceBasedPartition as default
ONNX: Saving initialized tensors.
ONNX: Done saving initialized tensors
ONNX: Session successfully initialized.
right direction predictions = 51.34 %
1
2
3
4
5
6
7
8
9
10
11
12
13

# 34.6 策略测试中的模型验证

为金融市场操作创建的模型可以在 MetaTrader 5程序端策略测试部分进行验证。这是最快、最方便的选项,无需额外模拟市场环境和交易条件。

若要测试模型,需要根据公共项目ONNX.Price.Prediction的代码创建一个EA交易程序。这将需要进行一些编辑。

将模型创建移至OnInit函数。onnx会话将在OnDeinit中关闭。将主要模型操作块定位到OnTick处理程序。

此外,添加获取前两个柱形图的收盘价,需要与实际收盘价和预测收盘价进行比较。

Expert Advisor 代码小且易于阅读。

const long   ExtInputShape [] = {1,10,4}; // 模型输入形状
const long   ExtOutputShape[] = {1,1};    // 模型输出形状
#resource "Python/model.onnx" as uchar ExtModel[];// 作为资源的模型
 
long handle;         // 模型句柄
ulong predictions=0; // 预测计数器
ulong confirmed=0;   // 成功预测计数器
//+------------------------------------------------------------------+
//| EA交易初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 基本检查
   if(_Symbol!="EURUSD")
     {
      Print("Symbol must be EURUSD, testing aborted");
      return(-1);
     }
   if(_Period!=PERIOD_H1)
     {
      Print("Timeframe must be H1, testing aborted");
      return(-1);
     }
//--- 创建模型
   handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- 指定输入数据形状
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("OnnxSetInputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 指定输出数据形状
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("OnnxSetOutputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA交易去初始化函数                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 完成模型操作
   OnnxRelease(handle);
//--- 计算和输出预测统计
   PrintFormat("Successfull predictions = %.2f %%",confirmed*100./double(predictions));
  }
//+------------------------------------------------------------------+
//| EA报价函数                                                        |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime open_time=0;
   static double predict;
//--- 检查当前柱形图开盘时间
   datetime time=iTime(_Symbol,_Period,0);
   if(time==0)
     {
      PrintFormat("Failed to get Time(0), error %d", GetLastError());
      return;
     }
//--- 如果开盘时间没有改变,则退出直至下一次OnTick调用
   if(time==open_time)
      return;
//--- 获取最后两个已完成柱形图的收盘价
   double close[];
   int recieved=CopyClose(_Symbol,_Period,1,2,close);
   if(recieved!=2)
     {
      PrintFormat("CopyClose(2 bars) failed, error %d",GetLastError());
      return;
     }
   double delta_predict=predict-close[0]; // predicted price change
   double delta_actual=close[1]-close[0]; // actual price change
   if((delta_predict>0 && delta_actual>0) || (delta_predict<0 && delta_actual<0))
      confirmed++;
 
//--- 计算新柱形图上的收盘价,以验证下一个柱形图上的价格
   matrix rates;
//--- 获取10个柱形图
   if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,1,10))
      return;
//--- 输入一组OHLC向量
   matrix x_norm=rates.Transpose();
   vector m=x_norm.Mean(0);
   vector s=x_norm.Std(0);
   matrix mm(10,4);
   matrix ms(10,4);
//--- 填充归一化矩阵
   for(int i=0; i<10; i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
//--- 标准化输入数据
   x_norm-=mm;
   x_norm/=ms;
//--- 将标准化输入数据转换为浮点类型
   matrixf x_normf;
   x_normf.Assign(x_norm);
//--- 在这里获取模型的输出数据,即价格预测
   vectorf y_norm(1);
//--- 运行模型
   if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
     {
      Print("OnnxRun failed, error ",GetLastError());
     }
//--- 进行反向转换,以获得预测价格并在新柱形图上对其验证
   predict=y_norm[0]*s[3]+m[3];
   predictions++;  // 增加预测计数器
   Print(predictions,". close prediction = ",predict);
//--- 保存柱形图开盘时间,以检查下一个报价
   open_time=time;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

编译EA交易,并在2022年期间运行测试。使用H1时间周期指定EURUSD,这是训练模型的数据。可以忽略报价建模模式,因为代码检查的是新柱形图的出现。

回测设置

运行并在测试日志中查看结果。它表明略高于50%的预测在2022年是正确的。

测试日志

如果初步模型测试产生令人满意的结果,您可以开始基于此模型编写完整的交易策略。

# 34.7 OnnxCreate

创建ONNX会话,从*.onnx文件中加载一个模型。

long  OnnxCreate(
   string  filename,  // 文件路径
   uint    flags      //创建模型的标识
   );
1
2
3
4

参数*

filename

[in] Path to the .onnx file of the model 相对于\MQL5\Files\文件夹的到模型.onnx文件的路径。

flags

[in] ENUM_ONNX_FLAGS中的标识,描述模型创建模式:ONNX_COMMON_FOLDER和ONNX_DEBUG_LOGS。

返回值

创建会话的句柄,或者出现错误时的INVALID_HANDLE。要获取错误代码,请调用GetLastError函数。

注意

如果在磁盘上找不到指定的文件,系统将尝试重新打开文件,并在文件名后附加“.onnx”扩展名。

# 34.8 OnnxCreateFromBuffer

创建ONNX会话,从数据数组加载模型。

long  OnnxCreateFromBuffer(
   const uchar&  buffer[],   // 数组参考
   ulong         flags       // 模型创建标识
   );
1
2
3
4

参数

buffer

[in] 使用ONNX模型数据的数组。

flags

[in] ENUM_ONNX_FLAGS中的标识,描述模型创建模式:ONNX_COMMON_FOLDER和ONNX_DEBUG_LOGS。

返回值

创建会话的句柄,或者出现错误时的INVALID_HANDLE。要获取错误代码,请调用GetLastError函数。

# 34.9 OnnxRelease

关闭ONNX会话。

bool  OnnxRelease(
   long   onnx_handle  //ONNX会话句柄
   );
1
2
3

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

返回值

如果成功返回true;否则返回false。要获取错误代码,请调用GetLastError函数。

# 34.10 OnnxRun

运行ONNX模型。

bool  OnnxRun(
   long    onnx_handle,  // ONNX会话句柄
   ulong   flags,        // 描述运行模式的标识
   ...                   // 模型的输入和输出
   );
1
2
3
4
5

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

flags

[in] ENUM_ONNX_FLAGS中的标识,描述运行模式:ONNX_DEBUG_LOGS和ONNX_NO_CONVERSION。

...

[in] [out] 模型输入和输出。

成功返回true,否则返回false。要获取错误代码,请调用GetLastError函数。

ENUM_ONNX_FLAGS

ID 描述
ONNX_DEBUG_LOGS 输出调试日志
ONNX_NO_CONVERSION 禁用自动转换,按原样使用用户数据
ONNX_COMMON_FOLDER 加载Common\Files文件夹的模型文件;该值等于FILE_COMMON标识

例如:

const long                             ExtOutputShape[] = {1,1};    // 模型输出形状
const long                             ExtInputShape [] = {1,10,4}; // 模型输入形状
#resource "Python/model.onnx" as uchar ExtModel[]                   // 作为资源的模型
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                   |
//+------------------------------------------------------------------+
int OnStart(void)
  {
   matrix rates;
//--- 获取10个柱形图
   if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,2,10))
      return(-1);
//--- 输入一组OHLC向量
   matrix x_norm=rates.Transpose();
   vector m=x_norm.Mean(0);               
   vector s=x_norm.Std(0);
   matrix mm(10,4);
   matrix ms(10,4);
//--- 填充归一化矩阵
   for(int i=0; i<10; i++)
     {
      mm.Row(m,i);
      ms.Row(s,i);
     }
//--- 标准化输入数据
   x_norm-=mm;
   x_norm/=ms;
//--- 创建模型
   long handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- 指定输入数据形状
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("OnnxSetInputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 指定输出数据形状
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("OnnxSetOutputShape failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 将标准化输入数据转换为浮点类型
   matrixf x_normf;
   x_normf.Assign(x_norm);
//--- 在这里获取模型的输出数据,即价格预测
   vectorf y_norm(1);
//--- 运行模型
   if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
     {
      Print("OnnxRun failed, error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 将模型的输出值打印到日志中
   Print(y_norm);
//--- 进行反向转换,以获得预测价格
   double y_pred=y_norm[0]*s[3]+m[3];
   Print("price predicted:",y_pred);
//--- 已完成操作
   OnnxRelease(handle);
   return(0);
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

另见

OnnxSetInputShape,OnnxSetOutputShape

# 34.11 OnnxGetInputCount

获取ONNX模型中的输入数。

long  OnnxGetInputCount(
   long   onnx_handle  //ONNX会话句柄
   );
1
2
3

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

返回值

如果成功,返回输入参数的数量;否则返回-1。要获取错误代码,请调用GetLastError函数。

# 34.12 OnnxGetOutputCount

获取 ONNX 模型的输出数。

long  OnnxGetOutputCount(
   long   onnx_handle  //ONNX会话句柄
   );
1
2
3

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

返回值

如果成功,返回输出参数的数量;否则返回-1。要获取错误代码,请调用GetLastError函数。

# 34.13 OnnxGetInputName

通过索引获取模型输入名称

string  OnnxGetInputName(
   long   onnx_handle,  // ONNX会话句柄
   long   index         // 参数索引
   );
1
2
3
4

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

index

[in] 输入参数的索引,从0开始。

返回值

如果成功,返回输入参数的名称;否则返回NULL。要获取错误代码,请调用GetLastError函数。

# 34.14 OnnxGetOutputName

通过索引获取模型输出名称。

string  OnnxGetOutputName(
   long   onnx_handle,  // ONNX会话句柄
   long   index         // 参数索引
   );
1
2
3
4

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

index

[in] 输出参数的索引,从0开始。

返回值

如果成功,返回输出参数的名称;否则返回NULL。要获取错误代码,请调用GetLastError函数。

# 34.15 OnnxGetInputTypeInfo

从模型获取输入类型的描述

bool  OnnxGetInputTypeInfo(
   long           onnx_handle,  // ONNX会话句柄
   long           index,        // 参数索引
   OnnxTypeInfo&  typeinfo      // 参数类型描述
   );
1
2
3
4
5

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

index

[in] 输入参数的索引,从0开始。

typeinfo

[out] 描述输入参数类型的OnnxTypeInfo结构。

返回值

如果成功返回true;否则返回false。要获取错误代码,请调用GetLastError函数。

# 34.16 OnnxGetOutputTypeInfo

从模型获取输出类型的描述。

bool  OnnxGetOutputTypeInfo(
   long           onnx_handle,  // ONNX会话句柄
   long           index,        // 参数索引
   OnnxTypeInfo&  typeinfo      // 参数类型描述
   );
1
2
3
4
5

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

index

[in] 输出参数的索引,从0开始。

typeinfo

[out] 描述输出参数类型的OnnxTypeInfo结构。

返回值

如果成功返回true;否则返回false。要获取错误代码,请调用GetLastError函数。

# 34.17 OnnxSetInputShape

通过索引设置模型输入数据的形状。

bool  OnnxSetInputShape(
   long          onnx_handle,  // ONNX会话句柄
   long          input_index,  // 输入参数索引
   const ulong&  shape[]       // 描述输入数据形状的数组
   );
1
2
3
4
5

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

input_index

[in] 输入参数的索引,从0开始。

shape

[in] 描述模型输入数据形状的数组。

返回值

如果成功,返回输入参数的名称;否则返回NULL。要获取错误代码,请调用GetLastError函数。

例如:*

//---- 描述模型输入和输出数据的形状
   const long  ExtOutputShape[] = {1,1};
   const long  ExtInputShape [] = {1,10,4};
//--- 创建模型
   long handle=OnnxCreateFromBuffer(model,ONNX_DEBUG_LOGS);
//--- 指定输入数据形状
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("failed, OnnxSetInputShape error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 指定输出数据形状
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("failed, OnnxSetOutputShape error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

另见

OnnxSetOutputShape

# 34.18 OnnxSetOutputShape

通过索引设置模型输出数据的形状。

bool  OnnxSetOutputShape(
   long          onnx_handle,   // ONNX会话句柄
   long          output_index,  // 输出参数索引
   const ulong&  shape[]        // 描述输出数据形状的数组
   );
1
2
3
4
5

参数

onnx_handle

[in] 通过OnnxCreate或OnnxCreateFromBuffer创建的ONNX会话对象句柄。

output_index

[in] 输出参数的索引,从0开始。

shape

[in] 描述模型输出数据形状的数组。

返回值

如果成功,返回输入参数的名称;否则返回NULL。要获取错误代码,请调用GetLastError函数。

例如:

//---- 描述模型输入和输出数据的形状
   const long  ExtOutputShape[] = {1,1};
   const long  ExtInputShape [] = {1,10,4};
//--- 创建模型
   long handle=OnnxCreateFromBuffer(model,ONNX_DEBUG_LOGS);
//--- 指定输入数据形状
   if(!OnnxSetInputShape(handle,0,ExtInputShape))
     {
      Print("failed, OnnxSetInputShape error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
//--- 指定输出数据形状
   if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
     {
      Print("failed, OnnxSetOutputShape error ",GetLastError());
      OnnxRelease(handle);
      return(-1);
     }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

另见

OnnxSetInputShape

# 34.19 数据结构

以下数据结构用于ONNX模型的操作:

OnnxTypeInfo # 该结构描述了ONNX模型的输入或输出参数的类型

struct OnnxTypeInfo
 {
  ENUM_ONNX_TYPE        type;          // 参数类型
  OnnxTensorTypeInfo    tensor;        // 张量描述
  OnnxMapTypeInfo       map;           // 映射描述
  OnnxSequenceTypeInfo  sequence;      // 序列描述
 };
1
2
3
4
5
6
7

只有张量(ONNX_TYPE_TENSOR)可以作为输入使用。在这种情况下,只有OnnxTypeInfo::tensor字段被填充值,而其他字段(映射和序列)没有定义。

三种OnnxTypeInfo类型(ONNX_TYPE_TENSOR、ONNX_TYPE_MAP或ONNX_TYPE_SEQUENCE)中只有一种可以作为输入使用。相对应的子结构(OnnxTypeInfo::tensor、OnnxTypeInfo::map或OnnxTypeInfo::sequence)根据类型来填充。

OnnxTensorTypeInfo

该结构描述了ONNX模型的输入或输出参数中的张量

struct OnnxTensorTypeInfo
 {
  const ENUM_ONNX_DATA_TYPE  data_type;      // 张量中的数据类型
  const long                 dimensions[];   // 张量中的元素数
 };
1
2
3
4
5

OnnxMapTypeInfo # 该结构描述了ONNX模型的输出参数中获得的映射

struct OnnxMapTypeInfo
 {
  const ENUM_ONNX_DATA_TYPE  key_type;      //密钥类型
  const OnnxTypeInfo&        value_type;    // 值类型
 };
1
2
3
4
5

OnnxSequenceTypeInfo # 该结构描述了ONNX模型的输出参数中获得的序列

struct OnnxSequenceTypeInfo
 {
  const OnnxTypeInfo&        value_type;     // 序列中的数据类型
 };
1
2
3
4

ENUM_ONNX_TYPE #

ENUM_ONNX_TYPE枚举描述了模型参数的类型

ID 描述
ONNX_TYPE_UNKNOWN 未知
ONNX_TYPE_TENSOR Tensor
ONNX_TYPE_SEQUENCE Sequence
ONNX_TYPE_MAP Map
ONNX_TYPE_OPAQUE Abstract (opaque)
ONNX_TYPE_SPARSETENSOR Sparse tensor

ENUM_ONNX_DATA_TYPE #

ENUM_ONNX_DATA_TYPE枚举描述了所用数据的类型

ID 描述
ONNX_DATA_TYPE_UNDEFINED 未定义
ONNX_DATA_TYPE_FLOAT 浮点型
ONNX_DATA_TYPE_INT8 8位整型
ONNX_DATA_TYPE_UINT16 16位无符号整型
ONNX_DATA_TYPE_INT16 16位整型
ONNX_DATA_TYPE_INT32 32位整型
ONNX_DATA_TYPE_INT64 64位整型
ONNX_DATA_TYPE_STRING 字符串
ONNX_DATA_TYPE_BOOL 布尔型
ONNX_DATA_TYPE_FLOAT16 16位浮点型
ONNX_DATA_TYPE_DOUBLE 双精度
ONNX_DATA_TYPE_UINT32 32位无符号整型
ONNX_DATA_TYPE_UINT64 64位无符号整型
ONNX_DATA_TYPE_COMPLEX64 64位复数
ONNX_DATA_TYPE_COMPLEX128 128位复数
ONNX_DATA_TYPE_BFLOAT16 16位bfloat(大脑浮点)

ENUM_ONNX_FLAGS #

ENUM_ONNX_FLAGS枚举描述了模型运行模式

ID 描述
ONNX_DEBUG_LOGS 输出调试日志
ONNX_NO_CONVERSION 禁用自动转换,按原样使用用户数据
ONNX_COMMON_FOLDER 加载Common\Files文件夹的模型文件;该值等于FILE_COMMON标识

使用ONNX模型时的数组转换 机器学习任务并不总是需要更高的计算精度。为了加快计算速度,一些模型使用较低精度的数据类型,例如Float16甚至Float8。为了允许用户将相关数据输入到模型中,MQL5提供了四个特殊函数,可将标准MQL5类型转换为特殊的FP16和FP8类型。

函数 操作
ArrayToFP16 将float或double类型数组复制到给定格式的ushort类型的数组中
ArrayToFP8 将float或double类型数组复制到给定格式的uchar类型的数组中
ArrayFromFP16 将ushort类型数组复制到给定格式的float或double类型的数组中
ArrayFromFP8 将uchar类型数组复制到给定格式的float或double类型的数组中

这些数组转换函数使用以下枚举中指定的特殊格式。

ENUM_FLOAT16_FORMAT #

ENUM_FLOAT16_FORMAT枚举描述了两种FP16类型格式。

ID 描述
FLOAT_FP16 标准16位格式,也称为half
FLOAT_BFP16 特殊的brain float point(大脑浮点)格式

这些格式中的每一种都有其优点和局限性。FLOAT16提供更高的精度,但需要更多的存储和计算资源。另一方面,BFLOAT16在数据处理方面提供了更高的性能和效率,但可能不太准确。

ENUM_FLOAT8_FORMAT #

ENUM_FLOAT8_FORMAT枚举描述了四种FP8类型格式。

FP8(8位浮点)是用于表示浮点数的数据类型之一。在FP8中,每个数字由8个数据位表示,通常分为三个部分:符号、指数和尾数。这种格式提供了准确性和存储效率之间的平衡,使其对于需要内存和计算效率的应用程序具有吸引力。

ID 描述
FLOAT_FP8_E4M3FN 8位浮点数,4位指数,3位尾数。通常用作系数。
FLOAT_FP8_E4M3FNUZ 8位浮点数,4位指数,3位尾数。支持NaN,不支持负零和Inf。通常用作系数。
FLOAT_FP8_E5M2FN 8位浮点数,5位指数,2位尾数。支持NaN和Inf。通常用于渐变。
FLOAT_FP8_E5M2FNUZ 8位浮点数,5位指数,2位尾数。支持NaN,不支持负零和Inf。也用于渐变。

FP8的主要优势之一是其处理大型数据集的效率。通过采用紧凑型数字表示法,FP8减少了内存需求并加速了计算。这对于经常处理大型数据集的机器学习和人工智能应用程序尤其重要。