在类中包装 ONNX 模型
概述
在上一篇文章中,我们用到两个 ONNX 模型来排列投票分类器。 整个源文本被归并到单一的 MQ5 文件。 整个代码被划分为多个函数。 但如果我们尝试模型换位呢? 亦或添加其它模型? 原文会变得更加庞大。 我们来尝试一下面向对象的方式。
1. 我们会用到什么模型呢?
在之前的投票分类器中,我们用到了一个分类模型和一个回归模型。 在回归模型中,我们在用于计算分类时,用预测价格替代预测价格走势(下跌、上涨、不变)。 然而,在这种情况下,我们不能依据分类得到概率分布,而对于所谓的“软投票”这样是不允许的。
我们已准备了 3 个分类模型。 在“如何在 MQL5 中集成 ONNX 模型的示例”一文中已用到两个模型。 第一个模型(回归)被转换为分类模型。 基于 10 个 OHLC 价格序列进行了培训。 第二个模型是分类模型。 基于 63 个收盘价序列进行了培训。
最后,还有一个模型。 该分类模型基于 30 个收盘价序列,和周期为 21 和 34 的简单移动平均线序列进行了训练。 对于图表上收盘价移动平均线与收盘价的交汇点,我们没有做出任何假设 — 所有形态都将由网络以层间系数矩阵的形式计算和记忆。
所有模型均基于 MetaQuotes-Demo 服务器上 2010.01.01 至 2023.01.01 的 EURUSD D1 数据进行训练。 所有三个模型的训练脚本都是用 Python 编写的,并附在本后。 我们不会在此提供它们的源代码,以免分散读者对我们文章主题的注意力。
2. 所有模型都需要一个基类
有三种模型。 每个在输入数据的大小和准备方面都彼此不同。 所有模型都拥有相同的接口。 所有模型的类必须继承自同一基类。
我们来尝试表示基类。
//+------------------------------------------------------------------+ //| ModelSymbolPeriod.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //--- price movement prediction #define PRICE_UP 0 #define PRICE_SAME 1 #define PRICE_DOWN 2 //+------------------------------------------------------------------+ //| Base class for models based on trained symbol and period | //+------------------------------------------------------------------+ class CModelSymbolPeriod { protected: long m_handle; // created model session handle string m_symbol; // symbol of trained data ENUM_TIMEFRAMES m_period; // timeframe of trained data datetime m_next_bar; // time of next bar (we work at bar begin only) double m_class_delta; // delta to recognize "price the same" in regression models public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES period,const double class_delta=0.0001) { m_handle=INVALID_HANDLE; m_symbol=symbol; m_period=period; m_next_bar=0; m_class_delta=class_delta; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CModelSymbolPeriod(void) { Shutdown(); } //+------------------------------------------------------------------+ //| virtual stub for Init | //+------------------------------------------------------------------+ virtual bool Init(const string symbol,const ENUM_TIMEFRAMES period) { return(false); } //+------------------------------------------------------------------+ //| Check for initialization, create model | //+------------------------------------------------------------------+ bool CheckInit(const string symbol,const ENUM_TIMEFRAMES period,const uchar& model[]) { //--- check symbol, period if(symbol!=m_symbol || period!=m_period) { PrintFormat("Model must work with %s,%s",m_symbol,EnumToString(m_period)); return(false); } //--- create a model from static buffer m_handle=OnnxCreateFromBuffer(model,ONNX_DEFAULT); if(m_handle==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Release ONNX session | //+------------------------------------------------------------------+ void Shutdown(void) { if(m_handle!=INVALID_HANDLE) { OnnxRelease(m_handle); m_handle=INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Check for continue OnTick | //+------------------------------------------------------------------+ virtual bool CheckOnTick(void) { //--- check new bar if(TimeCurrent()<m_next_bar) return(false); //--- set next bar time m_next_bar=TimeCurrent(); m_next_bar-=m_next_bar%PeriodSeconds(m_period); m_next_bar+=PeriodSeconds(m_period); //--- work on new day bar return(true); } //+------------------------------------------------------------------+ //| virtual stub for PredictPrice (regression model) | //+------------------------------------------------------------------+ virtual double PredictPrice(void) { return(DBL_MAX); } //+------------------------------------------------------------------+ //| Predict class (regression -> classification) | //+------------------------------------------------------------------+ virtual int PredictClass(void) { double predicted_price=PredictPrice(); if(predicted_price==DBL_MAX) return(-1); int predicted_class=-1; double last_close=iClose(m_symbol,m_period,1); //--- classify predicted price movement double delta=last_close-predicted_price; if(fabs(delta)<=m_class_delta) predicted_class=PRICE_SAME; else { if(delta<0) predicted_class=PRICE_UP; else predicted_class=PRICE_DOWN; } //--- return predicted class return(predicted_class); } }; //+------------------------------------------------------------------+
基类可用于回归模型和分类模型两者。 我们只需要在衍生类中实现相应的方法 — PredictPrice 或 PredictClass。
基类设置模型所要操控(训练模型的数据)的品种周期。 基类还会检查采用该模型的 EA 是否基于所需品种周期操作,并创建一个 ONNX 会话来执行该模型。 基类仅在新柱线的开始处提供操作。
3. 第一个模型类
我们的第一个模型称为 model.eurusd.D1.10.class.onnx,这是一个基于 EURUSD D1 的 10 个 OHLC价格序列进行训练的分类模型。
//+------------------------------------------------------------------+ //| ModelEurusdD1_10Class.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.10.class.onnx" as uchar model_eurusd_D1_10_class[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_10Class : public CModelSymbolPeriod { private: int m_sample_size; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_10Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1) { m_sample_size=10; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_10_class)) { Print("model_eurusd_D1_10_class : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (OHLC) const long input_shape[] = {1,m_sample_size,4}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_10_class : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of classes (up, same or down) const long output_shape[] = {1,3}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_10_class : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict class | //+------------------------------------------------------------------+ virtual int PredictClass(void) { static matrixf input_data(m_sample_size,4); // matrix for prepared input data static vectorf output_data(3); // vector to get result static matrix mm(m_sample_size,4); // matrix of horizontal vectors Mean static matrix ms(m_sample_size,4); // matrix of horizontal vectors Std static matrix x_norm(m_sample_size,4); // matrix for prices normalize //--- prepare input data matrix rates; //--- request last bars if(!rates.CopyRates(m_symbol,m_period,COPY_RATES_OHLC,1,m_sample_size)) return(-1); //--- get series Mean vector m=rates.Mean(1); //--- get series Std vector s=rates.Std(1); //--- prepare matrices for prices normalization for(int i=0; i<m_sample_size; i++) { mm.Row(m,i); ms.Row(s,i); } //--- the input of the model must be a set of vertical OHLC vectors x_norm=rates.Transpose(); //--- normalize prices x_norm-=mm; x_norm/=ms; //--- run the inference input_data.Assign(x_norm); if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(-1); //--- evaluate prediction return(int(output_data.ArgMax())); } }; //+------------------------------------------------------------------+
如上所述:“有三种模型。 在输入数据的大小和准备方面彼此间每个都有所不同“。 我们仅重新定义了两个方法 — Init 和 PredictClass。 对于其它两个模型,将在分别的两个类中重新定义相同的方法。
Init 方法调用 CheckInit 基类方法,在该处为 ONNX 模型创建会话,并显式设置输入和输出张量的大小。 这里的注释多于代码。
PredictClass 方法提供的输入数据准备与训练模型时完全相同。 输入是常规化的 OHLC 价格矩阵。
4. 我们检查一下它是如何工作的。
我们创建了一个非常紧凑的智能系统来测试我们类的性能。
//+------------------------------------------------------------------+ //| ONNX.eurusd.D1.Prediction.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "ModelEurusdD1_10Class.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position CModelEurusdD1_10Class ExtModel; CTrade ExtTrade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!ExtModel.Init(_Symbol,_Period)) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ExtModel.Shutdown(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!ExtModel.CheckOnTick()) return; //--- predict next price movement int predicted_class=ExtModel.PredictClass(); //--- check trading according to prediction if(predicted_class>=0) if(PositionSelect(_Symbol)) CheckForClose(predicted_class); else CheckForOpen(predicted_class); } //+------------------------------------------------------------------+ //| Check for open position conditions | //+------------------------------------------------------------------+ void CheckForOpen(const int predicted_class) { ENUM_ORDER_TYPE signal=WRONG_VALUE; //--- check signals if(predicted_class==PRICE_DOWN) signal=ORDER_TYPE_SELL; // sell condition else { if(predicted_class==PRICE_UP) signal=ORDER_TYPE_BUY; // buy condition } //--- open position if possible according to signal if(signal!=WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { double price=SymbolInfoDouble(_Symbol,(signal==ORDER_TYPE_SELL) ? SYMBOL_BID : SYMBOL_ASK); ExtTrade.PositionOpen(_Symbol,signal,InpLots,price,0,0); } } //+------------------------------------------------------------------+ //| Check for close position conditions | //+------------------------------------------------------------------+ void CheckForClose(const int predicted_class) { bool bsignal=false; //--- position already selected before long type=PositionGetInteger(POSITION_TYPE); //--- check signals if(type==POSITION_TYPE_BUY && predicted_class==PRICE_DOWN) bsignal=true; if(type==POSITION_TYPE_SELL && predicted_class==PRICE_UP) bsignal=true; //--- close position if possible if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { ExtTrade.PositionClose(_Symbol,3); //--- open opposite CheckForOpen(predicted_class); } } //+------------------------------------------------------------------+
由于该模型已经基于直至 2023 年之前的价格数据进行了训练,因此我们从 2023 年 1 月 1 日启动测试。
结果如下所示:
正如我们所见,该模型功能齐全。
5. 第二个模型类
第二个模型称为 model.eurusd.D1.30.class.onnx。 该分类模型的训练基于 EURUSD D1 的 30 个收盘价序列,和周期为 21 和 34 的两条简单移动平均线。
//+------------------------------------------------------------------+ //| ModelEurusdD1_30Class.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.30.class.onnx" as uchar model_eurusd_D1_30_class[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_30Class : public CModelSymbolPeriod { private: int m_sample_size; int m_fast_period; int m_slow_period; int m_sma_fast; int m_sma_slow; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_30Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1) { m_sample_size=30; m_fast_period=21; m_slow_period=34; m_sma_fast=INVALID_HANDLE; m_sma_slow=INVALID_HANDLE; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_30_class)) { Print("model_eurusd_D1_30_class : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (Close, MA fast, MA slow) const long input_shape[] = {1,m_sample_size,3}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_30_class : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of classes (up, same or down) const long output_shape[] = {1,3}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_30_class : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- indicators m_sma_fast=iMA(m_symbol,m_period,m_fast_period,0,MODE_SMA,PRICE_CLOSE); m_sma_slow=iMA(m_symbol,m_period,m_slow_period,0,MODE_SMA,PRICE_CLOSE); if(m_sma_fast==INVALID_HANDLE || m_sma_slow==INVALID_HANDLE) { Print("model_eurusd_D1_30_class : cannot create indicator"); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict class | //+------------------------------------------------------------------+ virtual int PredictClass(void) { static matrixf input_data(m_sample_size,3); // matrix for prepared input data static vectorf output_data(3); // vector to get result static matrix x_norm(m_sample_size,3); // matrix for prices normalize static vector vtemp(m_sample_size); static double ma_buffer[]; //--- request last bars if(!vtemp.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,1,m_sample_size)) return(-1); //--- get series Mean double m=vtemp.Mean(); //--- get series Std double s=vtemp.Std(); //--- normalize vtemp-=m; vtemp/=s; x_norm.Col(vtemp,0); //--- fast sma if(CopyBuffer(m_sma_fast,0,1,m_sample_size,ma_buffer)!=m_sample_size) return(-1); vtemp.Assign(ma_buffer); m=vtemp.Mean(); s=vtemp.Std(); vtemp-=m; vtemp/=s; x_norm.Col(vtemp,1); //--- slow sma if(CopyBuffer(m_sma_slow,0,1,m_sample_size,ma_buffer)!=m_sample_size) return(-1); vtemp.Assign(ma_buffer); m=vtemp.Mean(); s=vtemp.Std(); vtemp-=m; vtemp/=s; x_norm.Col(vtemp,2); //--- run the inference input_data.Assign(x_norm); if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(-1); //--- evaluate prediction return(int(output_data.ArgMax())); } }; //+------------------------------------------------------------------+
如同前一个类,在 Init 方法中调用 CheckInit 基类方法。 在基类方法中,为 ONNX 模型创建一个会话,并显式设置输入和输出张量的大小。
PredictClass 方法提供 30 个先前的收盘价序列,和计算出的移动平均线。 数据的常规化方式与训练时相同。
我们来看看这个模型是如何工作的。 为此,我们只需修改测试 EA 的两行代码。
#include "ModelEurusdD1_30Class.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position CModelEurusdD1_30Class ExtModel; CTrade ExtTrade;
测试参数相同。
我们看到该模型是有效的。
6. 第三个模型类
最后一个模型称为 model.eurusd.D1.63.class.onnx。 分类模型训练基于 EURUSD D1 的 63 个收盘价序列。
//+------------------------------------------------------------------+ //| ModelEurusdD1_63.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "ModelSymbolPeriod.mqh" #resource "Python/model.eurusd.D1.63.class.onnx" as uchar model_eurusd_D1_63_class[] //+------------------------------------------------------------------+ //| ONNX-model wrapper class | //+------------------------------------------------------------------+ class CModelEurusdD1_63Class : public CModelSymbolPeriod { private: int m_sample_size; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CModelEurusdD1_63Class(void) : CModelSymbolPeriod("EURUSD",PERIOD_D1,0.0001) { m_sample_size=63; } //+------------------------------------------------------------------+ //| ONNX-model initialization | //+------------------------------------------------------------------+ virtual bool Init(const string symbol, const ENUM_TIMEFRAMES period) { //--- check symbol, period, create model if(!CModelSymbolPeriod::CheckInit(symbol,period,model_eurusd_D1_63_class)) { Print("model_eurusd_D1_63_class : initialization error"); return(false); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size const long input_shape[] = {1,m_sample_size}; if(!OnnxSetInputShape(m_handle,0,input_shape)) { Print("model_eurusd_D1_63_class : OnnxSetInputShape error ",GetLastError()); return(false); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of classes (up, same or down) const long output_shape[] = {1,3}; if(!OnnxSetOutputShape(m_handle,0,output_shape)) { Print("model_eurusd_D1_63_class : OnnxSetOutputShape error ",GetLastError()); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+ //| Predict class | //+------------------------------------------------------------------+ virtual int PredictClass(void) { static vectorf input_data(m_sample_size); // vector for prepared input data static vectorf output_data(3); // vector to get result //--- request last bars if(!input_data.CopyRates(m_symbol,m_period,COPY_RATES_CLOSE,1,m_sample_size)) return(-1); //--- get series Mean float m=input_data.Mean(); //--- get series Std float s=input_data.Std(); //--- normalize prices input_data-=m; input_data/=s; //--- run the inference if(!OnnxRun(m_handle,ONNX_NO_CONVERSION,input_data,output_data)) return(-1); //--- evaluate prediction return(int(output_data.ArgMax())); } }; //+------------------------------------------------------------------+
这是三者中最简单的模型。 这就是 PredictClass 方法的代码为何如此紧凑的原因。
我们再次更改 EA 中的两行代码。
#include "ModelEurusdD1_63Class.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position CModelEurusdD1_63Class ExtModel; CTrade ExtTrade;
采用相同的设置启动测试。
该模型有效。
7. 在一个 EA 中收集所有模型。 硬投票
这三种模型都展现出它们的工作能力。 现在我们试着把它们的效能结合起来。 我们安排一个模型投票。
前向声明和定义
#include "ModelEurusdD1_10Class.mqh" #include "ModelEurusdD1_30Class.mqh" #include "ModelEurusdD1_63Class.mqh" #include <Trade\Trade.mqh> input double InpLots = 1.0; // Lots amount to open position CModelSymbolPeriod *ExtModels[3]; CTrade ExtTrade;
OnInit 函数
int OnInit() { ExtModels[0]=new CModelEurusdD1_10Class; ExtModels[1]=new CModelEurusdD1_30Class; ExtModels[2]=new CModelEurusdD1_63Class; for(long i=0; i<ExtModels.Size(); i++) if(!ExtModels[i].Init(_Symbol,_Period)) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); }
OnTick 函数
void OnTick() { for(long i=0; i<ExtModels.Size(); i++) if(!ExtModels[i].CheckOnTick()) return; //--- predict next price movement int returned[3]={0,0,0}; //--- collect returned classes for(long i=0; i<ExtModels.Size(); i++) { int pred=ExtModels[i].PredictClass(); if(pred>=0) returned[pred]++; } //--- get one prediction for all models int predicted_class=-1; //--- count votes for predictions for(int n=0; n<3; n++) { if(returned[n]>=2) { predicted_class=n; break; } } //--- check trading according to prediction if(predicted_class>=0) if(PositionSelect(_Symbol)) CheckForClose(predicted_class); else CheckForOpen(predicted_class); }
多数票的计算根据等式 <总票数>/2 + 1。 总共 3 票,多数票为 2 票。 这就是所谓的“硬投票”。
测试结果仍采用相同的设置。
我们分别回顾一下所有三个模型的工作,即盈利和无盈利交易的数量。 第一个模型 — 11:3;第二个模型 — 6:1;第三个模型 — 16:10。
看来,在硬投票的帮助下,我们改善了结果 — 16:4。 但是,当然,我们需要查看完整的报告和测试图表。
8. 软投票
软投票与硬投票的不同之处在于,其中参考的不是得票数,而是所有三个模型中所有三个类的概率之和。 该类是以最高概率选择的。
为了确保软投票,需要进行一些修改。
基类中:
//+------------------------------------------------------------------+ //| Predict class (regression -> classification) | //+------------------------------------------------------------------+ virtual int PredictClass(vector& probabilities) { ... //--- set predicted probability as 1.0 probabilities.Fill(0); if(predicted_class<(int)probabilities.Size()) probabilities[predicted_class]=1; //--- and return predicted class return(predicted_class); }
在衍生类中:
//+------------------------------------------------------------------+ //| Predict class | //+------------------------------------------------------------------+ virtual int PredictClass(vector& probabilities) { ... //--- evaluate prediction probabilities.Assign(output_data); return(int(output_data.ArgMax())); }
在 EA 中:
#include "ModelEurusdD1_10Class.mqh" #include "ModelEurusdD1_30Class.mqh" #include "ModelEurusdD1_63Class.mqh" #include <Trade\Trade.mqh> enum EnVotes { Two=2, // Two votes Three=3, // Three votes Soft=4 // Soft voting }; input double InpLots = 1.0; // Lots amount to open position input EnVotes InpVotes = Two; // Votes to make trade decision CModelSymbolPeriod *ExtModels[3]; CTrade ExtTrade;
void OnTick() { for(long i=0; i<ExtModels.Size(); i++) if(!ExtModels[i].CheckOnTick()) return; //--- predict next price movement int returned[3]={0,0,0}; vector soft=vector::Zeros(3); //--- collect returned classes for(long i=0; i<ExtModels.Size(); i++) { vector prob(3); int pred=ExtModels[i].PredictClass(prob); if(pred>=0) { returned[pred]++; soft+=prob; } } //--- get one prediction for all models int predicted_class=-1; //--- soft or hard voting if(InpVotes==Soft) predicted_class=(int)soft.ArgMax(); else { //--- count votes for predictions for(int n=0; n<3; n++) { if(returned[n]>=InpVotes) { predicted_class=n; break; } } } //--- check trading according to prediction if(predicted_class>=0) if(PositionSelect(_Symbol)) CheckForClose(predicted_class); else CheckForOpen(predicted_class); }
测试设置是相同的。 在输入中,选择 ”Soft“。
结果如下。
可盈利交易 — 15,无盈利交易 — 3。 在资金方面,硬投票也比软投票更好。
我们看一下一致投票的结果,即全票为 3 的投票数。
非常保守的交易。 唯一无盈利交易在测试结束时被平仓(也许,它并非无利可图)。
重要提示:请注意,本文中所用模型仅演示如何以 MQL5 语言运用 ONNX 模型。 智能交易系统不适用于真实账户交易。
结束语
在本文中,我们展示了面向对象编程如何令编写程序变得更加容易。 模型的所有复杂性都隐藏在它们的类中(模型可能比我们作为示例提供的模型复杂得多)。 其余的“复杂性”适合 OnTick 函数的 45 行代码。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/12484