将 MQL5 与数据处理包集成(第 4 部分):大数据处理·进阶篇
🗄️

将 MQL5 与数据处理包集成(第 4 部分):大数据处理·进阶篇

(2/3)· 当历史报价堆到百万行,内置函数开始吃力,这套外挂管线能救回你的回测时间

实战向 第 2/3 篇
很多人以为 MQL5 自带的 history 函数足以应付任何数据量,直到 BTC/USD 每小时数据拉满一年就卡死终端。把跨时区原始时间戳直接塞进数组而不转 pandas,后续清洗基本只能重写。大数据处理第一步不是写策略,是先让数据能顺畅离开 MT5。

「在 Jupyter 里给 BTC 一小时数据做清洗与指标落地」

把 MT5 导出的 BTCUSD_H1 原始 CSV 丢进 Jupyter Lab 后,第一件事不是跑模型,而是用 info() 和 head() 确认数据类型、行数和缺失情况。原文数据集经检查后显示:缺失值数量为 0,重复行与重复列均为 0,说明该 H1 切片本身干净,可直接进分析管道。 若源文件实际是制表符分隔(TSV)而非逗号分隔,要在 pd.read_csv() 里显式写 delimiter='\t',否则列会被吞成一整行。解析完后把 <DATE> 与 <TIME> 拼成 DATETIME 并设为索引,再 drop 掉原两列,后续按时间轴画图才不会错位。 技术指标侧,用 rolling(window=20).mean() 算收盘价 20 周期 MA,再用 ta 库的 RSIIndicator(window=14) 补一列 RSI。这两个字段是后面随机森林信号分类的常用特征,也方便手算趋势与超买超卖区间。 可视化用 matplotlib 开 12x6 英寸图,橙线叠 MA20、蓝线画收盘价,肉眼能直接比对价格与均线的穿插关系。外汇与贵金属品种同样适用这套流程,但需注意杠杆品种跳空可能造成缺失值,清洗逻辑要留容错。

