简介这是一份面向具备一定Python基础、对炒股与量化投资尚不熟悉的初学者的入门级demo源码围绕机器学习在A股量化策略中的应用展开。资源以完整项目形式呈现涵盖数据获取与清洗、特征工程、模型构建与训练、策略回测及风险管理等关键环节帮助读者理解如何用程序化方式判断买卖时机。压缩包共14个文件约727KB包含5个Python脚本、6张png图表、2个txt说明文件及1个gitignore脚本分别承担回测、特征处理、模型训练与主流程整合等职责图表则直观展示回撤、K线及预测效果。目前已有156人学习。通过这份源码读者可快速搭建可运行的学习环境对照模块理解量化策略从数据到回测的完整链路并借助示例掌握模型选型与效果评估的基本思路适合作为量化投资与机器学习结合的实践起点。1. 从一份机器学习量化策略 demo 源码说起它到底能跑出什么很多人第一次搜「基于机器学习的量化投资策略demo源码分享」心里想的其实是同一件事我不想再手撸一堆 if-else 规则了能不能用模型从历史行情里自己学出买卖点然后给我一份能直接跑的代码让我看看它到底靠不靠谱。这个诉求非常真实也非常容易翻车——因为量化策略的坑从来不在模型本身而在数据对齐、标签定义、回测撮合和过拟合这四件事上。这份 demo 源码要解决的核心问题是把「机器学习模型」和「量化交易策略」这两套语言接起来前者关心特征矩阵和标签后者关心信号、仓位、手续费和滑点。适合谁看适合已经会一点 Python、懂 pandas 基础、想从规则策略过渡到模型策略的从业者也适合做机器学习但没碰过金融时序的人用来理解为什么不能直接拿 sklearn 的交叉验证套在行情上。下面我按「先立住原理、再动手复现、最后讲坑」的顺序把这份 demo 拆成能抄作业的步骤。2. 策略骨架怎么搭从行情数据到可回测的信号2.1 为什么选日频 单标的 分类标签量化策略 demo 最容易犯的错是一上来就搞多因子、多标的、分钟频结果数据清洗就耗掉一周。我一般建议第一版锁死三个约束日频数据、单只流动性好的标的、二分类标签。原因是日频数据噪声相对可控单标的省去横截面对齐二分类让模型评估指标AUC、准确率直观可解释。标签定义是整份源码的灵魂。常见做法是「未来 N 日收益率是否为正」但这样会引入未来函数风险——你在 t 日收盘后才知道 t 日收盘价却用 t 日特征预测 t 到 tN 的收益必须保证特征只用到 t 日及之前的信息。demo 里我通常用shift(-N)构造标签再用dropna砍掉尾部无效行这一步不做回测收益能虚高到离谱。import pandas as pd import numpy as np def build_label(df, horizon5, threshold0.0): # 未来 horizon 日累计收益率 future_ret df[close].shift(-horizon) / df[close] - 1 # 二分类涨为1跌为0threshold 可调避免微小波动被当成正样本 df[label] (future_ret threshold).astype(int) # 尾部 horizon 行标签为 NaN必须丢弃 df df.dropna(subset[label]).reset_index(dropTrue) return df逻辑说明shift(-horizon)把未来价格拉到当前行threshold用来过滤掉「涨了 0.01% 也算涨」的噪声样本。参数上horizon 取 3 到 10 比较常见太短噪声大太长样本量骤减threshold 可以设成 0 或一个小的正数如 0.005取决于标的波动率。2.2 特征工程别堆指标先保证平稳性机器学习模型吃的是特征但金融时序特征最大的问题是非平稳。直接拿收盘价、成交量当特征模型学到的只是「价格水平」换一段行情就失效。demo 里我一般只保留几类经过平稳化处理的特征收益率、波动率、量比、均线偏离度。def build_features(df, windows(5, 10, 20)): df df.copy() # 日收益率平稳性最好 df[ret_1] df[close].pct_change() # 滚动波动率 df[vol_10] df[ret_1].rolling(10).std() # 量比当前成交量 / 过去5日均量 df[vol_ratio] df[volume] / df[volume].rolling(5).mean() for w in windows: ma df[close].rolling(w).mean() # 均线偏离度衡量价格相对均线位置 df[fma_dev_{w}] (df[close] - ma) / ma df df.dropna().reset_index(dropTrue) return df逻辑说明pct_change把价格转成收益率消除量纲rolling().std()捕捉波动聚集ma_dev是经典的均值回归特征。参数上windows 不要设太多3 到 4 个足够多了容易过拟合。注意所有 rolling 操作都只用历史数据不会引入未来信息。2.3 时序切分为什么不能随机 split这是新手最容易翻车的地方。用train_test_split(shuffleTrue)会把未来样本混进训练集模型在测试集上表现好得不像话实盘一跑就亏。正确做法是按时间顺序切分训练集在前测试集在后。def time_split(df, train_ratio0.7): n len(df) split int(n * train_ratio) train df.iloc[:split].copy() test df.iloc[split:].copy() return train, test feature_cols [ret_1, vol_10, vol_ratio, ma_dev_5, ma_dev_10, ma_dev_20] train, test time_split(df) X_train, y_train train[feature_cols], train[label] X_test, y_test test[feature_cols], test[label]逻辑说明train_ratio0.7是常见起点但金融数据建议留更长的测试集比如 0.6/0.4因为行情风格切换频繁。切分后不要再用测试集做任何特征选择或调参否则测试集就变成了验证集评估结果不可信。3. 模型训练与信号生成把概率变成仓位3.1 选逻辑回归还是树模型先跑通再谈调优demo 源码里我一般先用逻辑回归打底因为它可解释、训练快、不容易过拟合。树模型如 LightGBM在特征交互上更强但参数多、容易记住噪声。第一版用逻辑回归跑通全流程再换树模型对比是更稳的路径。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import roc_auc_score scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) # 注意用训练集的scaler不能重新fit model LogisticRegression(max_iter1000, C1.0) model.fit(X_train_s, y_train) proba model.predict_proba(X_test_s)[:, 1] print(AUC:, roc_auc_score(y_test, proba))逻辑说明StandardScaler必须只在训练集上 fit再 transform 测试集否则测试集的均值方差信息会泄漏。C是正则化强度的倒数越小正则越强金融数据建议从 0.1 到 1.0 之间试。AUC 在 0.52 到 0.58 之间就算可用别指望 0.8那是过拟合的信号。3.2 从概率到信号阈值不是拍脑袋定的模型输出的是概率策略需要的是买/卖/空仓信号。最简单的方式是设一个阈值概率大于阈值就持仓。但阈值直接决定交易频率和盈亏比不能随便设 0.5。def proba_to_signal(proba, buy_th0.55, sell_th0.45): signal np.zeros(len(proba)) signal[proba buy_th] 1 # 看多 signal[proba sell_th] -1 # 看空若允许做空 return signal signal proba_to_signal(proba)逻辑说明buy_th和sell_th之间留一个「空仓区」避免频繁交易被手续费吃掉。参数上可以先统计概率分布的分位数比如取 60% 分位作为买入阈值40% 分位作为卖出阈值。如果策略不允许做空把sell_th对应的信号设为 0 即可。3.3 回测撮合手续费和滑点必须算进去很多 demo 回测收益惊人是因为没算交易成本。日频策略单边手续费加滑点我一般按 0.1% 到 0.2% 估。信号在 t 日收盘生成t1 日开盘成交这个延迟也要模拟。def backtest(df, signal, cost0.001): df df.copy() df[signal] signal # 信号滞后一天模拟 t1 开盘成交 df[position] df[signal].shift(1).fillna(0) # 策略日收益 持仓 * 当日收益率 df[strategy_ret] df[position] * df[ret_1] # 仓位变化时扣成本 df[turnover] df[position].diff().abs().fillna(0) df[strategy_ret] - df[turnover] * cost df[cum_ret] (1 df[strategy_ret]).cumprod() return df逻辑说明shift(1)是关键保证不用当日信号吃当日收益。turnover衡量仓位变化乘以cost得到交易成本。参数上cost 可以按标的流动性调整大盘股 0.001小盘股可能要到 0.003。回测完一定要看最大回撤和夏普不只看累计收益。4. 避坑与排查这份 demo 最容易翻车的 5 个地方4.1 现象回测收益高得离谱实盘完全对不上原因最常见的是未来函数。比如用当日收盘价计算的特征却在当日收盘就生成信号并成交或者标签构造时用了未来数据但没对齐。解决逐行检查特征和标签的时间戳确保任何特征只用到 t 日及之前的数据信号至少滞后一天成交。可以在回测里加一行断言检查position是否只依赖历史。4.2 现象模型 AUC 很高但策略不赚钱原因AUC 衡量的是排序能力不是盈亏。模型可能在高波动样本上预测准但那些样本对应的收益很小真正赚钱的大涨样本反而预测错。解决不要只看 AUC要看按概率分组的收益单调性。把测试集按预测概率分成 5 组看最高组的平均未来收益是否显著高于最低组。4.3 现象换一段数据重新训练特征重要性完全变了原因特征之间高度相关比如多个均线偏离度本质是同一信息。树模型会随机选一个导致重要性不稳定。解决做特征聚类或相关性过滤保留相关性低于 0.8 的特征或者用 PCA 降维后再训练。逻辑回归的系数相对稳定可以作为参考。4.4 现象策略在训练集上表现完美测试集一塌糊涂原因过拟合。样本量太小、特征太多、模型太复杂都会导致。解决减少特征数量优先保留经济学含义清晰的增加正则化强度用滚动窗口做 walk-forward 验证而不是单次切分。walk-forward 能模拟真实交易中不断重新训练的过程。4.5 现象回测里频繁交易收益被手续费吃光原因阈值设得太靠近 0.5概率在阈值附近抖动就触发交易。解决拉大买卖阈值之间的空仓区或者加一个「信号持续 N 天才生效」的过滤。也可以把交易成本调高做压力测试看策略在成本翻倍时是否还能盈利。5. 进阶技巧用 walk-forward 验证和概率校准把 demo 变成可信方案单次时间切分的评估结果随机性太大换一个切分点可能结论完全相反。我后来习惯用 walk-forward把数据分成多个滚动窗口每个窗口用前一段训练、后一段测试最后把测试段的收益拼起来。这样得到的净值曲线更接近真实交易中「不断重新训练模型」的过程。def walk_forward(df, feature_cols, train_len500, test_len100): results [] start 0 while start train_len test_len len(df): train df.iloc[start:start train_len] test df.iloc[start train_len:start train_len test_len] scaler StandardScaler() X_tr scaler.fit_transform(train[feature_cols]) X_te scaler.transform(test[feature_cols]) model LogisticRegression(max_iter1000, C0.5) model.fit(X_tr, train[label]) proba model.predict_proba(X_te)[:, 1] test test.copy() test[proba] proba results.append(test) start test_len # 窗口向前滚动 return pd.concat(results, ignore_indexTrue)逻辑说明train_len和test_len决定窗口大小日频数据常用 500/100即约两年训练、四个月测试。start test_len让测试窗口不重叠避免重复计算。跑完 walk-forward 后把每段测试的收益按时间拼接得到一条更可信的净值曲线。另一个技巧是概率校准。逻辑回归输出的概率未必等于真实频率可以用CalibratedClassifierCV做 isotonic 或 sigmoid 校准让「预测 0.6」真的对应 60% 的上涨概率。校准后阈值的选择更有依据也能改善仓位管理——比如按概率大小动态调仓位而不是简单的 0/1。from sklearn.calibration import CalibratedClassifierCV base LogisticRegression(max_iter1000, C0.5) calibrated CalibratedClassifierCV(base, methodsigmoid, cv3) calibrated.fit(X_train_s, y_train) proba_cal calibrated.predict_proba(X_test_s)[:, 1]逻辑说明methodsigmoid适合样本量不大的场景cv3表示内部用 3 折交叉验证做校准。校准后概率更平滑但会牺牲一点排序能力AUC 可能略降这是正常取舍。最后说个我自己的习惯任何一份量化 demo我都会先跑一遍「随机信号」作为基准。如果随机信号的净值曲线和模型信号差不多那说明模型没学到东西收益只是市场 beta。只有模型信号显著跑赢随机基准才值得继续投入。这个习惯帮我省了很多自欺欺人的时间。希望帮到你。本文还有配套的精品资源点击获取