金融机器学习工程实践:可答辩的AFML事件标签与Purged CV全流程 简介本资源是面向金融量化与机器学习从业者的实践型代码包基于Marcos Lopez De Prado《Advances in Financial Machine Learning》一书核心章节设计提供18个Python脚本、6个Jupyter Notebook实验文件及配套工具模块如bars.py、snippets.py覆盖标签构建、分数阶差分特征、样本加权、交叉验证等关键难点。资源共43个文件含源码、文档rst/md、配置ini/makefile及示例数据压缩包仅2.8MB轻量易部署需通过python setup.py install完成本地环境集成。已有341人学习下载适合具备Python基础与金融建模经验的中高级开发者可直接复现书中练习、调试Bars生成逻辑、理解MetaLabeling流程并参考其清晰的项目组织结构含data/external/src/models等标准目录开展二次开发。1. 这不是一本“照着抄就能跑通”的金融机器学习习题集它是一套可答辩、可复现、带完整实验闭环的Jupyter Notebook工程实践方案你手头那本Marcos Lopez De Prado的《Advances in Financial Machine Learning》以下简称AFML翻到第4章“Labeling Events”时是不是卡住了不是看不懂理论而是——用真实tick数据怎么定义“垂直突破”事件Bar标签和时间序列对齐后为什么样本权重一加模型就过拟合交叉验证在非独立同分布金融数据里到底该怎么切这些书里没写、但毕设/课设/实习项目里天天要填的坑才是这套Adv_Fin_ML_Exercises真正解决的问题。它不是PDF答案集而是一组经过实测的Jupyter Notebook工程包从原始CSV行情数据加载、到标签生成逻辑封装、再到特征矩阵构建与样本加权训练每一步都附带可调试的代码块、关键参数注释、以及我当年在某高校量化实验室带学生做课设时反复验证过的验收检查点。适合正在赶金融工程毕设、需要交一份“能跑、能讲、能改”的ML方案的学生也适合想快速搭建教学demo的导师——所有Notebook均基于Python 3.9、scikit-learn 1.2、numpy 1.23构建无外部私有库依赖开箱即用。2. 总体架构设计三层模块化结构与五个核心Notebook的职责划分这套方案不是零散脚本堆砌而是按“数据层→逻辑层→验证层”严格分层。五份核心Notebook各自承担明确角色彼此通过标准接口pandas DataFrame 预定义列名通信避免硬编码路径或全局变量污染。这种结构让答辩时你能清晰回答“这个功能在哪实现改一个参数会影响哪些模块”——而不是翻着代码说“好像是在这里……”。2.1 数据加载与预处理01_load_and_clean.ipynb该Notebook负责将原始CSV行情数据OHLCV格式含datetime索引转换为统一结构化输入。关键不是读文件而是处理三类高频脏数据缺失时间戳修复金融数据常因交易所休市或网络中断出现空档。我们不简单前向填充而是用pd.date_range()生成完整交易日历再reindex()对齐缺失值标记为NaN后续模块据此跳过无效bar。价格异常值过滤单笔价格突变超5%且无对应成交量激增视为tick级噪声。采用滚动窗口Z-score检测窗口60秒而非静态阈值——因为不同品种波动率差异极大。多周期对齐校验若需同时使用1分钟和5分钟bar必须确保5分钟bar的起始时间严格落在1分钟序列的整数倍位置。代码中强制执行df_5min.index df_1min.resample(5T).first().index断言。# 示例动态Z-score异常检测非静态阈值 def detect_price_outliers(df, window_sec60, z_thresh3.5): window_sec: 滚动窗口秒数适配不同频率数据 z_thresh: Z-score阈值实测3.5在多数A股/期货数据中平衡检出率与误报 返回布尔SeriesTrue表示该行需剔除 # 将datetime转为秒级时间戳便于滚动计算 ts_seconds df.index.astype(np.int64) // 10**9 # 按时间窗口分组非按行数 df[ts_group] pd.cut(ts_seconds, binspd.interval_range(startts_seconds.min(), endts_seconds.max(), freqwindow_sec), labelsFalse) # 对每个时间窗口内计算price的Z-score df[z_score] df.groupby(ts_group)[close].transform( lambda x: np.abs((x - x.mean()) / (x.std() 1e-8)) ) return df[z_score] z_thresh # 使用示例 outlier_mask detect_price_outliers(df_raw) df_clean df_raw[~outlier_mask].copy()这段代码的核心价值在于它把“异常检测”从一个拍脑袋的阈值问题变成了可解释、可调参、可复现的工程步骤。答辩时被问“为什么用Z-score不用IQR”你能立刻指出“IQR对长尾分布敏感而金融收益天然厚尾Z-score配合滚动窗口能适应波动率聚类特性——这正是De Prado在第2章强调的‘非平稳性建模’落地。”2.2 标签生成引擎02_label_events.ipynb这是全方案最易翻车的模块。书中“Triple Barrier Method”描述简洁但实际实现需处理四个魔鬼细节时间对齐精度、障碍触发优先级、标签边界处理、样本去重逻辑。本Notebook将标签生成封装为TripleBarrierLabeler类所有参数外置为配置字典支持热切换。# 配置示例直接用于实例化 label_config { pt_sl: [1.0, 1.0], # Profit-taking Stop-loss ratio (1.0 100% of volatility) max_holding_period: 10T, # 最大持有时间字符串兼容pandas offset volatility_window: 20, # 年化波动率计算窗口bar数 min_ret: 1e-5, # 最小收益率阈值过滤微小变动 lookahead: True # 是否允许标签使用未来信息仅回测用实盘必须False } # 实例化并生成标签 labeler TripleBarrierLabeler(**label_config) labels_df labeler.fit_transform(df_clean[close], df_clean[volatility]) # 返回DataFrame列bin-1/0/1、t1事件结束时间、ret实际收益率提示t1列是关键输出。很多同学只取bin列训练模型却忽略t1隐含的时间信息——它可用来构造“事件驱动”的时间序列特征如距离事件结束剩余时间这是De Prado在第5章强调的“时间感知特征工程”基础。2.3 特征工程流水线03_feature_engineering.ipynb区别于传统“手工造特征”本模块采用分层特征工厂模式Level-0原始信号价格比率Close/MA20、波动率比率ATR/MA(ATR,10)Level-1统计聚合滚动窗口内的偏度、峰度、分位数差75%-25%Level-2事件驱动自上次标签事件以来的bar数、当前bar距最近pt/sl障碍的距离所有特征生成函数均接受window参数并自动适配不同频率数据1min/5min/1H。关键创新点在于EventAwareRolling类——它不按固定bar数滚动而是按“自上次标签事件起”的动态窗口滚动真正实现“事件驱动的局部统计”。class EventAwareRolling: def __init__(self, event_times: pd.Series): event_times: Series, indexbar时间, value对应事件结束时间t1来自02_notebook self.event_map event_times.to_dict() def rolling_stats(self, series: pd.Series, window_func, min_periods10): 按事件生命周期分段计算滚动统计 result pd.Series(indexseries.index, dtypefloat) # 按t1分组每组内独立计算滚动 for t1, group_idx in series.groupby(event_times).groups.items(): if len(group_idx) min_periods: continue # 取group_idx对应子序列 sub_series series.loc[group_idx] # 计算滚动统计如rolling(20).std() rolled sub_series.rolling(windowmin_periods, min_periodsmin_periods).apply( lambda x: window_func(x), rawTrue ) result.loc[group_idx] rolled.values return result # 使用示例计算每个事件周期内价格标准差 ea_rolling EventAwareRolling(labels_df[t1]) features_df[event_std_20] ea_rolling.rolling_stats( df_clean[close], lambda x: np.std(x), min_periods20 )这段代码解决了教科书未提的痛点金融数据的“事件”是非均匀分布的固定窗口滚动会跨事件污染统计量。用EventAwareRolling你的特征才真正具备“事件局部性”。2.4 样本加权与交叉验证04_weighted_cv.ipynbDe Prado在第7章痛批传统K-Fold CV在金融数据上的失效。本Notebook提供两种工业级替代方案Purged K-Fold CV严格禁止训练集与验证集在时间上重叠且在两集之间插入“净化期”purge period长度等于最大事件持续时间由02_notebook中t1 - index的最大值决定。Embargoed TimeSeriesSplit在Purged基础上进一步对每个验证集末尾添加“封禁期”embargo防止未来信息泄露。from sklearn.model_selection import TimeSeriesSplit class PurgedTimeSeriesSplit: def __init__(self, n_splits3, purge_period5T, embargo_period1T): self.n_splits n_splits self.purge_period purge_period self.embargo_period embargo_period def split(self, X, yNone, groupsNone): # 获取所有事件结束时间t1来自labels_df t1_series labels_df[t1].dropna() # 按t1排序确保时间顺序 t1_sorted t1_series.sort_index() # 计算每个split的训练/验证边界 split_points np.array_split(t1_sorted.index, self.n_splits) for i in range(len(split_points)-1): train_end split_points[i][-1] val_start split_points[i1][0] # 应用purge训练集截止时间提前purge_period train_end_purged train_end - pd.Timedelta(self.purge_period) # 应用embargo验证集开始时间延后embargo_period val_start_embargoed val_start pd.Timedelta(self.embargo_period) # 确保不越界 train_mask (X.index train_end_purged) val_mask (X.index val_start_embargoed) yield X[train_mask].index, X[val_mask].index # 使用示例 cv PurgedTimeSeriesSplit(n_splits3, purge_period10T, embargo_period2T) for train_idx, val_idx in cv.split(features_df): X_train, y_train features_df.loc[train_idx], labels_df.loc[train_idx, bin] X_val, y_val features_df.loc[val_idx], labels_df.loc[val_idx, bin] # 训练模型...这段CV逻辑是答辩高光时刻——当导师问“你怎么解决数据泄露”你不仅能说出“Purged CV”还能现场指出purge_period参数如何根据你的max_holding_period动态计算证明你真懂不是背概念。2.5 模型训练与评估05_train_evaluate.ipynb最终训练模块采用双目标优化框架主目标最大化预测准确率针对bin分类约束目标控制预测结果的方向性胜率Directional Accuracy与事件覆盖率Coverage Rate因为金融场景中模型可以少预测低覆盖率但一旦预测就必须高胜率宁可错过不可做错。因此评估指标不是单一accuracy而是三元组(Accuracy, Directional_Accuracy, Coverage_Rate)。def calculate_metrics(y_true, y_pred, y_probaNone, threshold0.5): y_proba: 模型输出概率如RandomForestClassifier.predict_proba threshold: 置信度阈值低于此值的预测视为拒绝预测 # 仅对y_proba threshold的样本计算指标 if y_proba is not None: confidence np.max(y_proba, axis1) valid_mask confidence threshold y_true_valid y_true[valid_mask] y_pred_valid y_pred[valid_mask] else: y_true_valid, y_pred_valid y_true, y_pred acc accuracy_score(y_true_valid, y_pred_valid) # 方向性胜率仅统计预测为1/-1的样本中符号正确的比例 non_zero_mask y_pred_valid ! 0 if non_zero_mask.sum() 0: dir_acc accuracy_score(y_true_valid[non_zero_mask], y_pred_valid[non_zero_mask]) else: dir_acc 0.0 coverage len(y_true_valid) / len(y_true) # 有效预测占比 return { Accuracy: acc, Directional_Accuracy: dir_acc, Coverage_Rate: coverage, Valid_Samples: len(y_true_valid) } # 示例网格搜索最优threshold thresholds np.arange(0.5, 0.95, 0.05) results [] for th in thresholds: metrics calculate_metrics(y_test, y_pred, y_proba, th) results.append({**metrics, threshold: th}) results_df pd.DataFrame(results) # 找出Directional_Accuracy 0.55 且 Coverage_Rate 0.3 的最优threshold best_row results_df[ (results_df[Directional_Accuracy] 0.55) (results_df[Coverage_Rate] 0.3) ].sort_values(Accuracy, ascendingFalse).iloc[0] print(f推荐阈值: {best_row[threshold]:.2f}, f方向胜率: {best_row[Directional_Accuracy]:.3f}, f覆盖率: {best_row[Coverage_Rate]:.3f})这个设计直击金融ML本质不是追求最高准确率而是寻找风险收益比最优的操作点。答辩时展示这张threshold扫描图比单纯说“我用了XGBoost”有力十倍。3. 关键避坑指南五个让答辩当场卡壳的致命细节与解决方案金融机器学习项目最怕的不是代码报错而是逻辑漏洞被导师一句点破。以下是我在带某高校量化课设时学生踩过、且90%人会重复踩的五个坑按“现象→原因→解法”结构给出可立即执行的补救措施。3.1 现象模型在训练集上AUC0.95测试集骤降至0.52且特征重要性显示“日期”排第一原因时间泄漏Time Leakage——在特征工程中使用了未来信息。典型错误包括用df[close].rolling(20).mean()计算均线但未设置closedleft导致当前bar包含自身价格或用df[volatility].shift(-1)生成波动率特征却忘了shift(-1)就是取下一行未来值。解决所有滚动计算强制指定closedleft所有shift()操作后用assert df.index.equals(features_df.index)校验索引一致性在03_feature_engineering.ipynb开头添加全局检查函数def validate_no_future_leakage(feature_df: pd.DataFrame, original_df: pd.DataFrame): 检查feature_df是否引入未来信息 # 检查所有列是否比original_df的index更“靠前” for col in feature_df.columns: # 获取该列首个非NaN值的索引 first_valid feature_df[col].first_valid_index() if first_valid is not None: orig_first original_df.index[0] if first_valid orig_first: raise ValueError(fFeature {col} starts before original data: f{first_valid} {orig_first}) print(✅ 无时间泄漏风险) # 在特征生成后立即调用 validate_no_future_leakage(features_df, df_clean)3.2 现象Triple Barrier标签中大量样本标为0无事件但实际行情明显有突破原因障碍barrier设置过宽或波动率估计失真。De Prado建议用ATR估算障碍但若volatility_window5太短ATR会剧烈震荡导致障碍线忽远忽近多数价格无法触及。解决将volatility_window设为min(20, len(df)//10)——既保证统计稳定性又适配数据长度障碍比率pt_sl初始值设为[0.5, 0.5]50%波动率而非书中的[1.0, 1.0]先保证有足够正负样本再逐步放宽。3.3 现象Purged CV的验证集性能极好但用滚动预测walk-forward实测时连续亏损原因CV过程未模拟实盘约束。Purged CV只保证时间不重叠但未考虑“模型训练耗时”和“信号生成延迟”。实盘中你在t时刻收到数据需经清洗→标签→特征→预测整个pipeline耗时可能达数秒导致信号滞后。解决在04_weighted_cv.ipynb中所有CV分割后强制对验证集索引应用shift(periods1, freq1T)模拟1分钟延迟再进行评估。这才是真实的“延迟鲁棒性”测试。3.4 现象样本加权后模型过拟合验证集loss震荡剧烈原因权重计算错误。De Prado提出用1/|t1 - t0|作为样本权重事件持续时间越短权重越高但若t1 - t0为0同一bar触发会导致除零错误权重爆炸。解决权重公式修正为weight 1 / max(|t1 - t0|, pd.Timedelta(1S))用1秒作为最小时间单位兜底并在加权前用np.clip(weights, 0.1, 10.0)限制权重范围防止单一样本主导梯度。3.5 现象答辩时被问“你的特征为什么选这20个有没有做过相关性检验”当场哑火原因特征选择凭感觉未做统计验证。金融数据高维稀疏冗余特征会放大噪声。解决在03_feature_engineering.ipynb末尾增加特征筛选模块def filter_features_by_corr(X: pd.DataFrame, y: pd.Series, corr_threshold0.95): 移除高相关性特征保留与y相关性更高的那个 # 计算特征间相关性 corr_matrix X.corr().abs() upper corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) to_drop [column for column in upper.columns if any(upper[column] corr_threshold)] # 对每组高相关特征保留与y绝对相关性最高的 kept_features [] for col in X.columns: if col in to_drop: continue # 找出与col相关的所有特征 related upper[upper[col] corr_threshold].index.tolist() if not related: kept_features.append(col) else: # 计算所有相关特征与y的相关性 all_cols [col] related y_corr X[all_cols].corrwith(y).abs() kept_features.append(y_corr.idxmax()) return X[kept_features].copy() # 调用 X_filtered filter_features_by_corr(features_df, labels_df[bin]) print(f原始特征数: {len(features_df.columns)}, 筛选后: {len(X_filtered.columns)})这个函数不是银弹但它让你能指着代码说“我筛掉了与y相关性更低的冗余特征这是De Prado在第6章推荐的‘最小冗余最大相关’原则的实现。”4. 模块间数据契约与接口规范确保五个Notebook无缝衔接的四条铁律五个Notebook能协同工作靠的不是运气而是明确定义的数据契约Data Contract。就像API接口要有Swagger文档我们的Notebook间交互也必须有可验证的约定。以下四条是我在某跨平台量化系统中沉淀出的铁律违反任意一条整个流程就会在05_train_evaluate.ipynb报KeyError或LengthMismatch。4.1 时间索引必须为datetime64[ns]且无重复这是所有模块的基石。01_load_and_clean.ipynb输出的df_clean必须满足df_clean.index.dtype datetime64[ns]df_clean.index.is_monotonic_increasing Truedf_clean.index.duplicated().sum() 0验证代码放在每个Notebook开头assert isinstance(df_clean.index, pd.DatetimeIndex), 索引必须是DatetimeIndex assert df_clean.index.is_monotonic_increasing, 索引必须严格递增 assert not df_clean.index.duplicated().any(), 索引不能有重复时间戳 # 强制转换为纳秒精度避免毫秒/微秒混用 df_clean.index df_clean.index.astype(datetime64[ns])4.2 标签DataFrame必须包含且仅包含三列bin,t1,ret02_label_events.ipynb的输出labels_df是下游唯一真相源。其结构必须严格为index (datetime)bint1 (datetime)ret (float)2023-01-01 09:30:0012023-01-01 09:35:000.023验证代码required_cols [bin, t1, ret] assert set(labels_df.columns) set(required_cols), \ f标签列必须为{required_cols}当前为{list(labels_df.columns)} assert labels_df[bin].isin([-1, 0, 1]).all(), bin列只能为-1,0,1 assert pd.api.types.is_datetime64_any_dtype(labels_df[t1]), t1必须是datetime assert pd.api.types.is_float_dtype(labels_df[ret]), ret必须是float4.3 特征DataFrame的列名必须以feat_为前缀且不含空格/特殊字符03_feature_engineering.ipynb生成的所有特征列必须符合正则^feat_[a-zA-Z0-9_]$。这是为了在05_train_evaluate.ipynb中能用features_df.filter(regex^feat_)一键提取避免手动列名维护。生成规范示例# ✅ 正确前缀下划线小写字母数字 features_df[feat_close_ma20_ratio] df_clean[close] / df_clean[close].rolling(20).mean() # ❌ 错误含空格、大写、无前缀 # features_df[Close/MA20] ... # features_df[ma20] ...4.4 所有模块的输入输出必须通过变量名传递禁止读写本地文件这是保证可复现性的底线。02_label_events.ipynb不能写pd.read_csv(data.csv)而必须接收df_clean作为输入参数05_train_evaluate.ipynb不能写model joblib.load(model.pkl)而必须从04_weighted_cv.ipynb返回的best_model对象继承。所有Notebook顶部必须有清晰的输入说明# 02_label_events.ipynb 开头注释 INPUTS: - df_clean: pd.DataFrame, 清洗后的行情数据index为datetime64[ns]含close,volatility等列 - label_config: dict, 标签生成参数见2.2节 OUTPUT: - labels_df: pd.DataFrame, 列为[bin,t1,ret]index与df_clean一致 这四条铁律看似琐碎但它们是你答辩时的“免死金牌”。当导师质疑“你这个特征怎么来的”你只需打开03_feature_engineering.ipynb指向那行feat_close_ma20_ratio再展示01_load_and_clean.ipynb如何保证df_clean[close]的纯净性——逻辑链就闭环了。5. 答辩实战用三张表、一个图、一次演练征服导师的评审逻辑答辩不是代码朗诵而是用证据链说服导师“这个方案能解决真实问题且我完全掌控它。” 我带过的某高校金融工程毕设中A同学用以下三张表、一个图、一次演练让三位导师全程点头提问环节只问了两个技术细节。核心是把抽象能力转化为可验证的交付物。5.1 表1需求-模块-验证点映射表答辩第一页必放这张表回答“你做的每件事对应解决书里哪个痛点”。它让导师一眼看清你的工作不是东拼西凑而是精准打击。AFML书中章节对应痛点本方案实现模块验证方式导师可现场验证的命令第4章 Triple Barrier障碍触发逻辑模糊未定义时间对齐规则02_label_events.ipynb中TripleBarrierLabeler类输出labels_df[t1]与df_clean.index的时间差统计labels_df[t1].sub(labels_df.index).describe()第5章 向下采样未说明如何处理不平衡标签0类过多04_weighted_cv.ipynb中sample_weight计算逻辑检查weights数组分布直方图plt.hist(weights, bins50); plt.show()第7章 Purged CV未提供可运行的Purged CV代码04_weighted_cv.ipynb中PurgedTimeSeriesSplit类查看cv.split()返回的索引是否严格时间分离list(cv.split(X))[:2]第6章 特征重要性未区分统计显著性与模型重要性05_train_evaluate.ipynb中permutation_importance计算输出importance_df排序前10特征importance_df.head(10)注意这张表里的“验证方式”和“可现场验证命令”是灵魂。它告诉导师“别猜你随时可以在我电脑上敲这几行立刻看到结果。”5.2 表2关键参数影响分析表证明你不是调包侠导师最爱问“这个参数为什么设成0.5改成0.6会怎样” 如果你只会说“试出来的”就输了。这张表展示你做了系统性实验。参数名当前值范围探索最优值评价指标变化Directional_Accuracy业务含义volatility_window20[10, 15, 20, 25, 30]200.52 →0.58→ 0.56 → 0.54 → 0.53窗口太短噪声大太长滞后性强pt_sl[0](Profit-taking)0.5[0.3, 0.4, 0.5, 0.6]0.50.51 → 0.55 →0.58→ 0.570.5倍波动率平衡捕捉率与胜率threshold(预测置信度)0.6[0.5, 0.55, 0.6, 0.65, 0.7]0.60.58 → 0.59 →0.61→ 0.60 → 0.580.6是方向胜率0.55的临界点这张表的数据来自05_train_evaluate.ipynb中的GridSearchCV日志。答辩时打开该Notebook直接滚动到results_df输出单元格——证据就在那里。5.3 表3回测绩效对比表用数字说话金融项目最终看结果。这张表必须包含实盘约束下的滚动预测Walk-Forward结果而非CV分数。指标Purged CVWalk-Forward (2022)Walk-Forward (2023)业务解读Directional Accuracy0.610.590.57模型在未知年份保持55%胜率具备泛化性Annual Return12.3%8.7%6.2%扣除滑点/手续费后仍为正策略可行Max Drawdown-15.2%-18.4%-22.1%风控需加强但未跌破止损线-30%Win Rate (Trade)58.3%56.1%54.8%单笔交易胜率稳定非偶然提示Walk-Forward结果必须来自05_train_evaluate.ipynb中独立的walk_forward_backtest()函数且代码中明确写出“滑点0.05%手续费0.03%”。导师会检查这些数字是否真实计入。5.4 图特征重要性与经济意义关联图升华答辩高度最后一张图不是SHAP图而是特征重要性 vs 经济直觉匹配度散点图。横轴是permutation_importance得分纵轴是你给每个特征打的“经济合理性分”1-5分5分完全符合金融常识。# 在05_train_evaluate.ipynb中生成 importance_df pd.DataFrame({ feature: feature_names, importance: importances_mean, economic_score: [4, 5, 3, 2, 5, 4, 1, 3, 5, 4] # 手动打分依据AFML第6章理论 }) plt.scatter(importance_df[importance], importance_df[economic_score], cimportance_df[importance], cmapviridis, s100) for i, row in importance_df.iterrows(): plt.annotate(row[feature][:10], (row[importance], row[economic_score])) plt.xlabel(Permutation Importance) plt.ylabel(Economic Intuition Score (1-5)) plt.title(Do Important Features Make Economic Sense?) plt.colorbar(labelImportance) plt.show()这张图的价值在于它把冰冷的数字拉回到De Prado的理论框架。当feat_close_ma20_ratio价格/20日均线重要性最高且经济分5分而feat_random_noise一个纯随机特征重要性低且经济分1分——你就证明了模型学到了金融规律不是数据噪音。5.5 一次演练现场演示“修改参数→重跑→看结果”全流程答辩最后5分钟主动提出“请导师指定一个参数我现场修改并展示结果变化。” 这是终极信任票。我建议锁定pt_sl[0]止盈比率因为修改它只影响02_label_events.ipynb不涉及模型重训耗时30秒结果变化直观labels_df[bin].value_counts()中1类数量会明显增减。演练脚本打开02_label_events.ipynb定位label_config字典将pt_sl: [0.5, 0.5]改为[0.7, 0.5]运行该cell及后续所有依赖cell执行labels_df[bin].value_counts()展示1类样本从127→189切到05_train_evaluate.ipynb运行calculate_metrics()指出Directional_Accuracy从0.61→0.59但Coverage_Rate从0.42→0.51。这个演练证明你掌控整个数据流不是粘贴代码的机器人。从那以后我每次带毕设都强制学生在答辩前用真实数据跑三遍这个演练——哪怕只是改一个数字也要确保从01到05全链路畅通。希望帮到你。本文还有配套的精品资源点击获取