使用MQL5和Python构建自优化EA(第三部分):破解Boom 1000算法·进阶篇
📘

使用MQL5和Python构建自优化EA(第三部分):破解Boom 1000算法·进阶篇

第 2/3 篇

建模前先把数据归一与切分

用机器学习预测 Boom 1000 这类高波动指数的价格方向,第一步不是直接喂模型,而是把特征做稳健缩放。RobustScaler 用四分位距而非标准差,对外汇、贵金属突发行情里的离群点不那么敏感,能压住极端跳空带来的扭曲。 本段代码基于 sklearn 1.4.1.post1 环境,加载了 RobustScaler、MLP 分类器以及时间序列切分工具。注意这里显式打印了库版本,MT5 外接 Python 环境若版本不一致,fit_transform 行为可能有细微差别,先核对再跑。 目标变量做了两套:一套是收盘价的二元标签「Price Binary Target」,一套是 RSI 的二元标签「RSI Binary Target」。两者都用 train_test_split 且 shuffle=False、test_size=0.5,意味着前 50% 当训练、后 50% 当测试,不打乱时序——这是时间序列建模的硬规矩,乱序会骗出虚假准确率。 验证准确率用了一个 5 行的 DataFrame 存,列名区分 Close 与 RSI。切分对象用 TimeSeriesSplit(gap=look_ahead, n_splits=5),gap 等于前瞻步数,避免标签泄漏。外汇与贵金属杠杆高、回撤快,这类模型输出仅作概率参考,实盘须严控仓位。

MQL5 / C++
class="macro">#Preparing to model the data
class="kw">import sklearn
from sklearn.preprocessing class="kw">import RobustScaler
from sklearn.neural_network class="kw">import MLPClassifier,MLPRegressor
from sklearn.model_selection class="kw">import TimeSeriesSplit, train_test_split
from sklearn.metrics class="kw">import accuracy_score
class="macro">#Display library version
print(f"Sklearn version {sklearn.__version__}")
class="macro">#Scale our data
X = pd.DataFrame(RobustScaler().fit_transform(boom_1000.loc[:,predictors]),columns=predictors)
class="macro">#Our old and new target
old_y = boom_1000.loc[:,"Price Binary Target"]
new_y = boom_1000.loc[:,"RSI Binary Target"]
class="macro">#Perform train test splits
train_X,test_X,ohlc_train_y,ohlc_test_y = train_test_split(X,old_y,shuffle=False,test_size=class="num">0.5)
_,_,rsi_train_y,rsi_test_y = train_test_split(X,new_y,shuffle=False,test_size=class="num">0.5)
class="macro">#Prepare data frames to store our accuracy levels
validation_accuracy = pd.DataFrame(index=np.arange(class="num">0,class="num">5),columns=["Close Accuracy","RSI Accuracy"])
class="macro">#Let&class="macro">#x27;s create the time series split object
tscv = TimeSeriesSplit(gap=look_ahead,n_splits=class="num">5)

◍ RSI 方向比价格本身更好猜

做交叉验证时,把近似价格变动和 RSI 变动两套目标摆在一起跑,准确率差异直接拉开。五折验证里,价格近似准确率在 0.4795 到 0.5706 之间晃,RSI 准确率则稳在 0.5976 到 0.6632 之间,均值分别是 0.5355 和 0.6304。 标准差也讲同一件事:价格模型 0.0338,RSI 模型 0.0266,越低代表模型对自己预测越有底气。也就是说,这套多层感知机更像是在学 RSI 的节奏,而不是裸价格的方向。 箱线图把差距画得更狠——RSI 模型的箱体整体抬到价格模型上方,离散度还更窄。外汇和贵金属这类高波动品种,用 RSI 变化做特征目标,模型泛化可能比直接猜涨跌更靠谱,但杠杆市场高风险仍在,回测不代表实盘必然重复。 下面这段 Python 风格的训练验证循环,就是上面数字的来源,在 MT5 外接分析环境里可直接复现。

MQL5 / C++
<span class="preprocessor">class="macro">#Instatiate </span>the model
model = MLPClassifier(hidden_layer_sizes=(<span class="number">class="num">30</span>,<span class="number">class="num">10</span>),max_iter=<span class="number">class="num">200</span>)
<span class="preprocessor">class="macro">#Cross </span>validate the model
<span class="keyword">for</span> i,(train,test) in enumerate(tscv.split(train_X)):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;model.fit(train_X.loc[train[<span class="number">class="num">0</span>]:train[-<span class="number">class="num">1</span>],:],ohlc_train_y.loc[train[<span class="number">class="num">0</span>]:train[-<span class="number">class="num">1</span>]])
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;validation_accuracy.iloc[i,<span class="number">class="num">0</span>] = accuracy_score(ohlc_train_y.loc[test[<span class="number">class="num">0</span>]:test[-<span class="number">class="num">1</span>]],model.predict(train_X.loc[test[<span class="number">class="num">0</span>]:test[-<span class="number">class="num">1</span>],:]))
validation_accuracy
validation_accuracy.mean()
validation_accuracy.std()
validation_accuracy.plot()
<span class="preprocessor">class="macro">#Our </span>RSI validation accuracy is better
sns.boxplot(validation_accuracy)

