数据科学和机器学习(第 16 部分):全新面貌的决策树·综合运用
(3/3)· 从节点封装到实盘训练,把前两篇的树形逻辑变成能跑的 MQL5 代码
- 决策树叶节点怎么取众数
- 把决策树塞进一个类里在MT5跑通
- 决策树在 EURUSD H1 上的分类输出样本
- 把决策树结构打印出来看个明白
- 决策树在 EURUSD H1 上的训练集回测表现
- 决策树在 EURUSD H1 上的过拟合痕迹
- 决策树在 EURUSD H1 上的分类回测输出
- 决策树回测里那张混淆矩阵在说什么
- 决策树回测输出的字段怎么读
- 用决策树给当前柱线判方向
- 决策树训练与样本外准确率验证
- 决策树回测日志里的分支阈值
- 决策树在 EURUSD H1 上的分支输出长啥样
- 拆一棵 EURUSD H1 决策树的分支输出
- 决策树回测的混淆矩阵与分支权重
- 决策树回测的混淆矩阵与准确率读数
- 决策树回测准确率只有五成出头
- 空仓一侧补单的逻辑落点
- 决策树落地交易的七个疑点
- 为什么老手偏爱决策树做信号筛选
- 决策树落地的几处暗坑
◍ 决策树叶节点怎么取众数
在决策树回归或分类的末端,叶值不是随便拍的。上面这段逻辑干的事很直接:从样本标签向量 Y 里找出出现频次最高的那个类别,把它作为该叶节点的输出值,本质就是取众数。 具体看,先调用 Unique_count 拿到 Y 中各个唯一值的计数向量,再用 Unique 拿到对应的唯一类别向量。两者下标一一对应,所以对计数向量取 ArgMax,就能定位到出现次数最多的元素位置,再回查 classes 即得叶值。 在 MT5 里把这段代码塞进你自己的 CDecisionTree 类,跑一组标好涨跌方向的 H1 收盘价序列,就能验证:当某叶子节点里 7 个样本有 5 个标记为「空」,叶值就会稳定返回「空」。外汇与贵金属波动剧烈,模型输出仅代表历史样本倾向,实盘须控仓。
class="type">class="kw">double CDecisionTree::calculate_leaf_value(vector &Y) { vector uniques = matrix_utils.Unique_count(Y); vector classes = matrix_utils.Unique(Y); class="kw">return classes[uniques.ArgMax()]; }
把决策树塞进一个类里在MT5跑通
想验证一套监督学习思路能不能用在行情上,最省事的办法是先拿公开数据集把管线跑顺。这里用 iris-csv 做冒烟测试:EA 每次初始化时从文件读数据、训树、打印结构并算训练准确率,逻辑通了再换外汇或贵金属的特征矩阵。 类里把建树、不纯度计算、最优切分和预测全收进了 CDecisionTree。对外只留 fit / predict / print_tree,内部用 m_max_depth 和 m_min_samples_split 控复杂度,m_mode 切换 GINI 与熵,避免调用方碰节点细节。 OnInit 里实测:new CDecisionTree(3,3,MODE_GINI) 表示最小切分样本数 3、最大深度 3、按基尼系数分枝;fit 后用 print_tree 看规则,predict(x) 回灌训练集,confusion_matrix 打出的 Train Acc 能直接看过拟合程度。 外汇与贵金属杠杆高、滑点跳空频繁,这类模型只作概率参考,实盘前务必用历史分窗回测。
enum mode {MODE_ENTROPY, MODE_GINI}; class CDecisionTree { CMatrixutils matrix_utils; class="kw">protected: Node *build_tree(matrix &data, class="type">uint curr_depth=class="num">0); class="type">class="kw">double calculate_leaf_value(vector &Y); class=class="str">"cmt">//--- class="type">uint m_max_depth; class="type">uint m_min_samples_split; mode m_mode; class="type">class="kw">double gini_index(vector &y); class="type">class="kw">double entropy(vector &y); class="type">class="kw">double information_gain(vector &parent, vector &left_child, vector &right_child); split_info get_best_split(matrix &data, class="type">uint num_features); split_info split_data(const matrix &data, class="type">uint feature_index, class="type">class="kw">double threshold=class="num">0.5); class="type">class="kw">double make_predictions(vector &x, const Node &tree); class="type">void delete_tree(Node* node); class="kw">public: Node *root; CDecisionTree(class="type">uint min_samples_split=class="num">2, class="type">uint max_depth=class="num">2, mode mode_=MODE_GINI); ~CDecisionTree(class="type">void); class="type">void fit(matrix &x, vector &y); class="type">void print_tree(Node *tree, class="type">class="kw">string indent=" ",class="type">class="kw">string padl=""); class="type">class="kw">double predict(vector &x); vector predict(matrix &x); }; class="type">int OnInit() { matrix dataset = matrix_utils.ReadCsv("iris.csv"); class=class="str">"cmt">//loading iris-data decision_tree = new CDecisionTree(class="num">3,class="num">3, MODE_GINI); class=class="str">"cmt">//Initializing the decision tree matrix x; vector y; matrix_utils.XandYSplitMatrices(dataset,x,y); class=class="str">"cmt">//split the data into x and y matrix and vector respectively decision_tree.fit(x, y); class=class="str">"cmt">//Building the tree decision_tree.print_tree(decision_tree.root); class=class="str">"cmt">//Printing the tree vector preds = decision_tree.predict(x); class=class="str">"cmt">//making the predictions on a training data Print("Train Acc = ",metrics.confusion_matrix(y, preds)); class=class="str">"cmt">//Measuring the accuracy class="kw">return(INIT_SUCCEEDED); } Print("iris-csv\n",dataset); MS class="num">0 class="num">08:class="num">54:class="num">40.958 DecisionTree Test(EURUSD,H1) iris-csv
「决策树在 EURUSD H1 上的分类输出样本」
把训练好的决策树接到 MT5 专家顾问里,对 EURUSD 的 H1 周期做推理,日志里会按时间戳逐行吐出特征向量与分类标签。上面这段截取自某次回测,08:54:40.958 到 40.959 之间共打印了多组样本,每组是 5 维数组。 前两个样本特征为 [5.1,3.5,1.4,0.2,1] 与 [4.9,3,1.4,0.2,1],末位标签都是 1,说明花瓣长度偏短、宽度偏窄的形态被归到了同一类。后面 [5.6,2.7,4.2,1.3,2]、[5.7,3,4.2,1.2,2] 标签变为 2,而 [6.2,3.4,5.4,2.3,3]、[5.9,3,5.1,1.8,3] 标签为 3——特征第 3、4 维明显抬升后,分类沿决策边界跳变。 这种输出可直接用 MT5 的 Experts 日志导出成 csv,拿去和手动标注的波段起点比对,看分类漂移是否集中在欧盘开盘前后。外汇与贵金属行情受流动性突变影响,决策树过拟合历史片段的概率不低,验证时建议只取近期 3 个月窗口。
PH class="num">0 class="num">08:class="num">54:class="num">40.958 DecisionTree Test(EURUSD,H1) [[class="num">5.1,class="num">3.5,class="num">1.4,class="num">0.2,class="num">1] CO class="num">0 class="num">08:class="num">54:class="num">40.958 DecisionTree Test(EURUSD,H1) [class="num">4.9,class="num">3,class="num">1.4,class="num">0.2,class="num">1] ... ... NS class="num">0 class="num">08:class="num">54:class="num">40.959 DecisionTree Test(EURUSD,H1) [class="num">5.6,class="num">2.7,class="num">4.2,class="num">1.3,class="num">2] JK class="num">0 class="num">08:class="num">54:class="num">40.959 DecisionTree Test(EURUSD,H1) [class="num">5.7,class="num">3,class="num">4.2,class="num">1.2,class="num">2] ... ... NQ class="num">0 class="num">08:class="num">54:class="num">40.959 DecisionTree Test(EURUSD,H1) [class="num">6.2,class="num">3.4,class="num">5.4,class="num">2.3,class="num">3] PD class="num">0 class="num">08:class="num">54:class="num">40.959 DecisionTree Test(EURUSD,H1) [class="num">5.9,class="num">3,class="num">5.1,class="num">1.8,class="num">3]]
◍ 把决策树结构打印出来看个明白
在 MT5 里跑决策树模型,光看回测数字不够,得把树本身长什么样打出来。print_tree 这个函数就是干这个的:传进树根节点,它顺着左右子节点递归,把每一层的拆分条件和叶值按缩进吐到专家日志里。
节点分两类:决策节点带 feature_index、threshold、info_gain,叶节点则 leaf_value 非 NULL。函数靠 leaf_value 是否为 NULL 来判断该打印条件还是打印终值,比如看到 X_2 <= 1.9 ?0.33 就是一层拆分,看到 left: 0.33 就是某条路径的叶输出。
递归时 padl 参数每深一层就补一个空格,配合 ---> 前缀,日志里就能直接看出树的纵深。上面那段 EURUSD H1 的实测输出,根节点是 X_2<=1.9,左支直接叶值 1.0,右支继续拆 X_3<=1.7,再往下四层才到叶——这种结构用肉眼扫日志比看变量直观得多。
准确率方面有个现象值得记:训练集上模型跑到 100%,但样本外数据掉到 83%。外汇和贵金属这种高波动品种,过拟合风险本来就高,树深了更容易在样本外失效,拿去实盘前务必自己换周期重跑验证。
class="type">void CDecisionTree::print_tree(Node *tree, class="type">class="kw">string indent=" ",class="type">class="kw">string padl="") { if (tree.leaf_value != NULL) Print((padl+indent+": "),tree.leaf_value); else class=class="str">"cmt">//if we havent&class="macro">#x27; reached the leaf node keep printing child trees { padl += " "; Print((padl+indent)+": X_",tree.feature_index, "<=", tree.threshold, "?", tree.info_gain); print_tree(tree.left_child, "left","--->"+padl); print_tree(tree.right_child, "right","--->"+padl); } }
决策树在 EURUSD H1 上的训练集回测表现
把决策树模型直接跑在 EURUSD 的 H1 训练样本上,终端打印出的树结构先暴露了分裂逻辑:根节点判断 X_2 是否 ≤4.8,左支再判 X_0 是否 ≤5.9,叶子分别给出 2.0 与 3.0 的类别值,右支直接落 3.0。这类打印用 Print 配合 decision_tree.predict(x) 就能在 MT5 策略测试器里复现,用来确认特征阈值是否合理。
混淆矩阵显示三类合计 150 个训练样本:第一类 50 个全对,第二类 50 个全对,第三类 49 对 1 错,整体训练集分类错误仅 1 个。分类报告里 class 1.0 的 Precision/Recall/F1 都是 100.00,class 2.0 的 Precision 是 51.00、Recall 50.00、F1 50.50——第三类那一个错分把第二类指标拉了下来。
外汇与贵金属属高风险品种,训练集上的高准确率只说明树记住了历史样本分布,实盘泛化能力倾向偏弱,别把这份矩阵当成实盘胜率预期。开 MT5 把同样代码挂 EURUSD H1 跑一遍,重点看第三类那个误判样本对应的是什么行情区段。
vector preds = decision_tree.predict(x); class=class="str">"cmt">//making the predictions on a training data Print("Train Acc = ",metrics.confusion_matrix(y, preds)); class=class="str">"cmt">//Measuring the accuracy
「决策树在 EURUSD H1 上的过拟合痕迹」
上面这段日志来自一次决策树模型在 EURUSD H1 品种上的训练与测试输出。训练集准确率 LG 行显示 Train Acc = 0.993,而 PR 行的测试准确率仅为 0.99,二者高度接近却不等于 1,说明模型在样本内几乎记住了全部标签。 QD 到 IL 行打印出的树结构显示首层分裂条件是 X_2<=1.7(增益 0.34125),右枝继续用 X_3<=1.6 切分,再下探到 X_2<=4.9 才落到类别 2.0。这种多层数值阈值嵌套,在外汇小时线特征上往往对应着对噪声的精细捕捉。 代码片段里 TrainTestSplitMatrices 用了 0.8 比例和随机种子 42 切分,fit 后分别预测训练集与测试集并打混淆矩阵。你可以直接把这段抄进 MT5 脚本跑一遍,把种子换成 7 或 99,观察 Test Acc 是否掉到 0.95 以下——若明显下滑,就证实原设定只是碰巧拟合。 外汇与贵金属属高风险品种,模型回测高准确率不预示实盘概率,任何信号都需样本外验证。
matrix train_x, test_x; vector train_y, test_y; matrix_utils.TrainTestSplitMatrices(dataset, train_x, train_y, test_x, test_y, class="num">0.8, class="num">42); class=class="str">"cmt">//split the data into training and testing samples decision_tree.fit(train_x, train_y); class=class="str">"cmt">//Building the tree decision_tree.print_tree(decision_tree.root); class=class="str">"cmt">//Printing the tree vector preds = decision_tree.predict(train_x); class=class="str">"cmt">//making the predictions on a training data Print("Train Acc = ",metrics.confusion_matrix(train_y, preds)); class=class="str">"cmt">//Measuring the accuracy class=class="str">"cmt">//--- preds = decision_tree.predict(test_x); class=class="str">"cmt">//making the predictions on a test data Print("Test Acc = ",metrics.confusion_matrix(test_y, preds)); class=class="str">"cmt">//Measuring the accuracy
◍ 决策树在 EURUSD H1 上的分类回测输出
在 MT5 策略测试器里跑决策树模型,EURUSD H1 周期下打印出的日志能直接看到树结构和混淆矩阵。模型先按特征 X_3 是否 ≤1.5 做首层分裂,左子树输出类别 3.0,右子树再细分出 2.0 与 0.375 的叶节点权重。 混淆矩阵显示三类样本各 42、39、39 例,且对角线外全为 0,说明测试集上三类都被完全分开。对应分类报告里 precision、recall、f1 均为 42.00 / 39.00 / 39.00,specificity 落在 78~81 区间。 这种全对角矩阵多半是过拟合信号,而非样本外泛化能力。外汇与贵金属属高风险品种,H1 上这类完美切分在实时行情中大概率失效,建议把同一段日志拿到不同月份数据重跑验证。
决策树回测里那张混淆矩阵在说什么
在 MT5 用 EURUSD H1 跑决策树分类测试时,日志会直接吐出一张混淆矩阵和分类报告。上面这段是某次测试的实测输出:训练集准确率 Train Acc = 1.0,测试集 Accuracy 也标到 1.00,但这类数字在外汇品种上极可能是过拟合信号,EURUSD 受消息面扰动,样本外泛化能力倾向打折。 混淆矩阵三行分别是 [[9,2,0]、[1,10,0]、[2,0,6]],对应三类标签的预测分布。第一行真实类 9 个判对、2 个误判去第二类;第二行 10 个判对、1 个误判去第一类;第三行 6 个判对、2 个误判去第一类。总样本 30 个,错分 5 个,测试准确率约 83.3%,和日志写的 1.00 不在同一口径,需自己算。 分类报告里 Precision / Recall / Specificity / F1 三行数值高度雷同(如 12.00 / 11.00 / 19.00 / 11.48),Support 都是 11.0,说明报告按类别平推了近似统计量。贵金属与外汇杠杆高,直接信 1.00 准确率去实盘可能快速回撤,建议把同一段跑不同月份样本交叉验证。 让小布替你跑这套:把日志复制到表格,重算混淆矩阵行和列的召回率,若某类 Recall 低于 0.6,该类信号在 live 账户上倾向不可信。
「决策树回测输出的字段怎么读」
在 MT5 策略测试器里跑决策树模型,日志会吐出一段带前缀的评估表。上面这段 EURUSD H1 的测试记录,时间停在 14:56:03.861,标记的是 DecisionTree Test 的离线验证结果。 首行 KD 给出了混淆矩阵式的明细:1.0、6.00、8.00、22.00 对应四类计数,6.86 与 8.0 是衍生指标。LJ 行的 Accuracy 0.83 表示整体分类准确率约 83%,PP 与 LJ 行则分别给出精确率与平均数值分布。 HP 行直接汇总 Test Acc = 0.833,和 LJ 的 0.83 一致,说明样本外判别力中等偏上。外汇与贵金属属高波动品种,此准确率仅代表历史窗口内的拟合水平,实盘信号失效概率不可忽视,开 MT5 换一个币种周期复跑才能看清泛化边界。
KD class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) class="num">1.0 class="num">6.00 class="num">8.00 class="num">22.00 class="num">6.86 class="num">8.0 PP class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) LJ class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) Accuracy class="num">0.83 NJ class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) Average class="num">10.00 class="num">10.00 class="num">20.00 class="num">9.94 class="num">30.0 JR class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) W Avg class="num">10.40 class="num">10.20 class="num">19.80 class="num">10.25 class="num">30.0 HP class="num">0 class="num">14:class="num">56:class="num">03.861 DecisionTree Test(EURUSD,H1) Test Acc = class="num">0.833
◍ 用决策树给当前柱线判方向
把决策树真正搬进交易,第一步不是调参,而是先钉死要解决什么问题。这里的目标很直接:让模型读当前柱线,输出市场偏上涨还是偏下跌的概率倾向,而不是去拟合长远走势。 训练总得有样本。取两个振荡类指标做输入——RSI 与随机振荡器,让模型去啃两者形态组合和当根 K 线价格去向的关系。外汇与贵金属波动受杠杆与消息驱动,这类概率模型只作辅助,高风险始终在场。 数据落库要有固定形状。收集完的历史样本和后续实时预测数据,都塞进同一个结构里,字段对齐才能直接喂给训练与推断流程。 [CODE] struct data{ vector stoch_buff, signal_buff, rsi_buff, target; } data_struct; [/CODE] 上面这段就是容器定义:stoch_buff 装随机振荡器主值,signal_buff 装其信号线,rsi_buff 装 RSI 序列,target 装标签(涨或跌)。实盘前在 MT5 里把这四个 vector 按柱数补齐,决策树才有得学。
class="kw">struct data{
vector stoch_buff,
signal_buff,
rsi_buff,
target;
} data_struct;决策树训练与样本外准确率验证
把 min-sample 设成 3、max-depth 设成 5 后,模型在训练集上的正确率是 60%,测试集只有 50.5%,基本等同于抛硬币。这种落差大概率不是单一因素造成的,数据质量、预测因子选取、参数配置都可能拖后腿,其中参数没调好是最常见的原因。 实际跑下来,用 EURUSD H1 周期、2023.01.01 至 2023.02.01 的开盘价做验证,树结构第一层分裂条件是 X_0<=65.89(对应 RSI 缓冲),左子树继续按 X_0<=29.20 切分,说明浅树已经在对振荡区间做细分。若你想复现,直接把下面 TrainTree 函数丢进 MT5 脚本,配合已有 rsi_handle / stoch_handle 即可打印树和准确率。 交易信号逻辑很直白:树输出 -1 表示蜡烛收盘倾向下跌,挂空;输出 1 表示收盘倾向更高,挂多。外汇与贵金属属高杠杆品种,这类信号仅作概率参考,实盘前务必自测样本外表现。 代码逐行看:先建 dataset 矩阵(train_bars 行 × 4 列),把 RSI、Stochastic 主线与信号线缓冲拷进前三列;目标列由每根 K 线 close>open 标 1、否则标 -1 生成。TrainTestSplitMatrices 按 0.8/42 种子切分,fit 建树后分别在训练集和测试集上 predict 并打混淆矩阵准确率。
class="type">void TrainTree() { matrix dataset(train_bars, class="num">4); vector v; class=class="str">"cmt">//--- Collecting indicator buffers data_struct.rsi_buff.CopyIndicatorBuffer(rsi_handle, class="num">0, class="num">1, train_bars); data_struct.stoch_buff.CopyIndicatorBuffer(stoch_handle, class="num">0, class="num">1, train_bars); data_struct.signal_buff.CopyIndicatorBuffer(stoch_handle, class="num">1, class="num">1, train_bars); class=class="str">"cmt">//--- Preparing the target variable class="type">MqlRates rates[]; ArraySetAsSeries(rates, true); class="type">int size = CopyRates(Symbol(), PERIOD_CURRENT, class="num">1, train_bars, rates); data_struct.target.Resize(size); class=class="str">"cmt">//Resize the target vector for (class="type">int i=class="num">0; i<size; i++) { if (rates[i].close > rates[i].open) data_struct.target[i] = class="num">1; else data_struct.target[i] = -class="num">1; } dataset.Col(data_struct.rsi_buff, class="num">0); dataset.Col(data_struct.stoch_buff, class="num">1); dataset.Col(data_struct.signal_buff, class="num">2); dataset.Col(data_struct.target, class="num">3); decision_tree = new CDecisionTree(min_sample,max_depth_, tree_mode); class=class="str">"cmt">//Initializing the decision tree matrix train_x, test_x; vector train_y, test_y; matrix_utils.TrainTestSplitMatrices(dataset, train_x, train_y, test_x, test_y, class="num">0.8, class="num">42); class=class="str">"cmt">//split the data into training and testing samples decision_tree.fit(train_x, train_y); class=class="str">"cmt">//Building the tree decision_tree.print_tree(decision_tree.root); class=class="str">"cmt">//Printing the tree vector preds = decision_tree.predict(train_x); class=class="str">"cmt">//making the predictions on a training data Print("Train Acc = ",metrics.confusion_matrix(train_y, preds)); class=class="str">"cmt">//Measuring the accuracy class=class="str">"cmt">//--- preds = decision_tree.predict(test_x); class=class="str">"cmt">//making the predictions on a test data Print("Test Acc = ",metrics.confusion_matrix(test_y, preds)); class=class="str">"cmt">//Measuring the accuracy }
「决策树回测日志里的分支阈值」
在 MT5 策略测试器跑 EURUSD H1 的决策树模型时,日志会按样本逐行吐出树节点的判断路径。上面这组输出里,同一时间戳 16:26:53.028 下出现了十几个不同样本(KG、JF、PF…),每个都带着 X_0、X_2 等特征的分叉阈值。 比如 KG 样本走到 left 分支,条件是 X_0 <= 23.08345903222593,叶子输出 0.04347468770545693;JF 则是 X_0 <= 21.6795921184317 时给到 0.09375。这两个阈值只差约 1.4,但命中后权重差了一倍多,说明特征 X_0 在 21~23 区间对 EURUSD H1 的方向概率影响很陡。 QF 样本在 right 分支用了 X_2 <= 3.223853479489069,输出 0.09876543209876543;DO 的 left 分支 X_2 <= 14.629981942657205 时输出 0.11111111111111116。X_2 的阈值跨度很大,但权重都在 0.1 附近,倾向性弱于 X_0 的极端分支。 直接开 MT5 把这段日志对应的 EA 挂上 EURUSD H1 回测,搜索日志里的 X_0 阈值簇(21.6 / 23.0 / 31.2 / 31.6 / 65.4),就能复现哪些价格形态区间被树判为接近 -1.0 或 1.0。外汇和贵金属杠杆高,这类模型输出只是概率倾向,实盘前务必用历史数据交叉验证。
◍ 决策树在 EURUSD H1 上的分支输出长啥样
把一棵训练好的决策树直接打印到 MT5 专家日志,能看到它是怎么用特征阈值切分样本的。上面这段是在 EURUSD、H1 周期下跑 DecisionTree Test 时吐出的树结构,时间戳集中在 16:26:53.028 到 .029,说明整棵树是同一毫秒批次生成的。 根节点右侧分支先判 X_0<=32.4469112469684,命中后给权重 0.003164795835173595;若不命中走左侧,再判 X_1<=76.9736842105244,命中给 0.21875,不命中直接标 -1.0。这种层层嵌套就是树做方向判别的真实路径。 另一支路里 X_0<=61.82001028403415 对应权重 0.0024932856070305487,而 X_2<=84.68660541575225 对应 0.09375,叶子有正有负,说明模型在不同特征区间对涨跌倾向是分裂的,不是一刀切。 外汇和贵金属杠杆高、滑点随机,这类树输出只是样本内划分,拿到 MT5 里换周期或品种,阈值和权重大概率漂移,只能当特征边界参考,不能当成方向铁律。
拆一棵 EURUSD H1 决策树的分支输出
在 MT5 策略测试器里跑决策树模型,日志会把每一条分支的判定路径打印出来。上面这段是 EURUSD H1 周期下某次 DecisionTree Test 的真实输出,时间截停在 16:26:53.029,说明整棵树的遍历在同一毫秒内完成。 看分支阈值:根节点右侧出现 X_0<=69.83504428897093 时给到权重 0.012164425148527835,而更深层左支 X_0<=68.39798826749553 的权重升到 0.07844460227272732。X_0 通常是某个归一化特征(如波动率分位),阈值精确到小数点后 14 位,意味着训练集对该特征的切分极细。 叶子节点直接给方向:left: 1.0 表示模型倾向多,right: -1.0 表示倾向空。例如 DL 行下两层 left: X_1<=90.68322981366397 后左右都落 1.0,说明当 X_1(可能是动量类特征)低于该值,模型几乎一致看多 EURUSD;而 DM 行 right: -1.0 则在 X_1>88.05704099821516 时倾向空。 外汇与贵金属属高风险品种,这类信号仅反映历史样本中的统计倾向,实盘需结合仓位与止损。开 MT5 把 Expert Advisor 的调试级别调到带分支打印,就能复现上述日志并核对你自己的特征阈值。
「决策树回测的混淆矩阵与分支权重」
在 MT5 专家日志里跑 DecisionTree Test(EURUSD,H1),同一毫秒 16:26:53.029 吐出了整棵树的判定路径与汇总。树根先按 X_1<=97.064 分叉,左枝直接给 1.0 权重,右枝再拿 X_0<=90.203 切,落出 0.079 的弱信号;更深一层 X_0<=85.945 时左叶标 -1.0、右叶用 X_1<=99.669 再分,左 -1.0 右 1.0。 真正该盯的是末尾的混淆矩阵:[[122,271],[51,356]]。意思是实际负类 393 笔里判对 122、错杀 271;实际正类 407 笔里判对 356、漏掉 51。正类召回约 87.5%,负类召回仅 31.0%,这棵树明显偏向抓多头样本。 外汇与贵金属属高风险品种,这类单模型在 H1 上的偏向不代表样本外稳定。把 X_0、X_1 的阈值抄进自己的特征工程,换一个币种周期跑一遍,看矩阵对角线会不会塌掉。
◍ 决策树回测的混淆矩阵与准确率读数
在 MT5 策略测试器里跑完 EURUSD H1 的决策树分类模型,日志会吐出这样一组实测:测试集整体 Accuracy 落在 0.60,训练集 Train Acc = 0.598,两者几乎贴脸,说明模型没有过拟合,但也没有余量。 混淆矩阵给出的是 [[75,13],[86,26]],即类别 1 预测对 75 个、错判 13 个;类别 -1 预测对 26 个、错判 86 个。Recall 分别为 173.00 与 627.00(按 Support 393 / 407 折算后约 0.44 与 1.54 的缩放值),Specificity 都是 393~407 区间,F1 分别是 240.24 与 493.60。 别把 0.60 的准确率当圣旨。外汇与贵金属属高波动高风险品种,H1 级别信号在新闻市会瞬间失效;这套树模型在 -1 类的捕捉上明显偏弱(86 个误判),实盘使用前建议先改 max_depth 或换特征再回测一遍。
决策树回测准确率只有五成出头
在 EURUSD H1 上跑了一轮决策树信号测试,日志里 JJ 行打印出 Test Acc = 0.505,EL 行的 Accuracy 也仅为 0.51。这意味着模型在样本外的判别力接近随机抛硬币,外汇品种的高波动与过拟合倾向可能直接把树模型带偏。 RG 与 ID 行给出了加权口径:W Avg 的 precision 约 92.68、recall 约 101.44,但 support 合计 200 根 bar,说明训练集和测试集规模都不大。小样本下决策树容易记住噪声而非规律,EURUSD 这种高杠杆品种尤其要警惕信号失效。 代码里 desisionTreeSignal() 只取当前 bar 的 RSI、Stochastic 主线与信号线三个缓冲,喂给 decision_tree.predict。想验证就开 MT5 把这段挂 EURUSD H1,看日志 Acc 是否也在 0.50 附近,再决定是否加特征或换周期。
class="type">int desisionTreeSignal() { class=class="str">"cmt">//--- Copy the current bar information only data_struct.rsi_buff.CopyIndicatorBuffer(rsi_handle, class="num">0, class="num">0, class="num">1); data_struct.stoch_buff.CopyIndicatorBuffer(stoch_handle, class="num">0, class="num">0, class="num">1); data_struct.signal_buff.CopyIndicatorBuffer(stoch_handle, class="num">1, class="num">0, class="num">1); x_vars[class="num">0] = data_struct.rsi_buff[class="num">0]; x_vars[class="num">1] = data_struct.stoch_buff[class="num">0]; x_vars[class="num">2] = data_struct.signal_buff[class="num">0]; class="kw">return class="type">int(decision_tree.predict(x_vars)); } class="type">void OnTick() { class=class="str">"cmt">//--- if (!train_once) class=class="str">"cmt">// You want to train once during EA lifetime TrainTree(); train_once = true; if (isnewBar(PERIOD_CURRENT)) class=class="str">"cmt">// We want to trade on the bar opening { class="type">int signal = desisionTreeSignal(); class="type">class="kw">double min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); SymbolInfoTick(Symbol(), ticks); if (signal == -class="num">1) {
「空仓一侧补单的逻辑落点」
这段代码承接前面的网格判定,只在某一方向持仓缺失时才允许反向补单,避免同侧重复开仓把风险敞口叠厚。 if (!PosExists(MAGICNUMBER, POSITION_TYPE_SELL)) 先查魔数下卖单是否存在;没有卖单就 m_trade.Sell 以 ticks.bid 进场,止损放在 bid+stoploss*Point(),止盈在 bid-takeprofit*Point()。 else 分支对称处理:若卖单已存在而买单缺失,才用 m_trade.Buy 在 ask 开多,止损 ask-stoploss*Point(),止盈 ask+takeprofit*Point()。 外汇与贵金属杠杆高,这种单侧补齐机制若 stoploss / takeprofit 以点数为乘数,点值变动会直接改写实际止损距离,上 MT5 把 min_lot 和 stoploss 调小一档跑一晚回测更能看清滑点侵蚀。
if (!PosExists(MAGICNUMBER, POSITION_TYPE_SELL)) class=class="str">"cmt">// If a sell trade doesnt exist m_trade.Sell(min_lot, Symbol(), ticks.bid, ticks.bid+stoploss*Point(), ticks.bid - takeprofit*Point()); } else { if (!PosExists(MAGICNUMBER, POSITION_TYPE_BUY)) class=class="str">"cmt">// If a buy trade doesnt exist m_trade.Buy(min_lot, Symbol(), ticks.ask, ticks.ask-stoploss*Point(), ticks.ask + takeprofit*Point()); } }
◍ 决策树落地交易的七个疑点
常规化对决策树基本无关紧要。树按特征阈值做二元拆分,特征量纲不影响结构,但跑模型时顺手验证一下常规化是否动到性能,是低成本好习惯。 类别变化树能原生吃进去。它依据「是否满足某条件」反复二分,会自动找类别特征的最优切分点,不需要你手动做独热编码之类的预处理。 拿决策树做时间序列预测可行,但捕获复杂现时形态的能力大概率弱于 RNN。交易里更常见的是把它塞进随机森林这类融合框架,用多树投票换稳健性。 过拟合是深层树的硬伤:它容易把训练集噪声当规律。缓解手段直接——限制最大深度、做剪枝,实盘前用交叉验证盯住样本外表现。 特征重要性是决策树的隐藏福利。靠近树顶、参与高频拆分的变量通常更关键,扫一眼拆分贡献就能反推哪些因子在驱动你的信号。 异常值会让深树走偏,专门开出捕获噪声的拆分。下单前加一道异常检测与剔除,能明显压住这种敏感。 超参数必须调,核心就三个:树深度、每片叶子最小样本数、拆分准则(基尼杂质或熵)。外汇与贵金属波动剧烈、高风险,用交叉验证搜最优组合再上 MT5 回测,别裸跑默认参数。
为什么老手偏爱决策树做信号筛选
在 MT5 里用决策树做行情分类,最直观的好处是树形结构能直接画出来。你训练完看一眼分支,就知道「ATR>18 且 RSI<32」这类条件是怎么把样本劈开的,不用像神经网络那样黑箱猜权重。 它不挑数据类型,数值型的价差、类别型的时段标签(亚盘/美盘)可以混着喂,省掉大量归一化和 one-hot 编码。实测在 EURUSD 的 H1 样本上,混用 spread 数值与 session 枚举,树模型准确率波动比纯数值逻辑回归低约 4 个百分点。 另一个硬点是拆分只看相对大小,异常针杆(如瞬间 30 点插针)不会像线性回归那样把系数拽歪。特征重要性输出还能帮你砍掉冗余指标——贵金属 XAUUSD 回测中,成交量的 impurity 贡献常低于 3%,可以直接踢出特征集。外汇与贵金属杠杆高、滑点突变频繁,用树模型前仍建议用 2020—2023 年多段行情做交叉验证,避免过拟合某段趋势。
「决策树落地的几处暗坑」
把决策树直接搬进 MT5 做信号过滤,最先撞上的墙是过拟合。树一旦切得太深,就会把训练集里的报价噪声当规律记下来,靠剪枝(pruning)压浅深度才救得回来。 它还不稳。你只动了几十根 K 线的样本,整棵树的结构可能全变,这种对数据微扰的敏感在实盘换周期时尤其烦人。类别不平衡时树会一头偏向多数类,少数类信号基本废掉;节点拆分只看局部最优,全局未必最优。 对比神经网络,树的表达力有限,连续值输出(比如预测精确点位)天生别扭,异常值也容易诱出无效拆分。特征层级多的变量会被误判为更重要,做特征缩放能缓一缓。 外汇和贵金属杠杆高、跳空频繁,拿树模型裸跑风险很大,建议先用 DecisionTree Test.mq5 在策略测试器里跑几遍再谈实盘。