MQL5运行TensorFlow训练出来的模型

Title MQL5运行TensorFlow训练出来的模型
Framework MQL5
User wy8817399@vip.qq.com
Id 64
Created 3/6/26, 7:47 PM
Modified 3/6/26, 7:47 PM
Published Yes
Content

1、本篇笔记也是在MQL5网上看到大佬的笔记作的修改
2、利用Tensorflow训练,MQL5运行。
3、模型训练的方式,已经在另一篇文章记录

//+------------------------------------------------------------------+
//|                                                   TimelessAI.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Timeless"
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\Trade.mqh>

input double InpLots = 1.0;    // Lots amount to open position
input bool   InpUseStops   = true;   // Use stops in trading
input int    InpTakeProfit = 500;    // TakeProfit level
input int    InpStopLoss   = 500;    // StopLoss level

#resource "model.eurusd.H1.120.onnx" as uchar ExtModel[]

//SAMPLE_SIZE: 定义每次预测使用的历史数据的大小(120个时间点)。
#define SAMPLE_SIZE 120

//ExtHandle: 保存ONNX模型的句柄。
//ExtPredictedClass: 保存模型预测的结果(价格上升、持平或下降)。
//ExtNextBar、ExtNextDay: 用于管理图表的下一根K线和日期。
//ExtMin、ExtMax: 保存价格的最小值和最大值,用于归一化。
//ExtTrade: 用于交易操作的CTrade对象。
long     ExtHandle = INVALID_HANDLE;
int      ExtPredictedClass = -1;
datetime ExtNextBar = 0;
datetime ExtNextDay = 0;
float    ExtMin = 0.0;
float    ExtMax = 0.0;
CTrade   ExtTrade;


//--- price movement prediction
#define PRICE_UP   0
#define PRICE_SAME 1
#define PRICE_DOWN 2

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
//---
    if(_Symbol != "EURUSD" || _Period != PERIOD_H1) {
        Print("model must work with EURUSD,H1");
        return(INIT_FAILED);
    }

//--- 加载ONNX模型并获取句柄ExtHandle
    ExtHandle = OnnxCreateFromBuffer(ExtModel, ONNX_DEFAULT);
    if(ExtHandle == INVALID_HANDLE) {
        Print("OnnxCreateFromBuffer error ", GetLastError());
        return(INIT_FAILED);
    }

// 由于并非输入张量中定义的所有大小,我们必须显式设置它们
// 第一个索引-批量大小,第二个索引-系列大小,第三个索引-序列号(仅关闭)
// 设置ONNX模型的输入和输出维度。输入维度为{1, SAMPLE_SIZE, 1},
// 表示批量大小为1,输入时间序列长度为120,特征数为1(收盘价)。
    const long input_shape[] = {1, SAMPLE_SIZE, 1};
    if(!OnnxSetInputShape(ExtHandle, ONNX_DEFAULT, input_shape)) {
        Print("OnnxSetInputShape error ", GetLastError());
        return(INIT_FAILED);
    }

//---由于并非输出张量中定义的所有大小,我们必须显式设置它们
//---第一个索引-批大小,必须与输入张量的批大小匹配
//---第二个指数-预测价格的数量(我们只预测收盘价)
//输出维度为{1, 1},表示模型输出1个预测值。
    const long output_shape[] = {1, 1};
    if(!OnnxSetOutputShape(ExtHandle, 0, output_shape)) {
        Print("OnnxSetOutputShape error ", GetLastError());
        return(INIT_FAILED);
    }
//---
    return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
//---

}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
	// 返回当前服务器的时间(以秒为单位),
	//ExtNextDay 是存储下一个新的一天的时间戳(通常基于午夜)。
	//这行代码判断当前时间是否超过或等于下一天的时间点。
    if(TimeCurrent() >= ExtNextDay) {
        GetMinMax();
        //--- set next day time
        ExtNextDay = TimeCurrent();
        ExtNextDay -= ExtNextDay % PeriodSeconds(PERIOD_D1);
        ExtNextDay += PeriodSeconds(PERIOD_D1);
    }

