首页 / 资讯中心 / 文章详情

金融机器学习实战:从数据标注到交叉验证的Notebook实验指南

金融机器学习实战:从数据标注到交叉验证的Notebook实验指南 ★ FEATURED ARTICLE
简介这份资源面向已具备一定Python与量化金融基础、希望动手实践《金融机器学习进展》一书核心方法的学习者提供书中选定练习的实验性解决方案帮助读者在真实代码中理解金融机器学习的关键技术环节。压缩包共43个文件约2.8MB以18个py脚本和6个ipynb笔记本为主辅以rst文档、Makefile构建脚本、png图表及csv数据等覆盖从环境配置到实验复现的完整流程。笔记本内容涉及监督分类中的标签与元标签、金融交叉验证、样本权重、分数阶差分特征以及Tick、成交量与美元成交量bar的构建等主题src目录下还包含bars.py、snippets.py等可复用工具脚本便于读者在本地安装后直接调用。目前已有342人学习下载适合作为对照书中理论、复现实验并积累特征工程与验证方法的实践参考。1. 从一份“练习答案”说起金融机器学习项目到底能跑出什么很多人下载量化金融的代码仓库第一反应是找策略、找信号、找能直接上实盘的模型。但真正在一线做过的人都知道金融机器学习最难的不是调包而是把书上的公式变成能跑、能验证、能复现的实验。这个项目就是干这件事的它围绕一本金融机器学习专著里的练习题用 Jupyter Notebook 把每一道题拆成可执行的实验覆盖数据标注、样本权重、交叉验证、特征重要性、回测评估这些核心环节。适合谁适合已经会 Python、想系统啃一遍金融机器学习方法论、又不想对着公式干瞪眼的开发者。你拿到的不是一套交易系统而是一组能逐格运行、逐格改参数的研究笔记跑通它你对“为什么金融数据不能随便交叉验证”这类问题会有肌肉记忆。2. 项目结构与技术栈Notebook 驱动的实验组织方式2.1 目录与文件组织这类练习型仓库通常不会有一个庞大的src/包而是以章节或主题为单位组织 Notebook。常见做法是按书中的章节编号建目录每个目录下放若干.ipynb配套一个data/放样例数据一个utils/或helpers/放跨 Notebook 复用的函数。我一般会先看根目录有没有requirements.txt或environment.yml这决定了环境怎么搭。如果没有就按 Notebook 里import的包反推依赖。一个典型的目录长这样Adv_Fin_ML_Exercises/ ├── data/ # 样例数据通常是 csv 或 parquet ├── utils/ # 复用函数如数据加载、绘图、指标计算 ├── ch02_financial_data/ # 按章节或主题划分 │ └── exercise_*.ipynb ├── ch03_labeling/ │ └── exercise_*.ipynb ├── ch04_sample_weights/ ├── ch06_cross_validation/ └── requirements.txt注意具体目录名以你下载到的版本为准但组织逻辑基本是“一章一目录、一题一 Notebook”。这种结构的好处是每个实验自包含坏处是复用代码容易散落所以utils/的存在很关键。2.2 技术栈与依赖从关键词 JupyterNotebook 就能看出整个项目的运行载体是 Notebook。底层依赖集中在几个方向数值计算用numpy、pandas统计与机器学习用scikit-learn、scipy金融数据与回测常用statsmodels绘图用matplotlib、seaborn。部分涉及树模型特征重要性的练习可能会用到xgboost或lightgbm涉及多重检验的会用到statsmodels的校正方法。搭环境我一般用 conda 隔离避免和本机其他项目打架# 创建独立环境Python 版本按 Notebook 里的语法选3.9~3.11 比较稳 conda create -n advfinml python3.10 -y conda activate advfinml # 先装 Notebook 运行器 pip install jupyterlab notebook # 再按需装核心依赖 pip install numpy pandas scipy scikit-learn statsmodels matplotlib seaborn如果仓库自带requirements.txt直接pip install -r requirements.txt更省事。装完启动jupyter lab浏览器打开后从第一个 Notebook 开始按顺序执行。这里有个血泪经验不要一上来就“Run All”先看每个 cell 的输入输出尤其是涉及随机种子的地方否则你复现出来的数字和笔记里对不上会怀疑人生。2.3 一个 Notebook 的典型执行链路以“数据标注”类练习为例一个 Notebook 通常走这条链路加载价格序列 → 计算滚动统计量 → 按阈值或统计检验打标签 → 可视化标签分布 → 保存结果供后续章节使用。理解这条链路你才知道每个 cell 在整条流水线里的位置改参数时不会牵一发动全身。3. 核心模块拆解从数据标注到交叉验证的关键实现3.1 数据加载与清洗的通用写法金融数据的第一道坎是时间索引和缺失值。练习里常见的加载函数会强制把日期列转成DatetimeIndex并按时间排序因为后续所有滚动窗口和交叉验证都依赖有序索引。import pandas as pd import numpy as np def load_price_series(path, date_coldate, price_colclose): # 读取后立刻把日期设为索引避免后续 rolling 时顺序错乱 df pd.read_csv(path, parse_dates[date_col]) df df.sort_values(date_col).set_index(date_col) # 只保留需要的价格列并去掉重复索引 series df[price_col].astype(float) series series[~series.index.duplicated(keepfirst)] # 前向填充少量缺失但不做过度插值避免引入未来信息 series series.ffill() return series这段代码的关键点有三个parse_dates保证索引是时间类型sort_values保证滚动计算方向正确ffill只做前向填充绝不用后向填充或插值因为那等于把未来价格泄漏到过去。参数上date_col和price_col要按你的数据列名改很多练习数据列名不统一这是第一个容易翻车的地方。3.2 标签构造阈值法与三重障碍法的思路金融机器学习的标签不是简单的涨跌而是“在某个时间窗口内价格先碰到上障碍还是下障碍”。练习里常见两种做法一种是固定阈值收益率超过正负阈值分别记 1 和 -1另一种是三重障碍法用波动率动态设定上下障碍和持有期限。def threshold_label(returns, upper0.01, lower-0.01): # 按未来一期收益打标签1 涨、-1 跌、0 中性 labels np.where(returns upper, 1, np.where(returns lower, -1, 0)) return pd.Series(labels, indexreturns.index, namelabel) def triple_barrier_label(price, vol_window20, barrier_mult2.0, max_hold10): # 用滚动波动率设定动态障碍持有期上限防止样本无限拖长 vol price.pct_change().rolling(vol_window).std() labels [] idx [] for i in range(vol_window, len(price) - max_hold): upper price.iloc[i] * (1 barrier_mult * vol.iloc[i]) lower price.iloc[i] * (1 - barrier_mult * vol.iloc[i]) window price.iloc[i1:i1max_hold] hit_up (window upper).any() hit_down (window lower).any() # 先碰到哪个障碍就记哪个同时碰到按保守处理记 -1 if hit_up and not hit_down: labels.append(1) elif hit_down: labels.append(-1) else: labels.append(0) idx.append(price.index[i]) return pd.Series(labels, indexidx, namelabel)threshold_label简单直接适合先跑通流程triple_barrier_label更贴近真实交易逻辑但循环写法在长序列上慢实际改造时可以向量化或用numba加速。参数barrier_mult控制障碍宽度越大标签越稀疏max_hold控制样本重叠程度设太大样本间相关性高会让后续交叉验证的分数虚高。这是练习里最值得反复调的两个旋钮。3.3 样本权重解决标签不平衡与重叠金融标签天然不平衡中性标签往往占大头而且三重障碍法产生的样本在时间上高度重叠。练习里通常会给样本分配权重常见做法是按标签频率的倒数加权再对重叠样本做时间衰减。def sample_weights(labels, decay0.5): # 按类别频率倒数给基础权重缓解不平衡 freq labels.value_counts(normalizeTrue) base_w labels.map(lambda x: 1.0 / freq[x]) # 按时间顺序做指数衰减越早的样本权重越低 n len(labels) time_w np.exp(-decay * np.arange(n) / n) w base_w.values * time_w # 归一化到均值为 1方便后续模型直接使用 return pd.Series(w / w.mean(), indexlabels.index, nameweight)decay越大近期样本权重越突出设 0 就退化成纯类别平衡。注意权重归一化到均值 1 是个好习惯否则不同练习之间权重尺度不一致模型的正则化强度会跟着变结果没法横向比较。3.4 交叉验证为什么不能随机打乱这是整个项目里最核心、也最容易踩坑的模块。金融时间序列有自相关随机 K 折会把未来样本混进训练集分数漂亮但实盘一塌糊涂。练习里通常实现 purged K-fold 或 walk-forward 验证在训练集和验证集之间留一段“净化期”并把与验证集标签窗口重叠的训练样本剔除。def purged_kfold_indices(n_samples, n_splits5, purge5): # 按时间顺序切分不在切分后打乱 fold_size n_samples // n_splits splits [] for k in range(n_splits): test_start k * fold_size test_end (k 1) * fold_size if k n_splits - 1 else n_samples test_idx np.arange(test_start, test_end) # 训练集去掉测试集前后各 purge 个样本切断标签窗口重叠 train_idx np.concatenate([ np.arange(0, max(0, test_start - purge)), np.arange(min(n_samples, test_end purge), n_samples) ]) splits.append((train_idx, test_idx)) return splitspurge的大小要和你标签的持有期匹配持有 10 天就至少净化 10 个样本否则净化不彻底。这个函数返回的是索引对直接喂给sklearn的cross_val_score时要用cv参数的自定义切分器包装或者手动循环。很多练习 Notebook 会在这里画一张训练/测试区间示意图建议你跑完后对着图看一遍比读十遍公式都管用。4. 跑起来与验证环境、执行顺序与结果核对4.1 环境搭建的完整步骤除了前面装依赖还要注意 Notebook 的 kernel 选择。装完环境后在 Jupyter 里要手动切到advfinml这个 kernel否则你pip install的包和 Notebook 实际用的解释器不是同一个报ModuleNotFoundError时你会以为是包没装。# 把 conda 环境注册成 Jupyter kernel python -m ipykernel install --user --name advfinml --display-name Python (advfinml)然后在 Jupyter Lab 右上角选 “Python (advfinml)”。这一步不做后面所有排查都是白费。4.2 执行顺序与结果核对练习型仓库的 Notebook 之间常有数据依赖前一章导出的标签文件是后一章的输入。所以执行顺序不能乱我一般按目录编号从小到大跑每跑完一个 Notebook 检查它有没有在data/或output/下生成文件。核对结果时重点看三样标签分布是否合理中性标签不应超过 80%、交叉验证分数是否稳定各折之间差异过大说明切分或净化有问题、特征重要性排序是否符合直觉如果某个未来信息列排第一基本可以确定有泄漏。提示Notebook 里如果用了np.random.seed注意它只对当前 kernel 生效重启 kernel 后要重新执行种子 cell否则结果不可复现。4.3 把 Notebook 变成可复用脚本跑通之后建议把核心函数从 Notebook 抽到utils/下的.py文件Notebook 里只保留调用和可视化。这样改参数时不用在几十个 cell 里翻找也方便后续写测试。抽取时注意把硬编码的路径改成参数把打印改成返回这是从“练习”走向“能二次开发”的第一步。5. 避坑与排查五个真实会卡住你的问题5.1 现象Notebook 跑完分数高得离谱实盘逻辑完全对不上原因随机交叉验证把未来样本混进了训练集或者特征里混入了未来才有的数据比如用当日收盘价算当日信号。解决把所有train_test_split换成按时间切分检查每个特征的计算窗口是否只用了shift之后的数据标签构造里确认没有用到i之后的索引。5.2 现象ModuleNotFoundError明明刚 pip 装过原因Jupyter 用的 kernel 和你装包的环境不是同一个。解决用import sys; print(sys.executable)在 Notebook 里打印解释器路径和which python对比不一致就重新注册 kernel 或在该解释器下装包。5.3 现象三重障碍法跑得极慢一个 Notebook 要十几分钟原因逐行 Python 循环加iloc切片在几千行数据上就会明显变慢。解决先把价格和波动率转成 numpy 数组用向量化窗口或numba.jit加速调试阶段先用前 500 行数据跑通逻辑再放全量。5.4 现象不同 Notebook 之间结果对不上标签文件读进来行数不对原因前一个 Notebook 改了参数但没重新导出或者导出时覆盖了旧文件但索引没对齐。解决每个导出 cell 加一行打印行数和时间范围读入时做assert校验养成“改参数必重跑导出 cell”的习惯。5.5 现象绘图中文乱码或负号显示成方块原因matplotlib默认字体不含中文。解决在绘图前设置plt.rcParams[font.sans-serif]和plt.rcParams[axes.unicode_minus] False或者干脆把图例改成英文练习阶段不必在这上面耗时间。6. 二次开发把练习改造成自己的研究流水线跑通练习只是起点真正有价值的是把它改造成能承载你自己数据的流水线。我的做法是保留 Notebook 作为“实验记录”另建一个pipeline/目录放可执行脚本形成“脚本产出数据、Notebook 做分析和可视化”的分工。具体改造分三步走。第一步把utils/里的函数补上类型注解和最小测试。比如给triple_barrier_label加一个用构造数据验证边界情况的测试价格单调上涨时标签应全为 1单调下跌时应全为 -1。这一步能挡住大部分改参数引入的低级错误。第二步把数据加载层抽象出来。练习里读 csv实际项目可能读数据库或 parquet定义一个load_series(source)接口Notebook 里只调接口换数据源时不动分析代码。下面是一个简单的适配写法def load_series(source, **kwargs): # 按扩展名分派新增数据源只需加一个分支 if source.endswith(.csv): return load_price_series(source, **kwargs) elif source.endswith(.parquet): df pd.read_parquet(source) return df[kwargs.get(price_col, close)].sort_index() else: raise ValueError(funsupported source: {source})第三步把交叉验证的净化参数和标签持有期绑定成配置而不是散落在各个 Notebook 里。我一般用一个config.yaml或简单的dict集中管理max_hold、purge、barrier_mult跑实验时只改配置、记录配置哈希这样半年后回看还能知道当时那组分数是哪套参数跑出来的。改造过程中最容易犯的错是过早追求“工程化”而丢掉实验的灵活性。我的教训是Notebook 里的探索性代码不要急着抽函数等同一个逻辑在三个以上 Notebook 里重复出现再抽反过来凡是产出数据文件的 cell必须当天就固化成脚本否则下次你根本记不清那个 csv 是用哪版代码生成的。从那以后我每次改完标签或切分逻辑都强制走一遍“重跑导出 → 校验行数 → 记录配置”这三步再去看分数。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站