「逐步筛选与互信息下的特征权重」

用逐步前向选择(SFS)从空模型起逐次加特征,以 5 折交叉验证的 accuracy 为准则,跑到 k_features 上限 (1, X.shape[1]) 后,被选中的是 Open、High、Low、Close、RSI 全部五项——说明在该 MLP(隐藏层 30,10,max_iter=200)设定下,砍掉任一原始行情字段都会拖累 RSI 预测性能。 mlxtend 版本锁定在 0.23.1,n_jobs=-1 跑满本机核心;cv=5 意味着每次评估都把训练集拆成五份轮换,结果相对稳定,不是单次切分的偶然。 互信息(MI)分数换了一套视角:它走对数尺度,能抓非线性依赖,实务中 MI 超过 3 极少见。柱图显示 RSI 列自身的 MI 最高,即历史 RSI 对预测下一步 RSI 的边际信息量最大,OHLC 四项次之且彼此接近。 外汇与贵金属行情噪声大、跳空频繁,这类特征重要性结论只在对应品种与周期下成立,换周期可能重构排名,建议开 MT5 导出同结构数据自己跑一遍再信。

MQL5 / C++
class="macro">#Feature importance
class="kw">import mlxtend
from mlxtend.feature_selection class="kw">import SequentialFeatureSelector as SFS
print(f"Mlxtend version: {mlxtend.__version__}")
class="macro">#Reinitialize the model
model = MLPClassifier(hidden_layer_sizes=(class="num">30,class="num">10),max_iter=class="num">200)
class="macro">#Define the forward feature selector
sfs1 = SFS(
        model,
        k_features=(class="num">1,X.shape[class="num">1]),
        n_jobs=-class="num">1,
        forward=True,
        cv=class="num">5,
        scoring="accuracy"
)
class="macro">#Fit the feature selector
sfs = sfs1.fit(train_X,rsi_train_y)
sfs.k_feature_names_
class="macro">#Importing the libraries we need
from mlxtend.plotting class="kw">import plot_sequential_feature_selection as plot_sfs
class="kw">import matplotlib.pyplot as plt
fig1 = plot_sfs(sfs.get_metric_dict(), kind=&class="macro">#x27;std_err&class="macro">#x27;)
plt.title(&class="macro">#x27;Sequential Forward Selection&class="macro">#x27;)
plt.grid()
plt.show()
class="macro">#Let&class="macro">#x27;s analyze our MI scores
from sklearn.feature_selection class="kw">import mutual_info_classif
mi_scores = pd.DataFrame(mutual_info_classif(train_X,rsi_train_y).reshape(class="num">1,class="num">5),columns=predictors)
class="macro">#Let&class="macro">#x27;s visualize the results
mi_scores.plot.bar()

用随机搜索给MLP找更优超参

想让行情分类模型再多榨一点性能,核心矛盾永远是准确率与计算时间的折中。RandomizedSearchCV 的思路是在参数空间里随机抽 n_iter 组配置做交叉验证,而不是穷举,所以迭代次数直接决定了你愿意用多少算力换潜在提升。 上面这段脚本把隐藏层定为 (30,10)、最大迭代 200 重新初始化 MLPClassifier,再用 300 次随机采样、5 折交叉、全核心并行去搜激活函数、求解器、正则项 alpha 等 8 类超参,评分标准锁在 accuracy。 跑完拿到的 best_params_ 里,tol=1e-05、solver='lbfgs'、learning_rate='adaptive'、learning_rate_init=0.01 这组组合,在对应训练集上可能比默认配置泛化更稳。但外汇与贵金属波动具有高噪声,过拟合风险始终存在,实盘前务必用样本外数据复核。 别把正态当圣经 交叉验证分数高只代表历史片段拟合较好,换一个利率事件窗口可能立刻失效,调参后先丢去回测再上 MT5 信号接口。

MQL5 / C++
class="macro">#Parameter tuning
from sklearn.model_selection class="kw">import RandomizedSearchCV
class="macro">#Reinitialize the model
model = MLPClassifier(hidden_layer_sizes=(class="num">30,class="num">10),max_iter=class="num">200)
class="macro">#Define the tuner
tuner = RandomizedSearchCV(
            model,
            {
                "activation":["relu","tanh","logistic","identity"],
                "solver":["adam","sgd","lbfgs"],
                "alpha":[class="num">0.1,class="num">0.01,class="num">0.001,class="num">0.00001,class="num">0.000001],
                "learning_rate": ["constant","invscaling","adaptive"],
                "learning_rate_init":[class="num">0.1,class="num">0.01,class="num">0.001,class="num">0.0001,class="num">0.000001,class="num">0.0000001],
                "power_t":[class="num">0.1,class="num">0.5,class="num">0.9,class="num">0.01,class="num">0.001,class="num">0.0001],
                "shuffle":[True,False],
                "tol":[class="num">0.1,class="num">0.01,class="num">0.001,class="num">0.0001,class="num">0.00001],
            },
            n_iter=class="num">300,
            cv=class="num">5,
            n_jobs=-class="num">1,
            scoring="accuracy"
)
class="macro">#Fit the tuner
tuner_results =tuner.fit(train_X,rsi_train_y)
class="macro">#Best parameters
tuner_results.best_params_

◍ 交叉验证揪出过拟合

判定一套神经网络是不是在训练集上「背答案」,最直接的办法是把默认结构和调过超参的定制结构都丢到验证集上做时间序列交叉验证。若默认模型准确率反而更高,说明定制化的那次调参只是把噪声也吃进去了,过拟合概率偏大。 跑出来的五折验证准确率如下:默认网络分别为 0.627656、0.637258、0.621414、0.6429、0.664866;定制网络为 0.597767、0.635938、0.631977、0.6411、0.652503。均值上默认 0.638819 略胜定制的 0.631857,但定制模型的准确率标准差达到 0.020557,比默认的 0.016771 更散。 箱线图暴露了关键问题:定制模型出现了默认模型没有的离群折,稳定性更差。综合平均表现与波动,默认模型更值得保留,定制调参在这组数据里没有带来净增益。外汇与贵金属行情具有高杠杆与跳空风险,此类模型仅作辅助信号,实盘前务必用 MT5 历史数据重跑验证。 下面这段 Python 代码就是上述流程的落地版,虽然不在 MQL5 环境跑,但逻辑可直接迁移到 MT5 的 Python 接口做 RSI 二分类验证: #Testing for overfitting # 定义默认 MLP:两层隐藏层(30,10),最多迭代200次 default_model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200) # 定义定制 MLP:在默认基础上加了更严的 tol、lbfgs 求解器、自适应学习率等 default_model = MLPClassifier(hidden_layer_sizes=(30,10),max_iter=200) customized_model = MLPClassifier( hidden_layer_sizes=(30,10), max_iter=200, tol=0.00001, solver="lbfgs", shuffle=True, power_t=0.0001, learning_rate_init=0.01, learning_rate="adaptive", alpha=0.000001, activation="logistic" ) #First we will train both models on the training set # 在两个模型上用训练集拟合 RSI 目标 default_model.fit(train_X,rsi_train_y) customized_model.fit(train_X,rsi_train_y) #Now we will reset our indexes # 重置测试集与目标索引以便按位置切片 rsi_test_y = rsi_test_y.reset_index() test_X = test_X.reset_index() #Format the data # 只抽取需要的列:目标与预测因子 rsi_test_y = rsi_test_y.loc[:,"RSI Binary Target"] test_X = test_X.loc[:,predictors] #Prepare a data frame to store our accuracy levels # 建一个5行2列的空表存每折准确率 validation_error = pd.DataFrame(index=np.arange(0,5),columns=["Default Neural Network","Customized Neural Network"]) #Perform cross validation # 用时间序列切分做交叉验证,逐折写定制模型准确率 for i,(train,test) in enumerate(tscv.split(test_X)): customized_model.fit(test_X.loc[train[0]:train[-1],predictors],rsi_test_y.loc[train[0]:train[-1]]) validation_error.iloc[i,1] = accuracy_score(rsi_test_y.loc[test[0]:test[-1]],customized_model.predict(test_X.loc[test[0]:test[-1]])) validation_error # 输出均值与标准差,并画图 validation_error.mean() validation_error.std() validation_error.plot() sns.boxplot(validation_error)

MQL5 / C++
class="macro">#Testing for overfitting
default_model = MLPClassifier(hidden_layer_sizes=(class="num">30,class="num">10),max_iter=class="num">200)
customized_model = MLPClassifier(
    hidden_layer_sizes=(class="num">30,class="num">10),
    max_iter=class="num">200,
    tol=class="num">0.00001,
    solver="lbfgs",
    shuffle=True,
    power_t=class="num">0.0001,
    learning_rate_init=class="num">0.01,
    learning_rate="adaptive",
    alpha=class="num">0.000001,
    activation="logistic"
)
class="macro">#First we will train both models on the training set
default_model.fit(train_X,rsi_train_y)
customized_model.fit(train_X,rsi_train_y)
class="macro">#Now we will reset our indexes
rsi_test_y = rsi_test_y.reset_index()
test_X = test_X.reset_index()
class="macro">#Format the data
rsi_test_y = rsi_test_y.loc[:,"RSI Binary Target"]
test_X = test_X.loc[:,predictors]
class="macro">#Prepare a data frame to store our accuracy levels
validation_error = pd.DataFrame(index=np.arange(class="num">0,class="num">5),columns=["Default Neural Network","Customized Neural Network"])
class="macro">#Perform cross validation
for i,(train,test) in enumerate(tscv.split(test_X)):
    customized_model.fit(test_X.loc[train[class="num">0]:train[-class="num">1],predictors],rsi_test_y.loc[train[class="num">0]:train[-class="num">1]])
    validation_error.iloc[i,class="num">1] = accuracy_score(rsi_test_y.loc[test[class="num">0]:test[-class="num">1]],customized_model.predict(test_X.loc[test[class="num">0]:test[-class="num">1]]))
validation_error
validation_error.mean()
validation_error.std()
validation_error.plot()
sns.boxplot(validation_error)

「导出ONNX前先把数据标准化」

要把训练好的模型丢进 MetaTrader 5 跑推理,第一步不是直接转 ONNX,而是先解决缩放问题。不同预测列的量纲差很大,比如价格类和 RSI 类混在一起,模型容易偏向数值大的那列。做法很直接:对每一列减去自身均值、除以自身标准差,把分布拉成近似零均值单位方差。 光在内存里缩放不够,MT5 终端里复现推理时必须用同一套参数。所以要把每列的 mean 和 standard deviation 写成 CSV,放到 MT5 的 MQL5/Files 目录下,后续 EA 加载模型前先读这个文件再做同样变换。 下面这段代码就是干这事:先建一个只有 mean / standard deviation 两行的 DataFrame,遍历所有 predictors 列填均值和标准差,当场对 X 做标准化,最后导出 CSV。路径写死在 wine 映射的 MT5 文件目录,换环境要改。外汇和贵金属行情高波动,标准化参数若用错周期数据,推理会系统性偏移,需自己核对。

MQL5 / C++
class="macro">#Preparing to class="kw">export to ONNX
class="macro">#Let&class="macro">#x27;s scale our data
scaling_factors = pd.DataFrame(columns=predictors,index=[&class="macro">#x27;mean&class="macro">#x27;,&class="macro">#x27;standard deviation&class="macro">#x27;])
X = boom_1000.loc[:,predictors]
y = boom_1000.loc[:,"RSI Target"]
class="macro">#Let&class="macro">#x27;s fill each column
for i in np.arange(class="num">0,len(predictors)):
      scaling_factors.iloc[class="num">0,i] = X.iloc[:,i].mean()
      scaling_factors.iloc[class="num">1,i] = X.iloc[:,i].std()
      X.iloc[:,i] = ( ( X.iloc[:,i] - scaling_factors.iloc[class="num">0,i] ) / scaling_factors.iloc[class="num">1,i])
class="macro">#Save the scaling factors as a CSV
scaling_factors.to_csv("/home/volatily/.wine/drive_c/Program Files/MetaTrader class="num">5/MQL5/Files/boom_1000_scaling_factors.csv")

常见问题

不归一化不同量纲特征会互相压制,模型偏向数值大的列;切分能留独立验证集,避免拿训练数据当成绩。先用MinMax或Z-score缩到同区间再按比例拆训练/测试。
原文回测显示RSI涨跌方向比绝对价格更好猜,分类误差更低。把标签设为RSI后续方向,模型泛化倾向更好,但仍是概率性信号,外汇高风险。
可以。小布盯盘的AIGC分析能按品种调取历史,输出互信息排序与逐步筛选结果,你直接看权重表调EA特征,不用自己写脚本。
原文用随机搜几百组取交叉验证最优。看测试集误差明显差于验证集就过拟合了,交叉验证波动大也说明参数脆弱。
推理时输入量纲和训练不一致,输出全偏。导出前把标准化步骤固化进计算图,实盘送入的数据必须过同一标准化。