//--- check new bar
    if(TimeCurrent() < ExtNextBar)
        return;
        
	//如果当前时间已经超过了 ExtNextBar,则更新 ExtNextBar 为下一个 K 线的时间:
	//PeriodSeconds() 获取当前图表周期的秒数(例如,PeriodSeconds() 如果是 M1 则返回 60 秒,M5 则返回 300 秒等)。
	//通过对当前时间取模并加上周期秒数,调整 ExtNextBar 为下一个完整的 K 线开始时间。
    ExtNextBar = TimeCurrent();
    ExtNextBar -= ExtNextBar % PeriodSeconds();
    ExtNextBar += PeriodSeconds();


    float close = (float)iClose(_Symbol, _Period, 0);
//    如果 ExtMin(当天最低价)大于当前的收盘价 close,则更新 ExtMin 为当前的 close。这行代码在每次 OnTick 被调用时检查并更新当天的最低价
    if(ExtMin > close)
        ExtMin = close;
    if(ExtMax < close)
        ExtMax = close;

//--- predict next price
    PredictPrice();
//--- check trading according to prediction
    if(ExtPredictedClass >= 0)
        if(PositionSelect(_Symbol))
            CheckForClose();
        else
            CheckForOpen();
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get minimal and maximal Close for last 120 days                  |
//+------------------------------------------------------------------+

//该函数获取过去120天(SAMPLE_SIZE)的日线收盘价,并计算其中的最小值和最大值,用于后续的归一化处理。
void GetMinMax(void) {
    //这里定义了一个名为 close 的变量,类型为 vectorf。
    //vectorf 是 MQL5 中的一个数据结构,用来存储浮动的数据序列,在这里用于存储一段时间内的收盘价数据。
    //vectorf 是一个一维数组,通常用于存储单一类型的数值数据,如收盘价、开盘价等。
    //MqlRates 是一个结构体,表示单根K线的数据,包含了开盘价、最高价、最低价、收盘价等多个字段。
    vectorf close;
    //获取最120根K线的收盘价
    close.CopyRates(_Symbol, PERIOD_D1, COPY_RATES_CLOSE, 0, SAMPLE_SIZE);
    //将过去120根K线的收盘价的最低价放在ExtMin里
    ExtMin = close.Min();
    //将过去120根K线的收盘价的最高价放在ExtMax里
    ExtMax = close.Max();
}

