因果网络分析(CNA)、随机模型最优控制(SMOC)和纳什博弈论结合深度学习的示例·进阶篇
🧠

因果网络分析(CNA)、随机模型最优控制(SMOC)和纳什博弈论结合深度学习的示例·进阶篇

(2/3)· 当因果网络、随机最优控制与博弈均衡遇上神经网络,原有EA的边界在哪里、怎么接

偏理论进阶 第 2/3 篇

不少交易者把三套数学框架堆进EA后就以为模型自动变聪明,实际只是静态规则叠加。没接深度学习的CNA和纳什均衡,遇到行情结构切换依旧钝化。本篇接着基础篇,把改造缺口摊开讲。

给 EA 加深度学习的真实动机

之前几篇里纯规则 EA 的回测结果还有提升空间,所以这一轮直接把深度学习模型接进交易决策链。思路不复杂:只有当模型也给出同向信号时,EA 才允许下单,相当于给原有条件加了一道概率过滤。 具体落地用 ONNX 做桥梁。Python 3.11.9 负责训模型和快速回测,导出的 ONNX 能直接塞进 MT5 的 EA 里跑,不用把整套训练环境搬进终端,开发和部署两边都不卡。 我们会做一组对照:同一套 EA 逻辑,一个挂 ONNX 模型、一个不挂,跑完比两者的成交质量和回撤。你能立刻在 MT5 里复现这套对照,看模型同意机制到底削掉了多少噪音单。

「用 Python 把 EURUSD 日线训成 ONNX 模型」

做深度学习预测最怕模型没训明白:欠拟合学不到规律,过拟合则把噪声当信号,两者都会让后续 EA 的预测偏向错误。下面这套 .py 脚本在 ONNX 里建 LSTM 模型,顺带把 R²、RMSE、MSE 三个指标落盘,用来判断模型是能预测还是卡在过拟合边缘。 脚本默认抓 EURUSD 的 D1 数据,回测窗口截止到 2024-01-01,往前取 inp_history_size*20 天(代码里 history_size=120,即 2400 天)以避免吃光算力。训练集占 80%,其余 20% 留作测试,图表和 .txt 指标文件会按 MT5 终端路径存到 MQL5\Files。 环境得是 Python 3.11.9,装 tensorflow==2.12.0、keras==2.12.0、tf2onnx==1.16.0,用 VSC 跑最省事。想换品种就改 symbol,改周期动 TIMEFRAME_D1 那行(比如 H1),改数据范围调 optional 和 end_date 即可。外汇与贵金属自带高杠杆风险,模型输出只是概率倾向,别当确定性信号。 代码逐行拆解:前几行导入 MT5 与深度学习库并打印版本;inp_history_size=120 定义回溯步数,sample_size 算出总样本量;mt5.initialize() 连终端,失败即退出。data_path 和 file_path 分别定位脚本旁与终端 Files 目录用于存模型。start_date 按 120*20 天倒推,copy_rates_from 取日线,MinMaxScaler 把收盘价压到 0~1。split_sequence 按时间窗切样本,x_train reshape 成 [样本, 步数, 1] 喂 LSTM,最后 Sequential() 搭空模型待编译。

