在任何市场中获得优势(第三部分):Visa消费指数·进阶篇
(2/3)· 当支付巨头的数据流接入MT5,71%准确率背后藏着哪些过拟合陷阱
把 MT5 月线拉进 pandas 并打标签
做跨资产或事件驱动研究,第一步是把 MT5 终端里的历史报价抓出来,而不是手动导 CSV。下面这段脚本直接初始化终端、锁定 UTC 时区,再从 2024-07-01 起取 EURUSD 的月线(TIMEFRAME_MN1),装进 DataFrame,省掉时区错位的坑。 标签逻辑很简单:用未来 look_ahead 根 K 线的收盘价做参照,若当前月线 close 更高则标 0,更低则标 1,再 dropna 丢掉边界 NaN。外汇和贵金属这类高杠杆品种波动剧烈,标签只是概率倾向,不等于方向确定性。 最后按时间索引把新闻类数据集(visa_headline 等)并进来,形成多源合并表。注意 copy_rates_from 的 count 参数用了 visa_headline.shape[0],两边长度要对得上,否则合并会截断样本。
class="macro">#Initialize the terminal mt5.initialize() class="macro">#Set timezone to UTC timezone = pytz.timezone("Etc/UTC") class="macro">#Create a &class="macro">#x27;class="type">class="kw">datetime&class="macro">#x27; object in UTC utc_from = class="type">class="kw">datetime(class="num">2024,class="num">7,class="num">1,tzinfo=timezone) class="macro">#Fetch the data eurusd = pd.DataFrame(mt5.copy_rates_from("EURUSD",mt5.TIMEFRAME_MN1,utc_from,visa_headline.shape[class="num">0])) class="macro">#Label the data eurusd["target"] = np.nan eurusd.loc[eurusd["close"] > eurusd["close"].shift(-look_ahead),"target"] = class="num">0 eurusd.loc[eurusd["close"] < eurusd["close"].shift(-look_ahead),"target"] = class="num">1 eurusd.dropna(inplace=True) eurusd.set_index("time",inplace=True) class="macro">#Let&class="macro">#x27;s merge the datasets merged_data = eurusd.merge(visa_headline,right_index=True,left_index=True) merged_data = merged_data.merge(visa_discretionary,right_index=True,left_index=True) merged_data = merged_data.merge(visa_non_discretionary,right_index=True,left_index=True)
◍ 散点图里看 VISA 与欧美的分界
把 VISA 三类数据集分别和 EURUSD 收盘价画散点,蓝色点代表其后 10 根 K 线价格下跌,橙色点代表上涨。图表中心区域的涨跌分界依旧模糊,但推到极值区间时,VISA 数值对涨跌的区分度明显变好。 三个 VISA 版本(非自主决定型、自主决定型、头条型)与 EURUSD 的相关性都为正且程度适中,绝对值不算抢眼。正值说明两者倾向同涨同跌,这和宏观直觉一致:美国消费支出走弱会拖累货币流通总量,可能反过来支撑美元。 下面这段是生成头条版散点图的 MQL5 侧调用代码,直接丢进分析脚本就能复现图 6 的视角,改 x 里的字段名可切到另外两个数据集。
class="macro">#Let&class="macro">#x27;s create scatter plots sns.scatterplot(data=merged_data,y="close",x="visa h",hue="target").set(title="EURUSD Close Against VISA Momentum Index: Headline")
「前向筛选只留下开盘价」
特征选择能帮我们看清目标与新特征之间到底靠不靠谱。如果筛选算法把新变量全剔掉,说明那段关系可能并不稳定。 我们用的是序列前向选择:从空模型起手,一次加一个特征,挑出单变量最优后再搜第二个,最终回吐整体最优模型。这次跑下来算法只保留了开盘价('open',),新构造的特征一个没选上。 绘图看趋势更直观——图里随着特征数增加,模型性能反而往下掉。准确性持续下降,要么本身关联性弱,要么我们没把关联表达成模型吃得进的形式。单特征模型反而够用。 互信息(MI)分数衡量单变量预测目标的潜力,理论 0 到无穷,实际很少过 2,大于 1 算优秀。VISA 头条集 MI 仅 0.0607,自由支配集 0.1277,全数据集都偏低,这给了我们对 VISA 数据做不同转换的充分理由,或许能挖出更强关联。 外汇与贵金属市场高风险,此类弱关联特征在外盘实盘可能随时失效,验证前请勿直接用于资金决策。
class="macro">#Let&class="macro">#x27;s see which features are the most important from mlxtend.feature_selection class="kw">import SequentialFeatureSelector as SFS from mlxtend.plotting class="kw">import plot_sequential_feature_selection as plot_sfs class="kw">import matplotlib.pyplot as plt class="macro">#Create the forward selection object sfs = SFS( MLPClassifier(hidden_layer_sizes=(class="num">20,class="num">10,class="num">4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"]), k_features=(class="num">1,train_X.shape[class="num">1]), forward=False, scoring="accuracy", cv=class="num">5 ).fit(train_X,train_y) fig1 = plot_sfs(sfs.get_metric_dict(),kind="std_dev") plt.title("Neural Network Backward Feature Selection") plt.grid() sfs.k_feature_names_ class="macro">#Mutual information from sklearn.feature_selection class="kw">import mutual_info_classif class="macro">#Mutual information from the headline visa dataset, print(f"VISA Headline dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,[&class="macro">#x27;visa h&class="macro">#x27;]],train_y)[class="num">0]}") class="macro">#Mutual information from the second visa dataset, print(f"VISA Discretionary dataset has a mutual info score of: {mutual_info_classif(train_X.loc[:,[&class="macro">#x27;visa d&class="macro">#x27;]],train_y)[class="num">0]}")
给 EURUSD 预测网络做标准化与调参
要把深度神经网络用在 EURUSD 汇率预测上,先得把合并数据集的索引重置干净,再明确目标列和特征列。这里目标直接定为 "target",特征由 OHLC 加 tick_volume 以及三组 visa 类字段拼成,共 8 个输入维度。 标准化采用最朴素的 z-score:每列减均值除标准差。这种做法对异常值极其敏感,EURUSD 在重大数据行情里出现的极端影线会直接拉偏标准差,实际盯盘时建议先过一遍分位数截断再缩放。 训练测试划分用 50% 切分且关闭 shuffle,保留时间序列顺序。随后用 RandomizedSearchCV 包住一个三层隐藏节点为 (20,10,4) 的 MLPClassifier,交叉验证折数 cv=5,随机搜索迭代 n_iter=1000,评分用 accuracy。 在本机演示里,训练集最高准确率跑到了 88%。但搜索算法本身带随机性,换一次随机种子结果可能就很难复现,外汇与贵金属本身高杠杆高风险,这个准确率仅代表样本内拟合,实盘概率倾向回落。 下面这段是可直接丢进 Python 环境跑的原文逻辑,逐行拆开看: #Reset the index 重置索引让后续按位置切片不错位 #Define the target 锁定监督信号列名 ohlc_predictors 列出开高低收与成交量 visa_predictors 列出三类辅助特征 all_predictors 把两类特征合并成模型输入 #Let's scale the data 建一个存均值和标准差的表 循环里先记每列 mean 再记 std 接着原位替换成 (x-mean)/std 的缩放值 scale_factors 输出便于反向还原 #Let's see the normalized data 直接打印看分布 #训练部分先引 MLPClassifier 和 RandomizedSearchCV 等 train_test_split 按 0.5 且不打乱切分 tuner 把网络丢进随机搜索,激活函数、求解器、alpha、学习率及初值都给了候选网格 cv=5 表示五折交叉,n_iter=1000 控制搜索次数,scoring 盯准确率。
class="macro">#Reset the index merged_data.reset_index(inplace=True) class="macro">#Define the target target = "target" ohlc_predictors = ["open","high","low","close","tick_volume"] visa_predictors = ["visa d","visa h","visa nd"] all_predictors = ohlc_predictors + visa_predictors class="macro">#Let&class="macro">#x27;s scale the data scale_factors = pd.DataFrame(index=["mean","standard deviation"],columns=all_predictors) for i in np.arange(class="num">0,len(all_predictors)): class="macro">#Store the mean and standard deviation for each column scale_factors.iloc[class="num">0,i] = merged_data.loc[:,all_predictors[i]].mean() scale_factors.iloc[class="num">1,i] = merged_data.loc[:,all_predictors[i]].std() merged_data.loc[:,all_predictors[i]] = ((merged_data.loc[:,all_predictors[i]] - scale_factors.iloc[class="num">0,i]) / scale_factors.iloc[class="num">1,i]) scale_factors class="macro">#Let&class="macro">#x27;s see the normalized data merged_data class="macro">#Lets try to train a deep neural network to uncover relationships in the data from sklearn.neural_network class="kw">import MLPClassifier from sklearn.model_selection class="kw">import RandomizedSearchCV from sklearn.model_selection class="kw">import train_test_split from sklearn.metrics class="kw">import accuracy_score class="macro">#Create train test partitions for our alternative data train_X,test_X,train_y,test_y = train_test_split(merged_data.loc[:,all_predictors],merged_data.loc[:,"target"],test_size=class="num">0.5,shuffle=False) tuner = RandomizedSearchCV(MLPClassifier(hidden_layer_sizes=(class="num">20,class="num">10,class="num">4),shuffle=False), { "activation": ["relu","identity","logistic","tanh"], "solver": ["lbfgs","adam","sgd"], "alpha": [class="num">0.1,class="num">0.01,class="num">0.001,(class="num">10.0 ** -class="num">4),(class="num">10.0 ** -class="num">5),(class="num">10.0 ** -class="num">6),(class="num">10.0 ** -class="num">7),(class="num">10.0 ** -class="num">8),(class="num">10.0 ** -class="num">9)], "learning_rate": ["constant", "invscaling", "adaptive"], "learning_rate_init": [class="num">0.1,class="num">0.01,class="num">0.001,(class="num">10.0 ** -class="num">4),(class="num">10.0 ** -class="num">5),(class="num">10.0 ** -class="num">6),(class="num">10.0 ** -class="num">7),(class="num">10.0 ** -class="num">8),(class="num">10.0 ** -class="num">9)], }, cv=class="num">5, n_iter=class="num">1000, scoring="accuracy",
◍ 用网格搜索挑神经网络超参
在 MT5 外接 Python 做模型调参时,网格搜索(GridSearchCV)能系统遍历激活函数、求解器、正则项 alpha、学习率等组合。上面这段代码把 return_train_score 设为 False,只保留验证集表现,避免训练分干扰排序。 拟合完成后,cv_results_ 会落进 DataFrame,抽取 param_activation、param_solver、param_alpha、param_learning_rate、param_learning_rate_init 和 mean_test_score 六列,按 mean_test_score 降序排列,就能直接看到哪组超参在交叉验证里得分最高。 实盘接这套流程前,建议先拿历史 tick 跑一遍,确认 best_score_ 对应的参数在样本外不塌。外汇与贵金属杠杆高、滑点跳空频繁,回测优不代表实盘稳,参数胜率仅代表历史概率。
return_train_score=False
)
tuner.fit(train_X,train_y)
tuner_results = pd.DataFrame(tuner.cv_results_)
params = ["param_activation","param_solver","param_alpha","param_learning_rate","param_learning_rate_init","mean_test_score"]
tuner_results.loc[:,params].sort_values(by="mean_test_score",ascending=False)「留出集上揪出过拟合」
把默认模型和调参后的自定义模型放到同一份留出集上比一比,就能判断训练阶段是否啃进了噪声。若默认模型在验证集上反而更强,说明自定义模型把训练样本记死了;反之才是真学到规律。 实测默认模型准确率 0.542,自定义模型 0.746,差距明显,训练时没有过拟合训练数据。 顺带看误差差:训练误差 88%、测试误差 74%,两者差 14 个百分点尚属合理区间。若测试误差远低于训练误差、或两者裂口过大,就该警惕过拟合。外汇与贵金属行情非平稳,此类模型在外推实盘时仍属高风险,务必用 MT5 历史数据自测。
class="macro">#Let&class="macro">#x27;s compare the class="kw">default model and our customized model on the hold out set default_model = MLPClassifier(hidden_layer_sizes=(class="num">20,class="num">10,class="num">4),shuffle=False) customized_model = MLPClassifier(hidden_layer_sizes=(class="num">20,class="num">10,class="num">4),shuffle=False,activation=tuner.best_params_["activation"],solver=tuner.best_params_["solver"],alpha=tuner.best_params_["alpha"],learning_rate=tuner.best_params_["learning_rate"],learning_rate_init=tuner.best_params_["learning_rate_init"]) class="macro">#The accuracy of the defualt model default_model.fit(train_X,train_y) accuracy_score(test_y,default_model.predict(test_X)) class="macro">#The accuracy of the defualt model customized_model.fit(train_X,train_y) accuracy_score(test_y,customized_model.predict(test_X))