多层感知器和反向传播算法(第 3 部分):与策略测试器集成 - 概述(I)·进阶篇
◍ MT5 与 Python 走 CSV 交换的取舍
在 MT5 和 Python 之间做模型协同,核心矛盾是消息格式。把数据统一成 CSV 落地磁盘,再用一个封装类屏蔽掉字符串拼接,是这套思路里最省事的做法:MQL5 端用矩阵和向量,Python 端用 pandas,两边解析都轻。 下面这个类把写表头和写行的工作接住了,调用时直接丢动态向量或矩阵进去,不用在主逻辑里写 StringAdd() 或 StringConcatenate()。ToString 负责把矩阵转成 CSV 文本,运行时拼文件很干净。 [CODE] class CFileCSV : public CFile { private: template<typename T> string ToString(const int, const T &[][]); template<typename T> string ToString(const T &[]); short m_delimiter; public: CFileCSV(void); ~CFileCSV(void); //--- methods for working with files int Open(const string,const int, const short); template<typename T> uint WriteHeader(const T &values[]); template<typename T> uint WriteLine(const T &values[][]); string Read(void); }; #include "FileCSV.mqh" #define PATH(path) "Test/"+path+".csv" string H[3] = { "data", "close", "val_ma" }; string L[4][3] = {{"10202022", "10.55", "10.49"},{"10212022", "10.95", "11.09"},{"10222022", "11.55", "11.29"},{"10232022", "11.15", "11.29"}}; CFileCSV File; ulong start=0,time=0; void OnStart() { start=0; time=0; start=GetTickCount(); 逐行看:CFileCSV 继承 CFile,私有里两个 ToString 模板分别吃二维和一维数组,m_delimiter 存分隔符;公有构造析构之后,Open 按路径和标志打开文件,WriteHeader 写一维表头,WriteLine 写二维数据行,Read 读回字符串。宏 PATH 把文件名拼到 Test/ 目录下,H 是 3 列表头,L 是 4×3 的样例数据,OnStart 里用 GetTickCount 打点计时。 代价是每次交换都写盘。实测发 100 个 3 列 4 行的文件,EURUSD M1 下耗时 5578 毫秒,平均单文件约 55.8 毫秒。比套接字慢,但策略只在每根新 K 线开盘推一次数据时,这个延迟通常可接受。 别把这套当流式架构 若你要喂实时报价、订单簿或 tick 级数据,落盘 CSV 会直接拖垮系统。它现在绑定单模型单策略,扩展性也弱,后续要改发送格式必须两端代码同步改,规划阶段就得留好余地。外汇和贵金属波动剧烈,这类桥接方案仅适合低频信号,高频场景高风险。
class CFileCSV : class="kw">public CFile { class="kw">private: class="kw">template<class="kw">typename T> class="type">class="kw">string ToString(class="kw">const class="type">int, class="kw">const T &[][]); class="kw">template<class="kw">typename T> class="type">class="kw">string ToString(class="kw">const T &[]); class="type">class="kw">short m_delimiter; class="kw">public: CFileCSV(class="type">void); ~CFileCSV(class="type">void); class=class="str">"cmt">//--- methods for working with files class="type">int Open(class="kw">const class="type">class="kw">string,class="kw">const class="type">int, class="kw">const class="type">class="kw">short); class="kw">template<class="kw">typename T> class="type">uint WriteHeader(class="kw">const T &values[]); class="kw">template<class="kw">typename T> class="type">uint WriteLine(class="kw">const T &values[][]); class="type">class="kw">string Read(class="type">void); }; class="macro">#include "FileCSV.mqh" class="macro">#define PATH(path) "Test/"+path+".csv" class="type">class="kw">string H[class="num">3] = { "data", "close", "val_ma" }; class="type">class="kw">string L[class="num">4][class="num">3] = {{"class="num">10202022", "class="num">10.55", "class="num">10.49"},{"class="num">10212022", "class="num">10.95", "class="num">11.09"},{"class="num">10222022", "class="num">11.55", "class="num">11.29"},{"class="num">10232022", "class="num">11.15", "class="num">11.29"}}; CFileCSV File; class="type">class="kw">ulong start=class="num">0,time=class="num">0; class="type">void OnStart() { start=class="num">0; time=class="num">0; start=GetTickCount();
MT5 与 Python 的百次文件握手实测
在 MT5 里用 MQL5 向共享目录写文件、再等外部 Python 服务回执,这套跨进程通信常被拿来做 EA 与 AI 分析模块的桥接。下面这段把 100 次写文件+等确认的过程包在 OnStart 里跑,用 GetTickCount 掐表,最终 Print 出总耗时毫秒数,读者可以直接抄进脚本测自己机器。
| 循环里每次先以 FILE_WRITE | FILE_SHARE_READ | FILE_ANSI 打开 init 文件,ResetLastError 清掉旧错误码,再尝试写表头和一行数据;若返回不满足条件就打印错误。随后关闭文件并死等 init_checked 出现,期间用 Comment 在图表刷“waiting for startup”,确认后删掉两个标记文件进入下一轮。 |
|---|
Python 侧监听同一个 common 文件路径(示例为 C:\Users\letha\AppData\Roaming\MetaQuotes\Terminal\...\MQL5\Files\Test\{}.csv),删掉 init 再写 init_checked 完成一次握手。100 轮跑完,Print 出的“Time send 100 archives with transfer message [ms]”就是端到端延迟样本,不同 SSD 或终端负载下可能差出几倍。 这种机制在外汇/贵金属自动化里属于高风险依赖:文件锁竞争或 Python 进程挂掉会让 MQL5 侧 while 卡死,实盘前务必在模拟环境压测并加超时退出。
class="type">void OnStart() { start=class="num">0; time=class="num">0; start=GetTickCount(); for(class="type">int i=class="num">0; i<class="num">100; i++) { File.Open(PATH("init"), FILE_WRITE|FILE_SHARE_READ|FILE_ANSI); ResetLastError(); if((File.WriteHeader(H)<class="num">1&File.WriteLine(L)<class="num">1)!=class="num">0) Print("Error : ", GetLastError()); File.Close(); class="kw">while(!File.IsExist(PATH("init_checked"))) { class=class="str">"cmt">//waiting for startup Comment("waiting for startup"); } File.Delete(PATH("init")); File.Delete(PATH("init_checked")); } time=GetTickCount()-start; Print("Time send class="num">100 archives with transfer message [ms]: ",time); }
「用 sklearn 跑通一条价格回归基线」
下面这段 Python 演示了怎么用 scikit-learn 的 LinearRegression,拿 500 行随机生成的假数据(价格均匀落在 100–200、成交量 1 万–10 万、经济指标 1–100)训一个线性模型,顺带算出 R² 当作拟合质量的参考。它把标的名做 One-Hot 编码后和量价、宏观指标拼成特征矩阵 X,以价格为 y 拟合,再用同一批数据回测预测值。 代码里 random.seed(42) 固定了随机种子,所以每次跑出来的 500 行 DataFrame 完全一致,方便你本地复现。注意原示例把编码列命名写成了 'tiker_encoder'(少了一个 c),X 里引用的也是这个拼错字段,直接抄进生产环境会埋坑。 R² 是在训练集上算的,示例既用历史数据 fit 又用同一份 predict,所以分数会偏向乐观,只说明「这条基线能记住样本」而非「能外推」。想看真实泛化能力,得另切测试集。 别把正态当圣经:示例数据是 uniform 随机,不具备真实行情的厚尾和自相关,直接换真tick也救不了模型结构本身的薄弱。外汇和贵金属杠杆高、跳空频繁,这类静态脚本仅用于论证流程,实盘接入前必须接实时行情并做样本外验证,否则亏损概率显著。
class="kw">import random class="kw">import pandas as pd from sklearn.linear_model class="kw">import LinearRegression from sklearn.metrics class="kw">import r2_score from sklearn.preprocessing class="kw">import OneHotEncoder random.seed(class="num">42) encoder = OneHotEncoder() # Create an empty dataframe data = pd.DataFrame(columns=[&class="macro">#x27;ticker&class="macro">#x27;, &class="macro">#x27;price&class="macro">#x27;, &class="macro">#x27;volume&class="macro">#x27;, &class="macro">#x27;economic_indicator&class="macro">#x27;]) # Fill the dataframe with random values for i in range(class="num">500): row = { &class="macro">#x27;ticker&class="macro">#x27;: "FAKE3", &class="macro">#x27;price&class="macro">#x27;: round(random.uniform(class="num">100, class="num">200), class="num">2), &class="macro">#x27;volume&class="macro">#x27;: round(random.uniform(class="num">10000, class="num">100000), class="num">2), &class="macro">#x27;economic_indicator&class="macro">#x27;: round(random.uniform(class="num">1, class="num">100), class="num">2) } data = data.append(row, ignore_index=True) print(data) # apply one-hot encoding of the column "ticker" onehot_encoded = encoder.fit_transform(data[[&class="macro">#x27;ticker&class="macro">#x27;]]) # add a new one-hot encoded column to the original dataframe data[&class="macro">#x27;tiker_encoder&class="macro">#x27;] = onehot_encoded.toarray() # Selecting independent and dependent variables X = data[[&class="macro">#x27;tiker_encoder&class="macro">#x27;, &class="macro">#x27;volume&class="macro">#x27;, &class="macro">#x27;economic_indicator&class="macro">#x27;]] y = data[&class="macro">#x27;price&class="macro">#x27;] # Creating the linear regression model model = LinearRegression() # Training the model on historical data model.fit(X, y) # Making predictions with the trained model y_pred = model.predict(X) # Evaluating prediction quality r2 = r2_score(y, y_pred) print("Determination coefficient:", r2) # Making predictions for new data new_data = [[class="num">1, class="num">23228.17, class="num">61.21]] new_price_pred = model.predict(new_data) print("Price prediction for new data:", new_price_pred)
◍ CFileCSV 是跨平台验证的落点
前面几节把 Python 训练好的 ML 模型搬进 MT5 策略测试器的通路打通了,这一节的落点是:下一阶段直接用 CFileCSV 类在 MQL5 侧做数据传输底座。 它不只是读写好看,而是把 Python 侧的高级特征工程和 MQL5 侧的 tick 级回测缝在一起,模型效率验证才有可重复的实验环境。外汇与贵金属波动受宏观事件驱动,跨平台回测结论只代表历史样本表现,实盘仍属高风险。 下一篇会拆 CFileCSV 的实现细节,建议先开 MT5 建个空 EA,把 csv 读写接口跑通,再接前面的推理函数,否则等到联调时你会卡在字段对齐上。