☰
金融机器学习实战:从数据采样到标签生成的Notebook实验指南
2026/11/2 6:44:17 网站建设 项目流程

简介:这份资源面向已具备一定Python与量化金融基础、希望动手实践《金融机器学习进展》一书核心方法的学习者,提供书中选定练习的实验性解决方案。内容围绕监督分类中的标注与元标注、金融交叉验证、样本权重、分数阶差分特征以及Tick、成交量与美元成交量bar构建等主题展开,帮助读者把书中的理论方法落到可运行的代码实验上。压缩包共43个文件,约2.8MB,以18个py脚本和6个ipynb笔记本为主体,辅以rst文档、Makefile、配置文件与少量图片、csv数据,src目录下含bars.py、snippets.py等可复用模块,需通过setup.py install安装后供笔记本调用。目前已有342人学习下载。读者可据此获得按章节组织的练习实现、可复用的特征与bar生成工具脚本,以及项目化的目录结构参考,便于对照复现与二次开发。

1. 从一份练习仓库说起:金融机器学习到底怎么落地

很多人学金融机器学习,卡在同一个地方:书看完了,公式推得动,真拿到一份带时间戳的行情数据,却不知道第一步该做什么。这份 Adv_Fin_ML_Exercises 就是冲着这个痛点来的——它把一本讲金融机器学习进展的书里的练习,做成了可以逐个跑通的 Jupyter Notebook 实验方案。每个 notebook 对应一个具体问题:数据怎么按时间切、标签怎么打、样本权重怎么算、回测怎么防过拟合。适合已经会写 Python、想把这套方法论落到自己策略研究里的人,也适合拿它当模板改造成自己项目骨架的开发者。它不是教程合集,是一份能直接打开、改参数、看结果的实验底稿。

2. 项目结构与技术选型:为什么是 Notebook 而不是一个包

2.1 目录组织与文件职责

这类练习型仓库,常见做法是按书里的章节或主题分目录,每个目录下放若干.ipynb,配套一个data/放样例数据、一个utils/或helpers/放跨 notebook 复用的函数。我拆过的同类项目,结构大致是这样:

Adv_Fin_ML_Exercises/ ├── data/ # 样例行情、成交、标签数据 ├── notebooks/ │ ├── 01_financial_data_structures.ipynb │ ├── 02_labeling.ipynb │ ├── 03_sample_weights.ipynb │ ├── 04_fractional_differentiation.ipynb │ └── ... ├── src/ # 被 notebook import 的公共函数 │ ├── bars.py # 各类 bar 采样 │ ├── labeling.py # 三重障碍法等打标签逻辑 │ └── sampling.py # 样本权重与交叉验证切分 ├── requirements.txt └── README.md

notebook 负责“演示 + 可视化”,src/负责“可复用逻辑”,这个分工是关键。如果所有函数都写在 notebook 单元格里,改一个参数要翻十几个格子,二次开发基本没法做。把核心算法抽到src/,notebook 只做调用和画图,才是能长期维护的写法。

2.2 技术栈与依赖

从关键词 JupyterNotebook 就能看出,运行环境是 Jupyter。配套的库基本跑不出这几样:pandas和numpy做数据处理,matplotlib或seaborn画图,scikit-learn做模型和交叉验证,scipy做统计检验和插值。金融数据这块可能还会用到statsmodels做时间序列分析。

requirements.txt里我一般会锁版本,因为这类项目对 pandas 的 API 变动很敏感——比如resample的行为、groupby的默认参数,跨大版本经常翻车。建议装的时候先建虚拟环境:

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt jupyter notebook

启动后浏览器会打开文件列表,点进notebooks/逐个跑。注意 notebook 之间有依赖顺序,比如打标签的 notebook 依赖前面生成的 bar 数据,乱序跑会报KeyError或读到空 DataFrame。

2.3 为什么用 Notebook 承载这类实验

金融机器学习有个特点:每一步的中间结果都值得看一眼。bar 采样后的价格序列长什么样、标签分布是否均衡、样本权重有没有极端值,这些用脚本跑只能看 print,用 notebook 可以直接画出来。而且书里的练习本身是探索性的,参数要反复调,notebook 的“改一格、跑一格”正好匹配这种节奏。

代价是版本管理和测试难做。我的做法是:notebook 只保留调用和展示,逻辑全在src/,src/里的函数单独写测试。这样 notebook 改乱了也不影响核心逻辑,git diff也不会因为输出单元格变化而一片红。

3. 核心模块拆解:从数据采样到标签生成的关键实现

3.1 数据采样:从 tick 到 bar 的转换

金融机器学习的第一步不是建模,是把原始数据整理成模型能吃的样本。书里强调的一个点是:时间 bar(固定时间间隔采样)在流动性不均时会产生偏差,应该用信息驱动的采样方式,比如 tick bar、volume bar、dollar bar。

下面是一个 dollar bar 的简化实现,按成交金额累积到阈值切一根 bar:

import numpy as np import pandas as pd def dollar_bars(trades: pd.DataFrame, threshold: float) -> pd.DataFrame: """ trades: 需包含 price, size, timestamp 三列,按时间升序 threshold: 每根 bar 累积的成交金额阈值 """ trades = trades.sort_values("timestamp").reset_index(drop=True) trades["dollar"] = trades["price"] * trades["size"] trades["cum_dollar"] = trades["dollar"].cumsum() # 用累积金额整除阈值得到 bar 编号 trades["bar_id"] = (trades["cum_dollar"] // threshold).astype(int) bars = trades.groupby("bar_id").agg( open=("price", "first"), high=("price", "max"), low=("price", "min"), close=("price", "last"), volume=("size", "sum"), dollar=("dollar", "sum"), start=("timestamp", "first"), end=("timestamp", "last"), ) return bars

逻辑说明:先算每笔成交的金额,累积后除以阈值取整,同一个整数区间的成交归为一根 bar。threshold是核心参数,设太小 bar 数量爆炸、噪声大;设太大样本太少、模型学不到东西。经验上让 bar 数量落在原始 tick 数的 1/50 到 1/100 之间比较稳。groupby里用first/last取开收盘,前提是数据已按时间排序,否则开收盘会错位——这是最常见的翻车点。

3.2 标签生成:三重障碍法

书里最有代表性的方法之一是三重障碍法(triple barrier)。思路是给每个样本设三堵墙:上止盈、下止损、时间上限,看价格先碰到哪堵,据此打标签。

def triple_barrier_labels(price: pd.Series, events: pd.DatetimeIndex, pt_sl=(2.0, 1.0), max_hold: int = 20, vol_window: int = 50) -> pd.DataFrame: """ price: 价格序列,索引为时间 events: 需要打标签的时点 pt_sl: (止盈倍数, 止损倍数),相对波动率 max_hold: 最长持有 bar 数 vol_window: 估计波动率的滚动窗口 """ vol = price.pct_change().rolling(vol_window).std() out = [] for t in events: if t not in price.index: continue idx = price.index.get_loc(t) p0 = price.iloc[idx] sigma = vol.iloc[idx] upper = p0 * (1 + pt_sl[0] * sigma) lower = p0 * (1 - pt_sl[1] * sigma) end = min(idx + max_hold, len(price) - 1) path = price.iloc[idx + 1: end + 1] label = 0 # 时间障碍:到期未触发 for ts, p in path.items(): if p >= upper: label = 1 break if p <= lower: label = -1 break out.append({"t": t, "label": label, "ret": (price.iloc[end] / p0 - 1)}) return pd.DataFrame(out).set_index("t")

逻辑说明:pt_sl控制止盈止损的宽窄,用滚动波动率sigma缩放,保证不同波动环境下障碍宽度自适应。max_hold是时间墙,防止样本无限期挂着。返回的label是 1/0/-1 三分类,ret是到期实际收益,后面算样本权重和回测都要用。

参数上,vol_window太小波动率估计噪声大,太大反应迟钝,50 到 100 是常见区间。max_hold要和你的持仓周期匹配,做日内就设小,做波段就设大。这里有个容易忽略的点:events不能随便取全序列,通常要过滤掉重叠区间,否则相邻样本高度相关,交叉验证会虚高。

3.3 样本权重与交叉验证切分

金融数据样本不独立、不同步长,标准 KFold 会泄漏未来信息。书里给的方案是按标签唯一性算样本权重,再用 purged K-Fold 切分。

def uniqueness_weights(label_df: pd.DataFrame, price_index) -> pd.Series: """按每个样本覆盖的时间区间计算平均唯一性作为权重""" # 简化:用持有期长度近似重叠程度 conc = pd.Series(0.0, index=price_index) for t, row in label_df.iterrows(): end = min(t + pd.Timedelta(days=row.get("hold", 1)), price_index[-1]) conc.loc[t:end] += 1 w = 1.0 / conc.replace(0, np.nan) return w.reindex(label_df.index).fillna(0)

逻辑说明:重叠越多的样本,独立信息越少,权重应该越低。conc统计每个时点被多少样本覆盖,取倒数就是唯一性。实际项目里hold要从三重障碍的结果里带出来,这里用简化占位。purged K-Fold 则是在训练集和验证集之间挖掉一段缓冲期,防止标签窗口跨边界泄漏。这两块是整套方法里最容易被跳过、又最影响结果可信度的部分。

4. 跑起来与改造:环境、数据与参数调优

4.1 环境准备与首次运行

除了前面说的虚拟环境和pip install,还要注意 Jupyter 的 kernel 要指向刚建的虚拟环境,否则 import 会找不到包:

pip install ipykernel python -m ipykernel install --user --name advfinml --display-name "advfinml"