MQL5 / C++
class="kw">import pandas as pd
# Load the uploaded BTC 1H CSV file
file_path = &class="macro">#x27;/home/int_junkie/Documents/DataVisuals/BTCUSD_H1.csv&class="macro">#x27;
btc_data = pd.read_csv(file_path)
# Display basic information about the dataset
btc_data_info = btc_data.info()
btc_data_head = btc_data.head()
btc_data_info, btc_data_head
# Reload the data with tab-separated values
btc_data = pd.read_csv(file_path, delimiter=&class="macro">#x27;\t&class="macro">#x27;)
# Display basic information and the first few rows after parsing
btc_data_info = btc_data.info()
btc_data_head = btc_data.head()
btc_data_info, btc_data_head
# Combine <DATE> and <TIME> into a single class="type">class="kw">datetime column and set it as the index
btc_data[&class="macro">#x27;DATETIME&class="macro">#x27;] = pd.to_datetime(btc_data[&class="macro">#x27;<DATE>&class="macro">#x27;] + &class="macro">#x27; &class="macro">#x27; + btc_data[&class="macro">#x27;<TIME>&class="macro">#x27;])
btc_data.set_index(&class="macro">#x27;DATETIME&class="macro">#x27;, inplace=True)
# Drop the original <DATE> and <TIME> columns as they&class="macro">#x27;re no longer needed
btc_data.drop(columns=[&class="macro">#x27;<DATE>&class="macro">#x27;, &class="macro">#x27;<TIME>&class="macro">#x27;], inplace=True)
# Display the first few rows after modifications
btc_data.head()
# Check for missing values and duplicates
missing_values = btc_data.isnull().sum()
duplicate_rows = btc_data.duplicated().sum()
# Clean data(if needed)
btc_data_cleaned = btc_data.drop_duplicates()
# Results
missing_values, duplicate_rows, btc_data_cleaned.shape
# Check for missing values
print("Missing values per column:\n", btc_data.isnull().sum())
# Check for duplicate rows
print("Number of duplicate rows:", btc_data.duplicated().sum())
# Drop duplicate rows if any
btc_data = btc_data.drop_duplicates()
# Calculate a class="num">20-period moving average
btc_data[&class="macro">#x27;MA20&class="macro">#x27;] = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].rolling(window=class="num">20).mean()
class="kw">import ta
# Add RSI class="kw">using the `ta` library
btc_data[&class="macro">#x27;RSI&class="macro">#x27;] = ta.momentum.RSIIndicator(btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;], window=class="num">14).rsi()
class="kw">import matplotlib.pyplot as plt
# Plot closing price and MA20
plt.figure(figsize=(class="num">12, class="num">6))
plt.plot(btc_data.index, btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;], label=&class="macro">#x27;Close Price&class="macro">#x27;)
plt.plot(btc_data.index, btc_data[&class="macro">#x27;MA20&class="macro">#x27;], label=&class="macro">#x27;class="num">20-period MA&class="macro">#x27;, class="type">class="kw">color=&class="macro">#x27;orange&class="macro">#x27;)
plt.legend()
plt.title(&class="macro">#x27;BTC Closing Price and Moving Average&class="macro">#x27;)
plt.show()

◍ 用 Python 把 BTC 小时线切成可回测的特征与信号

这段脚本走的是先清洗、再构造特征、最后用均线交叉出信号的路线,核心在把 MT5 导出的 H1 收盘价转成对数收益并落盘,方便后续反复跑。 日志收益那行用 np.log(close/close.shift(1)),比普通百分比收益更适合后续算相关矩阵;存成 BTCUSD_H1_cleaned.csv 后,seaborn 的 heatmap 能直接把各列相关系数画出来,annot=True 会把数值标在格子里。 特征侧除了 MA20、MA50(窗口 20 / 50 根 H1 K 线),还接了 ta 库的 RSI(14) 与 MACD。注意原文有两套 Signal 逻辑:前一段拿 MA20>RSI 当信号属于写错思路,后一段才是正经 MA20 上穿 MA50 给 1、下穿给 -1,且 shift(1) 避开了未来函数。 train_test_split 按 test_size=0.2、random_state=42 切数据,X 丢掉收盘价、y 用收盘价本身——这其实是回归框架的雏形。Target 列用 shift(-1) 标记下一根是否收高,是典型二分类标签。 回测部分用 pct_change 算市场收益,Signal.shift(1) 乘进去得策略收益,cumprod 画出累计曲线。外汇与贵金属虽不在本例,但同样口径下杠杆品种回测须警惕滑点与隔夜跳空,实际胜率可能明显低于裸曲线。 开 MT5 把 BTCUSD H1 导出来跑一遍这段,重点看 MA20/MA50 交叉信号在 2021 年震荡段的换手频率,能直观判断该策略在无明显趋势时会不会被两面打脸。

MQL5 / C++
class="kw">import numpy as np
# Add log returns
btc_data[&class="macro">#x27;Log_Returns&class="macro">#x27;] = (btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;] / btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].shift(class="num">1)).apply(lambda x: np.log(x))
# Save the cleaned data
btc_data.to_csv(&class="macro">#x27;BTCUSD_H1_cleaned.csv&class="macro">#x27;)
class="kw">import seaborn as sns
class="kw">import matplotlib.pyplot as plt
# Correlation heatmap
sns.heatmap(btc_data.corr(), annot=True, cmap=&class="macro">#x27;coolwarm&class="macro">#x27;)
plt.title(&class="macro">#x27;Correlation Heatmap&class="macro">#x27;)
plt.show()
from sklearn.model_selection class="kw">import train_test_split
# Define features and target variable
X = btc_data.drop(columns=[&class="macro">#x27;<CLOSE>&class="macro">#x27;])
y = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;]
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=class="num">0.2, random_state=class="num">42)
# Simple Moving Average Crossover Strategy
btc_data[&class="macro">#x27;Signal&class="macro">#x27;] = (btc_data[&class="macro">#x27;MA20&class="macro">#x27;] > btc_data[&class="macro">#x27;RSI&class="macro">#x27;]).astype(class="type">int)
btc_data[&class="macro">#x27;Returns&class="macro">#x27;] = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].pct_change()
btc_data[&class="macro">#x27;Strategy_Returns&class="macro">#x27;] = btc_data[&class="macro">#x27;Signal&class="macro">#x27;].shift(class="num">1) * btc_data[&class="macro">#x27;Returns&class="macro">#x27;]
# Plot cumulative returns
btc_data[&class="macro">#x27;Cumulative_Strategy&class="macro">#x27;] = (class="num">1 + btc_data[&class="macro">#x27;Strategy_Returns&class="macro">#x27;]).cumprod()
btc_data[&class="macro">#x27;Cumulative_Market&class="macro">#x27;] = (class="num">1 + btc_data[&class="macro">#x27;Returns&class="macro">#x27;]).cumprod()
btc_data[[&class="macro">#x27;Cumulative_Strategy&class="macro">#x27;, &class="macro">#x27;Cumulative_Market&class="macro">#x27;]].plot(title=&class="macro">#x27;Strategy vs. Market Returns&class="macro">#x27;)
plt.show()
# Calculate class="type">short-term and class="type">long-term moving averages
btc_data[&class="macro">#x27;MA20&class="macro">#x27;] = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].rolling(window=class="num">20).mean()
btc_data[&class="macro">#x27;MA50&class="macro">#x27;] = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].rolling(window=class="num">50).mean()
# Generate signals: class="num">1 for Buy, -class="num">1 for Sell
btc_data[&class="macro">#x27;Signal&class="macro">#x27;] = class="num">0
btc_data.loc[btc_data[&class="macro">#x27;MA20&class="macro">#x27;] > btc_data[&class="macro">#x27;MA50&class="macro">#x27;], &class="macro">#x27;Signal&class="macro">#x27;] = class="num">1
btc_data.loc[btc_data[&class="macro">#x27;MA20&class="macro">#x27;] < btc_data[&class="macro">#x27;MA50&class="macro">#x27;], &class="macro">#x27;Signal&class="macro">#x27;] = -class="num">1
# Shift signal to avoid look-ahead bias
btc_data[&class="macro">#x27;Signal&class="macro">#x27;] = btc_data[&class="macro">#x27;Signal&class="macro">#x27;].shift(class="num">1)
# Calculate returns
btc_data[&class="macro">#x27;Returns&class="macro">#x27;] = btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].pct_change()
btc_data[&class="macro">#x27;Strategy_Returns&class="macro">#x27;] = btc_data[&class="macro">#x27;Signal&class="macro">#x27;] * btc_data[&class="macro">#x27;Returns&class="macro">#x27;]
# Calculate cumulative returns
btc_data[&class="macro">#x27;Cumulative_Market&class="macro">#x27;] = (class="num">1 + btc_data[&class="macro">#x27;Returns&class="macro">#x27;]).cumprod()
btc_data[&class="macro">#x27;Cumulative_Strategy&class="macro">#x27;] = (class="num">1 + btc_data[&class="macro">#x27;Strategy_Returns&class="macro">#x27;]).cumprod()
# Plot performance
class="kw">import matplotlib.pyplot as plt
plt.figure(figsize=(class="num">12, class="num">6))
plt.plot(btc_data[&class="macro">#x27;Cumulative_Market&class="macro">#x27;], label=&class="macro">#x27;Market Returns&class="macro">#x27;)
plt.plot(btc_data[&class="macro">#x27;Cumulative_Strategy&class="macro">#x27;], label=&class="macro">#x27;Strategy Returns&class="macro">#x27;)
plt.title(&class="macro">#x27;Strategy vs. Market Performance&class="macro">#x27;)
plt.legend()
plt.show()
# Add RSI
from ta.momentum class="kw">import RSIIndicator
btc_data[&class="macro">#x27;RSI&class="macro">#x27;] = RSIIndicator(btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;], window=class="num">14).rsi()
# Add MACD
from ta.trend class="kw">import MACD
macd = MACD(btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;])
btc_data[&class="macro">#x27;MACD&class="macro">#x27;] = macd.macd()
btc_data[&class="macro">#x27;MACD_Signal&class="macro">#x27;] = macd.macd_signal()
# Target variable: class="num">1 if next period&class="macro">#x27;s close > current close
btc_data[&class="macro">#x27;Target&class="macro">#x27;] = (btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;].shift(-class="num">1) > btc_data[&class="macro">#x27;<CLOSE>&class="macro">#x27;]).astype(class="type">int)
from sklearn.model_selection class="kw">import train_test_split

用随机森林给 BTC 方向分类跑一遍

下面这段 Python 代码把 BTC 的均线、RSI 和 MACD 类因子塞进随机森林,做二分类预测,能直接复用到 MT5 导出的 CSV 上做离线验证。 特征取了 MA20、MA50、RSI、MACD、MACD_Signal 五个字段,目标列是预先打好的 Target(涨/跌标签)。用 train_test_split 按 0.2 比例切测试集,随机种子锁 42,保证每次跑结果可复现。 模型用 RandomForestClassifier,n_estimators=100,训练完在测试集上 print 出 accuracy 和 classification_report,能直接看到各类精确率、召回率。 最后用 joblib.dump 把模型存成 btc_trading_model.pkl,之后小布类工具加载这个文件就能对新的 MT5 日线快照做批量推理。外汇与贵金属波动更剧烈,直接套用 crypto 训练集概率会漂移,需重采样本地品种数据。

MQL5 / C++
from sklearn.ensemble class="kw">import RandomForestClassifier
from sklearn.metrics class="kw">import accuracy_score, classification_report
# Define features and target
features = [&class="macro">#x27;MA20&class="macro">#x27;, &class="macro">#x27;MA50&class="macro">#x27;, &class="macro">#x27;RSI&class="macro">#x27;, &class="macro">#x27;MACD&class="macro">#x27;, &class="macro">#x27;MACD_Signal&class="macro">#x27;]
X = btc_data.dropna()[features]
y = btc_data.dropna()[&class="macro">#x27;Target&class="macro">#x27;]
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=class="num">0.2, random_state=class="num">42)
# Train a Random Forest Classifier
model = RandomForestClassifier(n_estimators=class="num">100, random_state=class="num">42)
model.fit(X_train, y_train)
# Evaluate the model
y_pred = model.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
class="kw">import joblib
# Save the model
joblib.dump(model, &class="macro">#x27;btc_trading_model.pkl&class="macro">#x27;)

「用 WebRequest 把 MT5 接到 Python 模型」

想在 MT5 里跑训练好的模型,核心不是重写算法,而是打通 MQL5 与 Python 的通信。最轻量的办法是用 WebRequest 走本地 HTTP:MQL5 负责算指标、组特征,Python 端监听 127.0.0.1:5000/predict 做推理,返回买卖信号。 GetPrediction() 先把 double 型特征数组拼成 JSON 串,DoubleToString 锁死六位小数,再转 char 数组发 POST。若 WebRequest 返回 -1,说明请求没发出去,函数也回 -1 给上层,避免假信号。 OnTick 里先抓 MA20、MA50、RSI14 以及 MACD 三线(线/信号/柱),塞进 features 数组送模型。模型回 1 倾向买、回 -1 倾向卖,实盘用 MBuy / MSell 跟单。外汇与贵金属杠杆高,信号只是概率倾向,开 MT5 前先确认 Python 端已起服务,否则全部请求会落空。 下面这段是通信函数的骨架,逐行看能直接抄去改端点: // 头两行声明版权与链接属性,可忽略 // #property copyright / link / version 是脚本元信息 // #include <Trade\Trade.mqh> 引入交易库,CTrade trade 实例化下单对象 // GetPrediction 入参是 double 引用数组 features[] // 循环里 DoubleToString(features[i],6) 保证每值六位小数,逗号分隔拼成 [..] // url 写死本地 5000 端口 /predict // StringToCharArray 把 json 请求转 char 待发 // WebRequest 用 POST,超时 500 毫秒,响应进 resultsss // 若 result==-1 打错误码并 return -1 // CharArrayToString 把响应还原字符串,StringToDouble 取预测值

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| BTC-Big-DataH.mq5 |
class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#include <Trade\Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Function to get predictions from Python API
class="type">class="kw">double GetPrediction(class="type">class="kw">double &features[]) {
  class=class="str">"cmt">// Convert the features array to a JSON-like class="type">class="kw">string
  class="type">class="kw">string jsonRequest = "[";
  for (class="type">int i = class="num">0; i < ArraySize(features); i++) {
    jsonRequest += DoubleToString(features[i], class="num">6);
    if (i != ArraySize(features) - class="num">1) jsonRequest += ",";
  }
  jsonRequest += "]";
  class=class="str">"cmt">// Define the WebRequest parameters
  class="type">class="kw">string url = "http:class=class="str">"cmt">//class="num">127.0.class="num">0.1:class="num">5000/predict";
  class="type">class="kw">string hdrs = {"Content-Type: application/json"}; class=class="str">"cmt">// Add headers if needed
  class="type">char data[];
  StringToCharArray(jsonRequest, data); class=class="str">"cmt">// Convert JSON request class="type">class="kw">string to class="type">char array
  class="type">char response[];
  class="type">ulong result_headers_size = class="num">0;
  class=class="str">"cmt">//--------------------------------------------------------------------------------------
  class="type">class="kw">string cookie=NULL;
  class="type">char post[], resultsss[];
  class=class="str">"cmt">// Send the WebRequest
  class="type">int result = WebRequest("POST", url, cookie, NULL, class="num">500, post, class="num">0, resultsss, hdrs);
  class=class="str">"cmt">// Handle the response
  if (result == -class="num">1) {
    Print("Error sending WebRequest: ", GetLastError());
    class="kw">return -class="num">1; class=class="str">"cmt">// Return an error signal
  }
  class=class="str">"cmt">// Convert response class="type">char array back to a class="type">class="kw">string
  class="type">class="kw">string responseString;
  CharArrayToString(response, (class="type">int)responseString);
  class=class="str">"cmt">// Parse the response(assuming the server returns a numeric value)
  class="type">class="kw">double prediction = StringToDouble(responseString);
  class="kw">return prediction;
}
把跨环境取数交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到数据抽取与结构化的健康度提示,你只需专注怎么用这批数据做信号。

常见问题

Linux 环境直接持留内存数据易被终端 shutdown 打断,写文件再读能避免连接释放后的空引用,转 pandas 仅为便于按列运算。
可能略有增加,因为终端需维护更长的本地缓存,但对历史提取完整性有帮助,实盘轻量品种倾向可接受。
Jupyter 侧擅长探索和外部库接入,MQL5 内联更适合低延迟执行;进阶做法是用前者出因子再编译进后者。
可以,小布盯盘的 AIGC 会对接回品种页做结构校验,异常会直接标在盘口侧,不用你手动比对 UTC 与本地时间。
此类市场受宏观事件冲击大,样本外概率漂移明显,大样本容易拟合噪声,实盘前需用滚动窗口验证。