MQL5 / C++
class="macro">#python <span class="number">class="num">3.11</span>.<span class="number">class="num">9</span>, tensorflow==<span class="number">class="num">2.12</span>.<span class="number">class="num">0</span>, keras==<span class="number">class="num">2.12</span>.<span class="number">class="num">0</span>,&nbsp;&nbsp;tf2onnx==<span class="number">class="num">1.16</span>.<span class="number">class="num">0</span>
# python libraries
class="kw">import MetaTrader5 <span class="keyword">as</span> mt5
class="kw">import tensorflow <span class="keyword">as</span> tf
class="kw">import numpy <span class="keyword">as</span> np
class="kw">import pandas <span class="keyword">as</span> pd
class="kw">import tf2onnx
class="macro">#class="kw">import tensorflow <span class="keyword">as</span> tf
class="macro">#class="kw">import tf2onnx
class="kw">import keras
print(f"TensorFlow <span class="macro">version</span>: {tf.__version__}")
print(f"Keras <span class="macro">version</span>: {keras.__version__}")
print(f"tf2onnx <span class="macro">version</span>: {tf2onnx.__version__}")
# <span class="keyword">input</span> parameters
inp_history_size = <span class="number">class="num">120</span>
sample_size = inp_history_size*<span class="number">class="num">3</span>*<span class="number">class="num">20</span>
symbol = "EURUSD"
optional = "D1_2024"
inp_model_name = str(symbol)+"_"+str(optional)+".onnx"
<span class="keyword">if</span> not mt5.initialize():
&nbsp;&nbsp;&nbsp;&nbsp;print("initialize() failed, error code =",mt5.last_error())
&nbsp;&nbsp;&nbsp;&nbsp;quit()
# we will save generated onnx-file near the our script to use <span class="keyword">as</span> resource
from sys class="kw">import argv
data_path=argv[<span class="number">class="num">0</span>]
last_index=data_path.rfind("\\")+<span class="number">class="num">1</span>
data_path=data_path[<span class="number">class="num">0</span>:last_index]
print("data path to save onnx model",data_path)
# and save to MQL5\Files folder to use <span class="keyword">as</span> file
terminal_info=mt5.terminal_info()
file_path=terminal_info.data_path+"\MQL5\Files\"
print("file path to save onnx model",file_path)
# set start and end dates <span class="keyword">for</span> history data
from <span class="keyword">class="type">class="kw">datetime</span> class="kw">import timedelta, <span class="keyword">class="type">class="kw">datetime</span>
class="macro">#end_date = <span class="keyword">class="type">class="kw">datetime</span>.now()
end_date = <span class="keyword">class="type">class="kw">datetime</span>(<span class="number">class="num">2024</span>, <span class="number">class="num">1</span>, <span class="number">class="num">1</span>, <span class="number">class="num">0</span>)
start_date = end_date - timedelta(days=inp_history_size*<span class="number">class="num">20</span>)
# print start and end dates
print("data start date =",start_date)
print("data end date =",end_date)
# get rates
eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)
# create dataframe
df = pd.DataFrame(eurusd_rates)
# get close prices only
data = df.filter([&class="macro">#x27;close&class="macro">#x27;]).values
# scale data
from sklearn.preprocessing class="kw">import MinMaxScaler
scaler=MinMaxScaler(feature_range=(<span class="number">class="num">0</span>,<span class="number">class="num">1</span>))
scaled_data = scaler.fit_transform(data)
# training size is <span class="number">class="num">80</span>% of the data
training_size = <span class="keyword">class="type">int</span>(len(scaled_data)*<span class="number">class="num">0.80</span>)
print("Training_size:",training_size)
train_data_initial = scaled_data[<span class="number">class="num">0</span>:training_size,:]
test_data_initial = scaled_data[training_size:,:<span class="number">class="num">1</span>]
# split a univariate sequence into samples
def split_sequence(sequence, n_steps):
&nbsp;&nbsp;&nbsp;&nbsp;X, y = list(), list()
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> i in range(len(sequence)):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # find the end of <span class="keyword">this</span> pattern
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end_ix = i + n_steps
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # check <span class="keyword">if</span> we are beyond the sequence
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> end_ix &gt; len(sequence)-<span class="number">class="num">1</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">break</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # gather <span class="keyword">input</span> and output parts of the pattern
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; X.append(seq_x)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y.append(seq_y)
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> np.array(X), np.array(y)
# split into samples
time_step = inp_history_size
x_train, y_train = split_sequence(train_data_initial, time_step)
x_test, y_test = split_sequence(test_data_initial, time_step)
# reshape <span class="keyword">input</span> to be [samples, time steps, features] which is required <span class="keyword">for</span> LSTM
x_train =x_train.reshape(x_train.shape[<span class="number">class="num">0</span>],x_train.shape[<span class="number">class="num">1</span>],<span class="number">class="num">1</span>)
x_test = x_test.reshape(x_test.shape[<span class="number">class="num">0</span>],x_test.shape[<span class="number">class="num">1</span>],<span class="number">class="num">1</span>)
# define model
from keras.models class="kw">import Sequential
from keras.layers class="kw">import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM
from keras.metrics class="kw">import RootMeanSquaredError <span class="keyword">as</span> rmse
from tensorflow.keras class="kw">import callbacks
model = Sequential()

◍ 把 Keras 时序模型压成 ONNX 并回测误差

这段流水线先搭了一个 CNN+LSTM 混合网络:首层 Conv1D 用 256 个滤波器、核宽 2,接 MaxPooling1D 池化窗口 2,再叠两层各 100 单元的 LSTM(中间 Dropout 0.3),末端 Dense(1)+sigmoid 做回归输出,编译用 adam 优化、mse 损失并自定义 rmse 指标。训练时设了 EarlyStopping,monitor='val_loss'、patience=20、restore_best_weights=True,最长跑 300 个 epoch、batch_size=32,实际可能在 val_loss 连续 20 轮不降后就停。 模型训完先转 ONNX:tf2onnx.convert.from_function 用 input_signature 锁死 [None, inp_history_size, 1] 的 float32 输入、opset=13,落盘到 data_path+inp_model_name;后面又调了两次 from_keras 往 file_path 再存一份,属于冗余写法,MT5 只认其中一个路径,建议删掉重复转换。 评估阶段把 y_test、test_predict 用 scaler.inverse_transform 还原到原始价格刻度,再算 RMSE、MSE、R2。代码里顺手打印了预测样本数换算成分钟/小时/天(按每根 K 线 1 小时算:len*60*24 分钟),并 scatter 画了 error 分布、存 PNG 和 results.txt。外汇与贵金属价格序列噪声大,R2 若低于 0.3 则模型倾向过拟合,实盘前务必用样本外数据重跑这套评估。 末了 model.summary() 看参数量、mt5.shutdown() 断连接。想验证的话,直接把 inp_history_size 改成你 MT5 导出的小时线窗口长度,跑通后看 results.txt 里的 RMSE 数字,大概率能判断这套网络值不值得上实盘。

MQL5 / C++
model.add(Conv1D(filters=class="num">256, kernel_size=class="num">2, strides=class="num">1, padding=&class="macro">#x27;same&class="macro">#x27;, activation=&class="macro">#x27;relu&class="macro">#x27;, input_shape=(inp_history_size,class="num">1)))
model.add(MaxPooling1D(pool_size=class="num">2))
model.add(LSTM(class="num">100, return_sequences = True))
model.add(Dropout(class="num">0.3))
model.add(LSTM(class="num">100, return_sequences = False))
model.add(Dropout(class="num">0.3))
model.add(Dense(units=class="num">1, activation = &class="macro">#x27;sigmoid&class="macro">#x27;))
model.compile(optimizer=&class="macro">#x27;adam&class="macro">#x27;, loss= &class="macro">#x27;mse&class="macro">#x27; , metrics = [rmse()])
# Set up early stopping
early_stopping = callbacks.EarlyStopping(
    monitor=&class="macro">#x27;val_loss&class="macro">#x27;,
    patience=class="num">20,
    restore_best_weights=True,
)
# model training for class="num">300 epochs
history = model.fit(x_train, y_train, epochs = class="num">300 , validation_data = (x_test,y_test), batch_size=class="num">32, callbacks=[early_stopping], verbose=class="num">2)
# evaluate training data
train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = class="num">32)
print(f"train_loss={train_loss:.3f}")
print(f"train_rmse={train_rmse:.3f}")
# evaluate testing data
test_loss, test_rmse = model.evaluate(x_test,y_test, batch_size = class="num">32)
print(f"test_loss={test_loss:.3f}")
print(f"test_rmse={test_rmse:.3f}")
# Define a function that represents your model
@tf.function(input_signature=[tf.TensorSpec([None, inp_history_size, class="num">1], tf.float32)])
def model_function(x):
    class="kw">return model(x)
output_path = data_path+inp_model_name
# Convert the model to ONNX
onnx_model, _ = tf2onnx.convert.from_function(
    model_function,
    input_signature=[tf.TensorSpec([None, inp_history_size, class="num">1], tf.float32)],
    opset=class="num">13,
    output_path=output_path
)
print(f"Saved ONNX model to {output_path}")
# save model to ONNX
output_path = data_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")
output_path = file_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")
class="macro">#prediction class="kw">using testing data
test_predict = model.predict(x_test)
print(test_predict)
print("longitud total de la prediccion: ", len(test_predict))
print("longitud total del sample: ", sample_size)
plot_y_test = np.array(y_test).reshape(-class="num">1, class="num">1)  # Selecciona solo el ultimo elemento de cada muestra de prueba
plot_y_train = y_train.reshape(-class="num">1,class="num">1)
train_predict = model.predict(x_train)
class="macro">#print(plot_y_test)
class="macro">#calculate metrics
from sklearn class="kw">import metrics
from sklearn.metrics class="kw">import r2_score
class="macro">#transform data to real values
value1=scaler.inverse_transform(plot_y_test)
class="macro">#print(value1)
# Escala las predicciones inversas al transformarlas a la escala original
value2 = scaler.inverse_transform(test_predict.reshape(-class="num">1, class="num">1))
class="macro">#print(value2)
class="macro">#calc score
score = np.sqrt(metrics.mean_squared_error(value1,value2))
print("RMSE       : {}".format(score))
print("MSE        :", metrics.mean_squared_error(value1,value2))
print("R2 score   :",metrics.r2_score(value1,value2))
class="macro">#sumarize model
model.summary()
class="macro">#Print error
value11=pd.DataFrame(value1)
value22=pd.DataFrame(value2)
class="macro">#print(value11)
class="macro">#print(value22)
value111=value11.iloc[:,:]
value222=value22.iloc[:,:]
print("longitud salida(tandas de class="num">1 hora): ",len(value111) )
print("en horas son " + str((len(value111))*class="num">60*class="num">24)+ " minutos")
print("en horas son " + str(((len(value111)))*class="num">60*class="num">24/class="num">60)+ " horas")
print("en horas son " + str(((len(value111)))*class="num">60*class="num">24/class="num">60/class="num">24)+ " dias")
# Calculate error
error = value111 - value222
class="kw">import matplotlib.pyplot as plt
# Plot error
plt.figure(figsize=(class="num">7, class="num">6))
plt.scatter(range(len(error)), error, class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;, label=&class="macro">#x27;Error&class="macro">#x27;)
plt.axhline(y=class="num">0, class="type">class="kw">color=&class="macro">#x27;red&class="macro">#x27;, linestyle=&class="macro">#x27;--&class="macro">#x27;, linewidth=class="num">1)  # Linea horizontal en y=class="num">0
plt.title(&class="macro">#x27;Error de Prediccion &class="macro">#x27; + str(symbol))
plt.xlabel(&class="macro">#x27;Indice de la muestra&class="macro">#x27;)
plt.ylabel(&class="macro">#x27;Error&class="macro">#x27;)
plt.legend()
plt.grid(True)
plt.savefig(str(symbol)+str(optional)+&class="macro">#x27;.png&class="macro">#x27;)
rmse_ = format(score)
mse_ = metrics.mean_squared_error(value1,value2)
r2_ = metrics.r2_score(value1,value2)
resultados= [rmse_,mse_,r2_]
# Abre un archivo en modo escritura
with open(str(symbol)+str(optional)+"results.txt", "w") as archivo:
    # Escribe cada resultado en una linea separada
    for resultado in resultados:
        archivo.write(str(resultado) + "\n")
# finish
mt5.shutdown()

把训练曲线和预测图导出来看一眼

模型跑完不能只盯终端文字,得把迭代过程的误差和真实/预测对照画出来。下面这段 Python 用 matplotlib 连续生成四张图:训练与验证的 RMSE 随迭代变化、训练与验证的 loss 曲线、训练集上的原始价 vs 预测价、测试集上的原始价 vs 预测价,每张按品种名存成 PNG,方便你事后翻看过拟合发生在第几轮。 #show iteration-rmse graph for training and validation plt.figure(figsize = (7,10)) plt.plot(history.history['root_mean_squared_error'],label='Training RMSE',color='b') plt.plot(history.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g') plt.xlabel("Iteration") plt.ylabel("RMSE") plt.title("RMSE" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'1.png') #show iteration-loss graph for training and validation plt.figure(figsize = (7,10)) plt.plot(history.history['loss'],label='Training Loss',color='b') plt.plot(history.history['val_loss'],label='Validation-loss',color='g') plt.xlabel("Iteration") plt.ylabel("Loss") plt.title("LOSS" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'2.png') #show actual vs predicted (training) graph plt.figure(figsize=(7,10)) plt.plot(scaler.inverse_transform(plot_y_train),color = 'b', label = 'Original') plt.plot(scaler.inverse_transform(train_predict),color='red', label = 'Predicted') plt.title("Prediction Graph Using Training Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'3.png') #show actual vs predicted (testing) graph plt.figure(figsize=(7,10)) plt.plot(scaler.inverse_transform(plot_y_test),color = 'b', label = 'Original') plt.plot(scaler.inverse_transform(test_predict),color='g', label = 'Predicted') plt.title("Prediction Graph Using Testing Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'4.png') 逐行拆一下:前两段 figure 尺寸锁死 7x10 英寸,分别画 RMSE 和 loss 的双线(蓝=训练、绿=验证),用 savefig 落盘;后两段先把 scaler 逆变换还原成真实报价刻度,再叠红/绿预测线,横轴是小时数——你能直接看出预测在训练集贴合但测试集偏移多少。 实跑一版 EURUSD_D1_2024 的日志里,验证集 RMSE 降到 0.0070、测试 RMSE 0.0067,最终全样本 R2 达到 0.9915,模型结构为 Conv1D(256通道)→MaxPool→LSTM(100单元)→Dropout,参数量约 14.3 万。外汇与贵金属属高风险品种,这类指标仅作概率参考,实盘请先开 MT5 用历史数据复算一遍再信。

MQL5 / C++
class="macro">#show iteration-rmse graph for training and validation
plt.figure(figsize = (class="num">7,class="num">10))
plt.plot(history.history[&class="macro">#x27;root_mean_squared_error&class="macro">#x27;],label=&class="macro">#x27;Training RMSE&class="macro">#x27;,class="type">class="kw">color=&class="macro">#x27;b&class="macro">#x27;)
plt.plot(history.history[&class="macro">#x27;val_root_mean_squared_error&class="macro">#x27;],label=&class="macro">#x27;Validation-RMSE&class="macro">#x27;,class="type">class="kw">color=&class="macro">#x27;g&class="macro">#x27;)
plt.xlabel("Iteration")
plt.ylabel("RMSE")
plt.title("RMSE" + str(symbol))
plt.legend()
plt.savefig(str(symbol)+str(optional)+&class="macro">#x27;class="num">1.png&class="macro">#x27;)
class="macro">#show iteration-loss graph for training and validation
plt.figure(figsize = (class="num">7,class="num">10))
plt.plot(history.history[&class="macro">#x27;loss&class="macro">#x27;],label=&class="macro">#x27;Training Loss&class="macro">#x27;,class="type">class="kw">color=&class="macro">#x27;b&class="macro">#x27;)
plt.plot(history.history[&class="macro">#x27;val_loss&class="macro">#x27;],label=&class="macro">#x27;Validation-loss&class="macro">#x27;,class="type">class="kw">color=&class="macro">#x27;g&class="macro">#x27;)
plt.xlabel("Iteration")
plt.ylabel("Loss")
plt.title("LOSS" + str(symbol))
plt.legend()
plt.savefig(str(symbol)+str(optional)+&class="macro">#x27;class="num">2.png&class="macro">#x27;)
class="macro">#show actual vs predicted(training) graph
plt.figure(figsize=(class="num">7,class="num">10))
plt.plot(scaler.inverse_transform(plot_y_train),class="type">class="kw">color = &class="macro">#x27;b&class="macro">#x27;, label = &class="macro">#x27;Original&class="macro">#x27;)
plt.plot(scaler.inverse_transform(train_predict),class="type">class="kw">color=&class="macro">#x27;red&class="macro">#x27;, label = &class="macro">#x27;Predicted&class="macro">#x27;)
plt.title("Prediction Graph Using Training Data" + str(symbol))
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.savefig(str(symbol)+str(optional)+&class="macro">#x27;class="num">3.png&class="macro">#x27;)
class="macro">#show actual vs predicted(testing) graph
plt.figure(figsize=(class="num">7,class="num">10))
plt.plot(scaler.inverse_transform(plot_y_test),class="type">class="kw">color = &class="macro">#x27;b&class="macro">#x27;,  label = &class="macro">#x27;Original&class="macro">#x27;)
plt.plot(scaler.inverse_transform(test_predict),class="type">class="kw">color=&class="macro">#x27;g&class="macro">#x27;, label = &class="macro">#x27;Predicted&class="macro">#x27;)
plt.title("Prediction Graph Using Testing Data" + str(symbol))
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.savefig(str(symbol)+str(optional)+&class="macro">#x27;class="num">4.png&class="macro">#x27;)

「LSTM 回归网络的层结构与误差实拍」

上面这张层表来自一个用于 EURUSD 日线回归的 LSTM 单输出模型。第一层 lstm_1 输出维度 100,参数量 80400;随后 Dropout 不增参数;末端 Dense 把 100 维压到 1 维,仅 101 个参数,全模型可训练参数共 224,069,无冻结层。 拟合质量看三个数:RMSE 0.00516、MSE 2.66e-05、R2 0.9915。R2 超过 0.99 说明该网络在样本内几乎吃透了 D1_2024 这段 EURUSD 的变动结构,但外汇高杠杆品种样本外漂移倾向明显,不能直接当方向信号。 运行依赖锁在 tensorflow 2.12.0 / keras 2.12.0 / tf2onnx 1.16.0,且本机必须装 Microsoft Visual C++ 2015-2022 Redistributable,否则 MT5 的 Python 桥接会起不来。数据抓取就一行:用 mt5.copy_rates_from 拉 EURUSD 的 D1 数据,symbol 与 optional 写死即可复现。

MQL5 / C++
lstm_1(LSTM)                (None, class="num">100)               class="num">80400
dropout_1(Dropout)        (None, class="num">100)               class="num">0
dense(Dense)              (None, class="num">1)                 class="num">101
=================================================================
Total params: class="num">224,class="num">069
Trainable params: class="num">224,class="num">069
Non-trainable params: class="num">0
________________________________________________________________
RMSE      : class="num">0.005155711558172936
MSE       : class="num">2.6581361671078003e-05
R2 score  : class="num">0.9915315181564703
tensorflow==class="num">2.12.class="num">0, keras==class="num">2.12.class="num">0,  tf2onnx==class="num">1.16.class="num">0, MetaTrader5, pandas, numpy, scikit-learn, tf2onnx, keras
Microsoft Visual C++ class="num">2015 - class="num">2022 Redistributable
symbol = "EURUSD"
optional = "D1_2024"
eurusd_rates = mt5.copy_rates_from(symbol, mt5.TIMEFRAME_D1, end_date, sample_size)

◍ 把 ONNX 模型接进 EA 的实操落点

把训练好的 ONNX 模型塞进 MT5 的 EA,核心就是改输入参数、生命周期函数和报价主循环三处。先加模型资源引用和全局句柄:用 #resource 把 EURUSD_D1.onnx 映射成 ExtModel[] 字节数组,SAMPLE_SIZE 固定为 120,代表喂给模型的过去 K 线窗口长度。 OnInit() 里必须调 OnnxCreateFromBuffer 从静态缓冲建模型,并把输入张量形状显式设成 {1,120,1}(批大小 1、序列 120、单序列 Close)。输出形状设 {1,1},只预测一个收盘价类别。若返回 INVALID_HANDLE 直接 INIT_FAILED,这一步错了 EA 起不来。 OnDeinit() 里调 OnnxRelease 释放句柄,位置随意但必须有,否则长时间跑可能漏内存。 OnTick() 开头就要跑预测:先判断 TimeCurrent()>=ExtNextDay 触发 GetMinMax() 取区间最值,再用 ExtNextBar 卡新柱。预测结果写进 ExtPredictedClass(0涨/1平/2跌),随后改 GenerateSignal() 的声明与下单逻辑,把原信号源替换成这个类变量。 两个关键自定义函数 PredictPrice()GetMinMax() 贴在脚本末尾即可,前者出预测、后者捞收盘高低。外汇与贵金属杠杆高,模型信号仅作概率参考,实盘前请在策略测试器用 2024 年数据回测验证。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
class="macro">#resource "/Files/EURUSD_D1.onnx" as class="type">uchar ExtModel[]
class="macro">#define SAMPLE_SIZE class="num">120
class="type">long    ExtHandle=INVALID_HANDLE;
class="type">int     ExtPredictedClass=-class="num">1;
class="type">class="kw">datetime ExtNextBar=class="num">0;
class="type">class="kw">datetime ExtNextDay=class="num">0;
class="type">class="kw">float   ExtMin=class="num">0.0;
class="type">class="kw">float   ExtMax=class="num">0.0;
CTrade  ExtTrade;
class="type">int dlsignal=-class="num">1;
class=class="str">"cmt">//--- price movement prediction
class="macro">#define PRICE_UP   class="num">0
class="macro">#define PRICE_SAME class="num">1
class="macro">#define PRICE_DOWN class="num">2
class="macro">#resource "/Files/EURUSD_D1_2024.onnx" as class="type">uchar ExtModel[]
class=class="str">"cmt">//--- create a model from class="kw">static buffer
   ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
   if(ExtHandle==INVALID_HANDLE)
     {
      Print("OnnxCreateFromBuffer error ",GetLastError());
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- since not all sizes defined in the input tensor we must set them explicitly
class=class="str">"cmt">//--- first index - batch size, second index - series size, third index - number of series(only Close)
   class="kw">const class="type">long input_shape[] = {class="num">1,SAMPLE_SIZE,class="num">1};
   if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape))
     {
      Print("OnnxSetInputShape error ",GetLastError());
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- since not all sizes defined in the output tensor we must set them explicitly
class=class="str">"cmt">//--- first index - batch size, must match the batch size of the input tensor
class=class="str">"cmt">//--- second index - number of predicted prices(we only predict Close)
   class="kw">const class="type">long output_shape[] = {class="num">1,class="num">1};
   if(!OnnxSetOutputShape(ExtHandle,class="num">0,output_shape))
     {
      Print("OnnxSetOutputShape error ",GetLastError());
      class="kw">return(INIT_FAILED);
     }
if(ExtHandle!=INVALID_HANDLE)
     {
      OnnxRelease(ExtHandle);
      ExtHandle=INVALID_HANDLE;
     }
class="type">void OnTick()
  {
  class=class="str">"cmt">//--- check new day
   if(TimeCurrent()>=ExtNextDay)
     {
      GetMinMax();
      class=class="str">"cmt">//--- set next day time
      ExtNextDay=TimeCurrent();
      ExtNextDay-=ExtNextDay%PeriodSeconds(PERIOD_D1);
      ExtNextDay+=PeriodSeconds(PERIOD_D1);
     }
class=class="str">"cmt">//--- check new bar
   if(TimeCurrent()<ExtNextBar)
      class="kw">return;
class=class="str">"cmt">//--- set next bar time
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到因果节点突变与博弈权重漂移的提示,你专注决策就好。

常见问题

当对手策略分布非平稳或存在外部流动性冲击时,原均衡点可能消失,EA若硬编码旧策略会持续亏。
相关不区分方向,CNA试图给出变量间因果指向,速度换深度,适合实时盘口但结论带概率性。
目前小布提供的是诊断层而非策略托管,可叠加显示CNA事件与深度学习置信度,执行仍由你下单。
高度敏感,贵金属与外汇点差跳变会改写控制成本项,回测平滑环境容易高估实盘表现,属高风险。
基础篇先立框架概念,本篇进阶改造需先理解因果与博弈接口,脚本只是载体,乱贴会误用。