//+------------------------------------------------------------------+
//| Predict next price                                               |
//+------------------------------------------------------------------+
//该函数用于获取预测的价格方向:
//	首先从图表获取过去120个时间点的收盘价,并进行归一化处理(使用最小值和最大值)。
//	然后将归一化的价格数据输入到ONNX模型中,运行推理得到预测值。
//	根据预测值与当前收盘价的差异,确定预测的价格趋势:
//	PRICE_SAME:表示预测价格与当前价格相同。
//	PRICE_UP:表示预测价格上涨。
//	PRICE_DOWN:表示预测价格下跌。
void PredictPrice(void) {
    //这行代码定义了一个静态的 vectorf 类型变量 output_data,大小为 1。vectorf 用于存储浮动类型的数值数据,
    //这里用来存储预测的结果。
    static vectorf output_data(1);            // vector to get result
    //这行定义了另一个静态的 vectorf 类型变量 x_norm,大小为 SAMPLE_SIZE。
    //它用于存储经过归一化处理的价格数据。
    static vectorf x_norm(SAMPLE_SIZE);       // vector for prices normalize

    //检查是否可以进行归一化。如果 ExtMin (最小值)大于或等于 ExtMax (最大值),则无法归一化。
    //在这种情况下,ExtPredictedClass 被设置为 -1(表示错误状态),并且函数直接返回,不进行进一步操作。
    if(ExtMin >= ExtMax) {
        ExtPredictedClass = -1;
        return;
    }

    //使用 CopyRates 函数从图表请求历史数据。具体参数:
    //_Symbol 表示当前交易品种(例如 EURUSD)。
    //_Period 表示时间周期(例如 D1)。
    //COPY_RATES_CLOSE 表示只复制收盘价数据。
    //1 表示从最近的时间点开始请求数据。
    //SAMPLE_SIZE 表示请求的数据条数。
    //如果请求失败,ExtPredictedClass 被设置为 -1,表示发生错误,然后函数返回。
    if(!x_norm.CopyRates(_Symbol, _Period, COPY_RATES_CLOSE, 1, SAMPLE_SIZE)) {
        ExtPredictedClass = -1;
        return;
    }
    //---
    //该行获取最后一根 K 线的收盘价。因为数据被存储在 x_norm 中,x_norm[SAMPLE_SIZE - 1] 表示数组中的最后一个元素,即最近的收盘价。
    float last_close = x_norm[SAMPLE_SIZE - 1];

    //这两行代码将价格数据进行归一化处理:
    //x_norm -= ExtMin;:将数据中的每个元素减去最小值 ExtMin,以确保数据从 0 开始。
    //x_norm /= (ExtMax - ExtMin);:然后将数据除以 (ExtMax - ExtMin),使数据范围变为 0 到 1。这样就完成了归一化。
    x_norm -= ExtMin;
    x_norm /= (ExtMax - ExtMin);
    
	//OnnxRun 是一个函数,用于通过 ONNX 模型进行推理。具体参数:
	    //ExtHandle 是 ONNX 模型的句柄。
	    //ONNX_NO_CONVERSION 表示不进行数据类型转换。
	    //x_norm 是归一化后的价格数据,作为输入传入。
	    //output_data 用于存储推理的输出结果。
	    //如果推理失败,ExtPredictedClass 被设置为 -1,表示错误状态,函数直接返回。
    if(!OnnxRun(ExtHandle, ONNX_NO_CONVERSION, x_norm, output_data)) {
        ExtPredictedClass = -1;
        return;
    }
	
	//这行代码将预测的输出值从 0 到 1 的范围反归一化到实际价格范围。
	//	output_data[0] 是 ONNX 模型输出的归一化预测结果。
	//	(ExtMax - ExtMin) 将归一化结果还原为实际的价格范围。
	//	+ ExtMin 是为了将结果偏移回到正确的价格区间。
    float predicted = output_data[0] * (ExtMax - ExtMin) + ExtMin;
    
	//计算预测的价格(predicted)与最后的收盘价(last_close)之间的差异delta。
	//如果差异为负,表示预测价格低于当前收盘价;如果差异为正,表示预测价格高于当前收盘价。
    float delta = last_close - predicted;
    
    //如果 delta 的绝对值非常小(即价格变化几乎为 0),那么预测的价格与当前收盘价几乎相同。
    //此时,ExtPredictedClass 被设置为 PRICE_SAME,表示价格保持不变。
    if(fabs(delta) <= 0.003)
        ExtPredictedClass = PRICE_SAME;
    else {
        if(delta < 0)
            ExtPredictedClass = PRICE_UP;
        else
            ExtPredictedClass = PRICE_DOWN;
    }
}
//+------------------------------------------------------------------+
//| Check for open position conditions                               |
//+------------------------------------------------------------------+
//根据预测的价格趋势(上涨或下跌),判断建仓信号:
//如果预测价格上涨(PRICE_UP),则选择买入。
//如果预测价格下跌(PRICE_DOWN),则选择卖出。
//根据输入参数设置止损和止盈,并在允许交易的情况下建仓。
void CheckForOpen(void) {
	//定义一个变量 signal,类型为 ENUM_ORDER_TYPE,用于表示订单类型(买单或卖单)。初始化为 WRONG_VALUE,表示没有有效的信号。
    ENUM_ORDER_TYPE signal = WRONG_VALUE;
//--- check signals
    if(ExtPredictedClass == PRICE_DOWN)
        signal = ORDER_TYPE_SELL;  // sell condition
    else {
        if(ExtPredictedClass == PRICE_UP)
            signal = ORDER_TYPE_BUY; // buy condition
    }

	//检查 signal 是否有效(即不等于 WRONG_VALUE),并且检查终端是否允许交易(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 返回是否可以进行交易)。
    if(signal != WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) {
        double price, sl = 0, tp = 0;
        double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        if(signal == ORDER_TYPE_SELL) {
            price = bid;
            
//           如果启用了止损止盈(InpUseStops),则根据当前卖价 (bid) 和止损、止盈的参数进行计算:
//				sl = NormalizeDouble(bid + InpStopLoss * _Point, _Digits);:计算止损价格(卖价加上止损距离),并将其规范化为适合当前交易品种的价格精度。
//				tp = NormalizeDouble(ask - InpTakeProfit * _Point, _Digits);:计算止盈价格(买价减去止盈距离),并规范化为适合的精度。
            if(InpUseStops) {
                sl = NormalizeDouble(bid + InpStopLoss * _Point, _Digits);
                tp = NormalizeDouble(ask - InpTakeProfit * _Point, _Digits);
            }
        } else {
            price = ask;
            if(InpUseStops) {
                sl = NormalizeDouble(ask - InpStopLoss * _Point, _Digits);
                tp = NormalizeDouble(bid + InpTakeProfit * _Point, _Digits);
            }
        }
        ExtTrade.PositionOpen(_Symbol, signal, InpLots, price, sl, tp);
    }
}
//+------------------------------------------------------------------+
//| 平仓条件判断            										   |
//|		如果设置了止损和止盈,函数会直接返回。						  |
//|		如果没有设置止损和止盈,检查当前持仓的类型:                 |
//|		如果是买入仓位且预测价格下跌,则平仓并反向开仓。			 |
//|		如果是卖出仓位且预测价格上涨,则平仓并反向开仓。             |
//+------------------------------------------------------------------+
void CheckForClose(void) {
//这行代码检查是否启用了止损(InpUseStops)。
//如果启用了止损,函数将直接返回,不会继续执行其他的平仓操作。这意味着止损机制优先于其他信号。
    if(InpUseStops)
        return;
	
	//定义一个布尔变量 bsignal,初始化为 false。这个变量用来表示是否存在需要平仓的信号。
    bool bsignal = false;
	
	//使用 PositionGetInteger(POSITION_TYPE) 获取当前持仓的类型,返回值存储在 type 变量中:
	//	POSITION_TYPE_BUY 表示当前持仓为买单。
	//	POSITION_TYPE_SELL 表示当前持仓为卖单。
    long type = PositionGetInteger(POSITION_TYPE);
    
	//如果当前持仓为买单 (POSITION_TYPE_BUY),并且 ExtPredictedClass 为 PRICE_DOWN(即预测价格将下跌),则将 bsignal 设置为 true,表示需要平仓。
    if(type == POSITION_TYPE_BUY && ExtPredictedClass == PRICE_DOWN)
        bsignal = true;
     
	//如果当前持仓为卖单 (POSITION_TYPE_SELL),并且 ExtPredictedClass 为 PRICE_UP(即预测价格将上涨),则将 bsignal 设置为 true,表示需要平仓
    if(type == POSITION_TYPE_SELL && ExtPredictedClass == PRICE_UP)
        bsignal = true;
        
    

	//如果 bsignal 为 true(即需要平仓)并且终端允许交易(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 返回 true),则执行平仓操作。
    if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) {
    	//调用 ExtTrade.PositionClose 函数平掉当前持仓,_Symbol 表示当前的交易品种,3 表示平仓的魔术数字,通常用于标识特定的订单。
        ExtTrade.PositionClose(_Symbol, 3);
        //--- open opposite
        CheckForOpen();
    }
}
//+------------------------------------------------------------------+