简介:这是一份面向金融数据分析师和机器学习初学者的实践型PDF,定位于“金融+机器学习”的入门与项目落地。资源覆盖信用风险评估、股票预测、客户行为分析、欺诈检测等金融场景,系统梳理了金融数据挖掘、数据预处理、特征工程与模型评估等核心环节,并介绍了逻辑回归、决策树、随机森林、支持向量机、Gradient Boosting等常用算法的适用条件与建模流程。压缩包内共1个PDF文件,大小约2.9MB,篇幅克制但结构清晰,适合作为快速建立知识框架和随时查阅的参考资料。目前已有393人学习浏览,对希望了解金融预测模型构建全流程、量化风控、智能投顾等方向的人有直接参考价值。读者可借此掌握金融机器学习的典型应用思路,减少自行摸索成本,为后续实战项目提供确定的知识起点。
1. 金融机器学习实践:这份PDF把可落地的东西讲透了吗
金融机器学习实践这份PDF,我一开始是抱着怀疑去读的——金融数据信噪比极低、序列非平稳,机器学习在这里特别容易变成曲线拟合,卷深度学习模型的团队翻车案例也比比皆是。翻到后半程发现它的思路和很多网传项目正好相反:不堆模型,不炫注意力机制,而是把特征工程、标签设计、样本权重、时间序列切分这一整套流程讲得很扎实。它解决的实际问题是“从数据到可上线模型”中间的落地环节。合适的读者是做量化策略、金融数据分析的人,还有正准备从其他领域转过来的机器学习入门者。如果你只是想要一个预测涨跌的黑匣子,它不合适;如果你想搞清楚预测模型从训练到回测之间那些容易翻车的细节,这份PDF能省不少试错时间。
2. 特征工程与小波去噪:把K线洗到能直接喂模型
金融机器学习项目的第一步不是建模,是洗数据。这份PDF开篇没有上来展示模型,而是花了很大篇幅在讲数据清洗与特征构造。我按它的流程在真实日线数据上复现过一次,踩了几个坑,这里把完整步骤和参数写出来。
2.1 原始K线清洗:处理停牌、跳空与复权
行情数据很少能直接进模型。最常遇到的三类问题:一是股票停牌,造成连续多天同一收盘价;二是前后复权因子不一致,拉出的价格序列有跳空;三是部分接口下载数据时就混入了未来复权信息。常见做法是先做基于代码和时间戳的排序去重,再统一转成百分比收益率,让序列先平稳下来。
import pandas as pd import numpy as np df = pd.read_csv('daily_kline.csv', parse_dates=['trade_date']) df = df.sort_values(['ts_code', 'trade_date']) df = df.drop_duplicates(['ts_code', 'trade_date']) df['ret'] = df.groupby('ts_code')['close'].pct_change() limit = 0.11 df['abnormal'] = df['ret'].abs() > limit df.loc[df['abnormal'], 'ret'] = np.nan df['ret_fill'] = df.groupby('ts_code')['ret'].fillna(0)这段代码做了四件事:排序去重、计算单日收益率、按涨跌停幅度过滤异常值、用0填充停牌缺失。limit设成0.11而不是0.1,是因为A股有ST股的5%和创业板、科创板的20%涨跌幅限制,写死0.1会把一部分正常波动误杀。更稳妥的做法是直接用原始成交量与涨跌幅字段来判断停牌,比如连续多天成交量为0且涨跌幅为0,再统一fillna。
除权这一环是这里最隐蔽的坑。前复权价格会随最新行情不断重算,如果你每周下载一次历史数据,同一只股票在不同时间点看到的历史价格是不同的。滚动训练时要小心:较晚下载的数据已经把“未来”的除权信息嵌进了历史价格里。我一般会让数据源导出一份含复权因子和历史快照的数据,而不是每次现取全量。这个细节在后续标准化时尤其要命,第5章会单独展开。
2.2 小波去噪:不建议直接用滑动平均
金融价格序列可以拆成趋势、周期和噪声三部分。最直观的去噪做法是移动平均,但移动平均有两个代价:一是滞后,均线总比价格慢半拍;二是对价格突变过于平滑,会把启动信号给钝化掉。做预测时,特征里的“刚发生的变化”往往比“长期趋势”更有价值。
离散小波变换比移动平均更合适。它把原始序列分解成不同尺度的近似分量和细节分量,再把细节分量做阈值收缩,然后重构回去。这样既保留了趋势主体,又滤掉了与预测无关的微观噪声,不会引入移动平均那种固定滞后。
import numpy as np import pywt def wavelet_denoise(series, wavelet='db4', level=4, mode='soft'): coeffs = pywt.wavedec(series, wavelet, level=level) sigma = np.median(np.abs(coeffs[-1])) / 0.6745 threshold = sigma * np.sqrt(2 * np.log(len(series))) coeffs_thresh = [ pywt.threshold(c, threshold, mode=mode) if i > 0 else c for i, c in enumerate(coeffs) ] return pywt.waverec(coeffs_thresh, wavelet)[:len(series)]小波基选db4,分解层数取4,这是金融日线序列里比较通用的起点。阈值用的VisuShrink估计量,σ用细节系数中位数除以0.6745估算,噪声标准差用中位数估计比直接用std更抗异常值。mode='soft'做软阈值,重构后的序列更平滑;想保留更多突变信息可以换hard。level如果取到6或7,会把中长期趋势周期也滤掉一部分,反而丢失特征。
2.3 特征衍生:动量、波动率与成交量特征
特征体系我习惯分成三类:价格形态特征、波动率特征、成交量与资金流特征。价格形态里常用动量、均线偏离、通道位置;波动率里用ATR和历史波动率;成交量里用量比与成交额变化率。高频资金流数据获取成本高,多数日频项目只做前两类也够用。
df['mom_5'] = df['close'] / df['close'].shift(5) - 1 df['mom_20'] = df['close'] / df['close'].shift(20) - 1 df['ma_ratio'] = df['close'] / df['close'].rolling(20).mean() - 1 tr = np.maximum( df['high'] - df['low'], np.maximum( (df['high'] - df['close'].shift(1)).abs(), (df['low'] - df['close'].shift(1)).abs() ) ) df['atr'] = tr.rolling(14).mean() df['vol_ratio'] = df['volume'] / df['volume'].rolling(20).mean()这一段用shift和rolling生成特征,默认用的是历史窗口,不会引入未来数据。atr按TR的14日平均计算,衡量真实波动幅度;vol_ratio是当日成交量与近20日均量的比值,用来捕捉放量信号。有一点要注意:这些特征在t日收盘后是可知的,但它对应的预测标签是t+N日之后才能确认的,所以训练时X和y天然存在时间差,评估时不要对两者做同期对齐。
3. 标签设计与样本权重:预测方向比预测价格更现实
特征做好之后,下一个问题是:到底让模型学什么。很多人第一次上手金融机器学习,第一反应是预测明天的收盘价,但价格序列非平稳,直接回归绝对价格很容易让模型只学到“顺着最近的趋势外推”。这份PDF里讲的做法更实际:预测未来N期的收益符号或方向,必要时切分成三分类。标签设计得当,模型训练难度和实盘可用性都会好很多。
3.1 回归与三分类:标签两种做法的取舍
回归任务的问题不在模型,而在目标分布。不同标的的价格量级不同,同一标的在不同年份的价格中枢也不同;模型输出的绝对值几乎无法跨标的复用。把目标换成未来N日涨跌幅,再去预测方向,就避开了绝对值漂移问题。三分类比二分类多一个“震荡”状态,模型不强行在无趋势样本上判多空,线下评估和实盘信号都会更合理。
df['fwd_ret'] = df['close'].shift(-5) / df['close'] - 1 up_th = 0.02 down_th = -0.02 df['label'] = np.select( [df['fwd_ret'] > up_th, df['fwd_ret'] < down_th], [1, -1], default=0 ) df = df.dropna(subset=['fwd_ret'])shift(-5)是在取未来第5天的收益,这一步本身就是把未来信息写进标签,属于正常设计;关键是不能让它出现在特征里。固定阈值up_th和down_th设为正负2%,在日线5日窗口下大约只有三成样本有明确方向,其他都归为震荡。想要更均衡的类别分布,可以改成按fwd_ret的分位数切三段,让三类样本数量接近。分位数做法在多空信号密度和模型精度之间更容易平衡,我在复现时最终选了分位数方案。
3.2 样本权重:高波动时段不该学那么用力
金融序列波动率不恒定,高波动时段与低波动时段的样本难度差异很大。如果把所有样本一视同仁,模型会花大量容量去拟合极端行情下的剧烈涨跌,而这些样本在实际交易中最难把握。常见做法是给样本加权:波动率越低权重越高;或者反过来在策略需要捕捉波动时做对称处理。最常见的是波动率倒数权重。
df['vol'] = df['ret'].rolling(20).std() df['sample_weight'] = 1.0 / (df['vol'] + 1e-6) df['sample_weight'] = df['sample_weight'] / df['sample_weight'].median()这里把近20日收益率标准差取倒数作为权重,再加1e-6防止除零;除中位数归一化后,权重在1附近波动,避免传给LightGBM时数值尺度过大。权重会改变模型的样本分布,所以训练集和验证集要用同一套权重逻辑计算,不能只在训练集加权、验证集不加。
3.3 时间序列切分:不能随机打乱训练集
这是从图像或推荐场景转过来的机器学习基础最容易犯的错。金融数据是强自相关的,今天的特征和明天的特征高度重叠;一旦随机打乱,训练集里就会混入验证集前后时间窗的信息,模型等价于先看到了答案,线下指标自然虚高。正确做法是严格按时间顺序做嵌套切分,每次用更早的数据训练、之后的数据验证。
from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5, gap=20) for train_idx, valid_idx in tscv.split(X): X_train, X_valid = X.iloc[train_idx], X.iloc[valid_idx] y_train, y_valid = y.iloc[train_idx], y.iloc[valid_idx]gap=20表示训练集末尾与验证集开头之间留20个交易日缓冲,这是为了避免序列自相关把训练集末尾的信息间接泄漏进验证集。n_splits=5意味着整个数据集被切成5段,每段轮流做验证。这里有个容易忽略的细节:TimeSeriesSplit要求X按时间升序排列,且索引最好是0到N-1的整数;如果直接用原始日期做索引,split出来的下标会失效。
4. 模型训练与调参:LightGBM 的那套参数直接用
特征和标签就绪后,进入模型训练环节。不少网上的方案会直接怼深度学习出来,但金融日频特征动辄几百维、样本量几十万,深度模型收益有限,调参成本却很高。这份PDF把重点放在GBDT模型上,我在实际项目里也默认先跑LightGBM,原因有三:处理缺失值和零值能力强,训练速度快,带原生的特征重要性解释。它做出来的基线如果验证集IC不达标,换复杂模型通常也救不回来。
4.1 为什么优先LightGBM而不是XGBoost
LightGBM和XGBoost同属GBDT家族,但LightGBM用了直方图算法和带深度限制的叶子生长策略,训练速度和内存占用在金融这种大宽表场景下明显更友好。金融特征里有大量的零值、空值,LightGBM能直接处理NaN;配合feature_fraction对特征抽样,还能减轻高相关特征之间的共线性影响。相比之下,深度模型更像黑匣子,在这类表格数据上没有压倒性优势,还要花更多时间调网络结构,不划算。
4.2 训练流程与早停设置
import lightgbm as lgb model = lgb.LGBMClassifier( n_estimators=2000, learning_rate=0.02, num_leaves=31, min_child_samples=50, feature_fraction=0.7, bagging_fraction=0.8, bagging_freq=1, reg_alpha=0.1, reg_lambda=0.1, random_state=42 ) model.fit( X_train, y_train, sample_weight=sw_train, eval_set=[(X_valid, y_valid)], eval_metric='multi_logloss', callbacks=[lgb.early_stopping(stopping_rounds=100), lgb.log_evaluation(50)] )训练参数里n_estimators给出上限,早停会在验证集指标连续100轮不改善时截断,实际树数由模型自己决定;learning_rate设0.02是为了配合较多的树数,想缩短训练时间可以提高到0.05同时调低stopping_rounds。样本权重在fit时通过sample_weight传入,验证集不用传。因为标签是三分类,评估指标选multi_logloss而不是auc;如果是二分类任务,再换成auc即可。
早停后建议用model.best_iteration_重新训练一次完整数据集,略微增大学习率做微调。但要注意,重训后的模型没有验证集做早停参考,等于放弃了模型选择的依据;我的习惯是保留早停模型部署,重训版本只用于离线对比。
4.3 参数表与特征筛选经验
| 参数 | 作用 | 经验范围 |
|---|---|---|
| n_estimators | 最大树数 | 500 - 2000,配合早停 |
| learning_rate | 步长 | 0.01 - 0.05 |
| num_leaves | 叶子节点数 | 15 - 63 |
| min_child_samples | 叶子最少样本 | 50 - 200 |
| feature_fraction | 特征抽样比 | 0.5 - 0.8 |
| bagging_fraction | 样本抽样比 | 0.7 - 0.9 |
| reg_alpha / reg_lambda | L1/L2正则 | 0 - 1 |
这套参数在大多数日频场景下可以直接起步,不要一上来就跑大网格搜索。金融模型评估次数越多,越容易在验证集上过拟合。特征筛选用model.feature_importances_排序,取top30到top50重训一次,验证集表现不掉就说明冗余特征可以删掉。
5. 避坑排查:金融机器学习里最容易翻车的五个场景
从模型训练到“看起来能赚钱”,中间隔着好几个隐形的大坑。这一章写的都是我在复现和实盘验证里真实踩过的记录,每条按现象、原因、解决整理,你在自己项目里遇到类似情况可以直接对照。
5.1 未来函数泄漏:回测曲线越漂亮越要警惕
现象:回测净值曲线45度向上,胜率高得离谱,样本外却完全失效,实盘信号一出市场就反向。
原因:特征或标签里混入了未来信息。常见来源有三个:用当根K线收盘价同时做特征和标签、前复权因子随着新行情重算、缺失值填充时用了整列均值。三者都会让模型提前看到答案。
解决:制定时点隔离规则——预测t+1时,所有输入的最新值不得晚于t日收盘;所有用未来窗口计算的特征在送入训练前统一shift(1)。上线前做一次滞后泄漏检查:把标签与其自身滞后多期的序列算相关性,如果lag=0到lag=-5这一段出现异常高相关,就需要逐特征排查。
5.2 全局标准化泄漏:scaler只能在训练集上拟合
现象:验证集指标不错,但换到另一段行情后预测分布整体偏移,置信度明显失真。
原因:建模前对整个数据集做StandardScaler或MinMaxScaler,scaler看到了验证集和测试集的统计量,等价于把未来信息渗进了训练过程。这是比较隐蔽的泄漏,不会像未来函数那样直接体现在回测曲线上,但会让模型在实盘中显得“时灵时不灵”。
解决:任何标准化或归一化组件都严格遵守先fit训练集、再transform验证集和测试集的顺序。滚动训练时每年重拟合一次scaler,不要长期复用。我会把这个顺序写进数据管线的强制逻辑里,避免哪次重构代码时顺手把scaler提前fit了全量数据。
5.3 样本不均衡和标签漂移:准确率90%,净值不涨
现象:三分类模型整体准确率超过85%,但查看每类明细后发现模型把绝大多数样本预测成“震荡”,多空信号几乎没有。
原因:标签切分用固定阈值,在震荡市里fwd_ret超过2%的样本比例可能不足10%,模型学到的经验是“预测震荡的期望损失最小”,自然退化成保守预测器。只看整体准确率时,这种退化反而显得模型很准。
解决:改用分位数切标签,让三类样本数量均衡;评估时分别看“上涨类”和“下跌类”的recall与precision,不看整体accuracy。如果实盘要求多空都做,还可以把中间类的权重调低,迫使模型在边界样本上做出方向判断。
5.4 震荡市过拟合:训练区间决定模型性格
现象:训练区间是单边上涨行情,验证区间选在震荡市,IC从正的0.06跌到-0.02,模型瞬间失灵。
原因:金融数据存在不同市场状态,趋势市和震荡市的特征响应方式不同;训练数据只覆盖一种状态时,模型学到的规律在其他状态下不成立。这个现象在日频数据里非常普遍,尤其在A股这种风格切换快的市场。
解决:训练数据至少覆盖一段趋势市和一段震荡市;如果可用数据不够,宁可拉长低频周期,也不要只保留最近一年的日线。评估时按市场状态分段报告,单独看震荡市子区间的表现。这一步对模型上线后的稳定性影响很大,不建议略过。
5.5 幸存者偏差:数据里只有活着的股票
现象:历史回测年化收益很高,但按回测信号实盘买入后发现,组合里很多标的表现远差于回测。
原因:部分数据源默认只返回当前存续股票,退市股和长期停牌股在历史数据里缺失。样本由“能活到今天”的股票构成,模型学到的是存活者的规律,回测结果天然偏高。这个问题在美股数据和A股历史回测里都容易遇到。
解决:数据导入时明确要求历史快照或退市股数据,关键时点做一次标的数量比对,比如2018年末的股票数量应与当时实际上市数量相近。如果数据源不支持历史快照,至少要在回测里把退市风险作为成本扣除一部分,否则数字会一直虚高。
6. 回测到部署:验证有效性的最后三公里
模型在线下指标合格之后,离可上线还有一段路。我习惯把回测分成三个层次:第一层是纯信号回测,用收盘价对齐信号;第二层加手续费、滑点和涨跌停约束;第三层做walk-forward模拟,每段只允许使用当时可得的数据。第三层通过后,模型才进入部署观察期。
6.1 回测约束:手续费、滑点与不可成交
回测成交价要用信号产生后的下一根K线开盘价,而不是信号当根收盘价,否则等于提前成交。手续费、滑点对高换手策略影响极大,初始回测时不加成本,最终评估时必须按双边万分之几加滑点模拟。涨跌停当天不可成交也要在撮合逻辑里显式处理,简单跳过可能造成净值虚高。
6.2 部署检查清单与模型更新节奏
| 检查项 | 做法 |
|---|---|
| 未来函数 | 特征全部shift后重跑验证 |
| 时间序列切分 | 确认没有随机打乱 |
| 数据历史快照 | 比对标的数量与当时上市数 |
| 回测成本 | 按真实手续费滑点重算 |
| 震荡市子区间 | 单独报告指标,不合并 |
模型部署后,每周记录线上预测分布与实际收益的对比,一旦分布偏离训练期太多就要触发重训。日频模型更新周期一般1到3个月,过于频繁会引入样本噪声,过慢又会跟不上市场风格变化。
从那以后,我每次搭金融机器学习模型,都强制在复盘脚本里先走一遍这三步:查泄漏、按时间切分、用真实成本回测。这个习惯帮我挡掉过好几次“回测很漂亮、实际不能上”的方案。希望帮到你。
本文还有配套的精品资源,点击获取