然后在 notebook 里选advfinml这个 kernel。首次运行建议从数据采样那个 notebook 开始,确认data/里的样例数据能被正确读入、bar 能画出来,再往后跑标签和权重。如果样例数据缺失,就用自己的行情数据替换,列名对齐price/size/timestamp即可。

4.2 换成自己的数据要改哪里

改造点集中在三处:数据加载、参数配置、输出路径。数据加载一般每个 notebook 开头有个pd.read_csv,把路径换成自己的就行,但要注意时间列要pd.to_datetime并设为索引。参数配置建议抽成一个config.py或 notebook 顶部的字典,把threshold、pt_sl、max_hold、vol_window集中管理,别散落在各个单元格里。输出路径统一指向output/,避免结果文件混在 notebook 目录里。

4.3 参数调优的实操顺序

调参别一上来就网格搜索。我的顺序是:先定 bar 的threshold,看 bar 数量和分布是否合理;再定pt_sl和max_hold,看标签分布是否均衡(三分类里 0 类占比过高说明时间墙太短或障碍太宽);最后调vol_window和交叉验证的缓冲期。每一步都画图确认,别只看数字。这套顺序能避免“模型效果差其实是标签打错了”这种低级但高频的问题。

5. 避坑与常见问题:跑这类实验最容易踩的五个坑

5.1 现象:notebook 跑完结果每次都不一样

原因:没设随机种子,或者交叉验证切分本身带随机性。解决:在 notebook 开头统一设np.random.seed和random.seed,交叉验证用固定random_state。金融数据本身有随机性,但实验流程必须可复现,否则调参就是玄学。

5.2 现象:标签分布极端不均,0 类占九成

原因:max_hold设得太短,或者pt_sl障碍设得太宽,价格在期限内很少触发。解决:先画价格路径和障碍线叠加图,直观看出障碍位置,再调pt_sl到 1.0 到 2.0 之间、max_hold覆盖一个完整波动周期。

5.3 现象:交叉验证分数高得离谱

原因:样本重叠导致信息泄漏,训练集和验证集共享了同一段价格路径。解决:用 purged K-Fold,在切分边界挖掉至少一个max_hold长度的缓冲期,并配合样本权重降低重叠样本的影响。

5.4 现象:import src 里的模块报 ModuleNotFoundError

原因:notebook 的工作目录不在仓库根目录,Python 找不到src。解决:在 notebook 开头加sys.path.append(".."),或者把仓库根目录加进PYTHONPATH。更稳的做法是把src装成可编辑包pip install -e .。

5.5 现象:pandas 版本升级后 resample 或 groupby 结果变了

原因:pandas 跨大版本有行为变更。解决:requirements.txt锁死版本,升级前先跑一遍 notebook 对比输出。这类项目对库版本敏感,别图新。

6. 二次开发:把练习仓库改造成自己的研究骨架

这份仓库最大的价值不是练习答案本身,是它提供了一套可复用的流程骨架。我的改造思路是把它从“notebook 集合”升级成“小型研究框架”。

第一步,把src/里的函数补上类型注解和 docstring,再写一组 pytest 测试,覆盖 bar 采样、标签生成、权重计算三个核心函数。测试用构造的小数据集,断言输出形状和关键统计量。这样以后改逻辑有后悔药。

第二步,把参数配置从 notebook 抽到config.yaml,用pyyaml读入。不同策略用不同配置文件,notebook 只负责加载配置和展示结果。这一步做完,同一套代码能跑多组实验,不用复制 notebook。

第三步,加一个轻量的实验记录。不用上重型工具,一个 CSV 追加写入就够:记录配置哈希、关键参数、标签分布、交叉验证分数。下面是个最小实现:

import hashlib, json, csv, datetime def log_experiment(config: dict, metrics: dict, path="output/experiments.csv"): cfg_str = json.dumps(config, sort_keys=True) cfg_hash = hashlib.md5(cfg_str.encode()).hexdigest()[:8] row = {"time": datetime.datetime.now().isoformat(), "cfg_hash": cfg_hash, **config, **metrics} with open(path, "a", newline="") as f: writer = csv.DictWriter(f, fieldnames=row.keys()) if f.tell() == 0: writer.writeheader() writer.writerow(row)

逻辑说明:配置序列化后取哈希,方便回溯是哪组参数产出的结果;metrics里放标签分布比例、验证集分数等。字段名用**展开,配置和指标平铺在一行,查起来直观。注意首次写入要判断文件是否为空再写表头,否则会重复表头。

第四步,验证改造是否成功的方法:拿同一份数据,用改造前和改造后的流程各跑一遍,对比标签分布和交叉验证分数是否一致。一致说明重构没改变语义,不一致就 diff 配置和代码。这个对比步骤我每次重构都强制走一遍,吃过亏——有次改权重函数时把倒数写成了正比,分数看着还行,实际逻辑全反了,靠对比才抓出来。

从那以后我每次动核心逻辑,都先跑一遍基线对比再往下做。希望这份拆解能帮你少走点弯路,把这份练习仓库真正变成自己项目里能用的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询