尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
机器学习量化投资实战:从数据到回测的完整策略搭建指南
简介一套帮助初学者快速入门的机器学习量化投资演示源码以A股市场为例面向已有一定Python基础、但对炒股和量化投资了解不深的读者。量化投资的核心是通过历史数据分析发现并利用市场规律在风险可控前提下追求超额收益机器学习模型具备自我学习与适应能力可提升预测准确性策略本质是跟随趋势而非创造趋势。代码完整覆盖数据获取、缺失值清洗、特征工程、模型训练与回测评估等环节并融入止损设置、仓位控制等风险管理思想。压缩包共14个文件含5个Python脚本分别承担数据读取、特征构建、模型建模、回测执行和主流程串联、6张可视化图表K线、决策树预测、最大回撤曲线等、2个文本说明文档及依赖清单整体仅727KB轻量易读。已有156人学习下载通过运行示例读者可直观学会处理金融时间序列数据、应用常见机器学习算法预测市场走势并通过图表对照回测效果是一份实操性很强的入门参考。1. 机器学习加量化投资这可能是你最快拿到一份完整策略的方式把「基于机器学习的量化投资策略」做成一个 demo最大的价值不是帮你躺赚而是用最小的成本把「数据 → 特征 → 模型 → 回测」这条链路完整走一遍。很多人卡在第一步是因为资料太散看了一堆模型原理却不知道从哪下手而这份 demo 源码的意义在于它给了一个可以直接改的骨架换数据源、调特征、换模型、改回测规则每一步都是透明的。适合三类人想入门量化但没写过完整策略的工程师、手里有数据但不知道怎么跟机器学习结合的从业者以及想快速验证一个想法再决定是否投入的独立开发者。这篇笔记就按我实际搭建这类 demo 的顺序从数据、特征、模型到回测逐个拆开讲最后把坑也一并交代清楚。2. 从数据到特征搭一个能跑通的机器学习量化 demo 骨架2.1 数据源选型免费数据也有讲究别一上来就搞高频做 demo 第一个要解决的问题是数据从哪来。常见做法是用 akshare 或 tushare 这类免费接口拉日线行情没必要一开始就上高频或付费数据。akshare 的好处是免注册、接口多拉 A 股日线数据几行代码就能完成适合做原型tushare 需要 token但数据规整度更好如果后续要加大规模可以再迁过去。我一般会用 akshare 先拉一只股票的历史日线把数据存成 parquet 格式避免每次跑 demo 都重新请求接口。这里有个细节接口返回的列名是中文比如「收盘」「开盘」「成交量」这些做特征工程前最好先统一改成英文字段名后面写代码能少很多麻烦。import akshare as ak import pandas as pd # 拉取平安银行 2018 年至今的日线数据前复权 df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20180101, end_date20241231, adjustqfq, ) # 统一改成英文字段名方便后续处理 df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, }) df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() # 只保留需要的列 df df[[open, high, low, close, volume]] df.to_parquet(stock_000001.parquet) print(df.head())这段代码里adjustqfq表示前复权这是做历史回测必须的否则分红送股会让价格出现跳空模型学到的全是假信号。sort_index()保证时间序列从早到晚排列后面做滚动切分时才不会乱。存成 parquet 而不是 csv是因为 parquet 带类型信息、读取快而且文件体积小。需要注意akshare 接口偶尔会变返回的列名可能调整跑之前先print(df.columns)看一眼。另外免费接口有频率限制demo 阶段拉几十只票没问题要是拉全市场就得考虑本地缓存或者换付费源了。2.2 特征工程先把技术指标和目标变量之间的逻辑理顺有了原始行情下一步是造特征。常见做法是把价格和成交量加工成三类信号动量类过去 N 日涨跌幅、波动类过去 N 日收益标准差、量价类成交量与均量的比值。这三类特征基本覆盖了趋势跟踪和均值回归两类主流策略的信息需求。import numpy as np df[ret_1] df[close].pct_change(1) # 最近 1 日收益 df[ret_5] df[close].pct_change(5) # 最近 5 日收益 df[ret_10] df[close].pct_change(10) # 最近 10 日收益 df[ret_20] df[close].pct_change(20) # 最近 20 日收益 df[vol_10] df[ret_1].rolling(10).std() # 10 日波动率 df[vol_20] df[ret_1].rolling(20).std() # 20 日波动率 df[vol_ratio] df[volume] / df[volume].rolling(20).mean() # 量比 df[high_low_range] (df[high] - df[low]) / df[close] # 日内振幅 df[close_open_range] (df[close] - df[open]) / df[open] # 开盘收盘相对位置 # 删除 NaN 行 df df.dropna()pct_change()算的是收益率而非价格差这样对价格绝对水平不敏感不同股票之间的特征才能横向对比。rolling().std()是滚动标准差用来刻画波动vol_ratio大于 1 说明放量小于 1 说明缩量这是量价配合的核心指标。特征这块有个容易被忽略的坑所有特征计算只能用 t 时刻及之前的数据不能用未来信息。比如计算 5 日动量用的必须是close在 t-4 到 t 的数据而不能包含 t1 之后的价格。代码里pct_change(5)在 t 时刻的值天然只用到过去 5 天这是安全的。但如果你手写特征比如用shift(-n)把未来数据挪进来那训练出来的模型就是典型的「未来函数」回测净值曲线会漂亮得骗人。特征维度上demo 阶段 8 到 10 个特征足够了不需要堆上百个。特征太多会让模型过拟合历史噪音而且训练时间变长。先把这些基础特征跑通后面要加量价相关性、RSI、MACD 这些指标都是在df上加一列的事。3. 模型选型与训练为什么 demo 首选 LightGBM 而不是 LSTM3.1 先定预测目标预测涨跌方向比预测价格更靠谱量化策略里机器学习通常做两件事预测未来价格回归或者预测涨跌方向分类。做 demo 我强烈建议先做分类——预测未来 5 个交易日收益是否大于某个阈值。原因很简单价格预测容易过拟合而且哪怕预测准了买卖点也难落地分类问题不仅好评估还能直接映射成买入/不买入的决策。标签怎么打用「未来收益 阈值」来标。注意一定是「未来」也就是要用 t5 的收盘价相对于 t 时刻的收益这需要把未来数据往前挪但要保证挪完之后和特征严格对齐。# 定义标签未来 5 日收益 2% 记为 1否则为 0 df[future_ret] df[close].shift(-5) / df[close] - 1 df[label] (df[future_ret] 0.02).astype(int)shift(-5)是把未来第 5 天的数据拿到当前行。这里要特别注意打完标签后最后 5 行会因为未来数据缺失而变成 NaN需要dropna()删掉。阈值 2% 不是定死的它决定正负样本的比例——如果设置太高样本里 1 太少模型学不到东西设置太低预测准确率虚高但没有实际交易价值。A 股日线级别上5 日涨 2% 大约对应 40% 左右的正样本比例是个比较均衡的起点。3.2 时序切分而不是随机切分这是训练里最重要的一步训练集和测试集的划分股票数据和常规机器学习有本质区别。随机切分会把未来数据混进训练集模型在测试集上表现好是因为它「见过」测试集时间段附近的走势。正确的做法是按时间点切分早段训练晚段测试中间绝对不能有重叠。split_date 2023-01-01 feature_cols [ret_1, ret_5, ret_10, ret_20, vol_10, vol_20, vol_ratio, high_low_range, close_open_range] X df[feature_cols] y df[label] train_mask df.index split_date test_mask df.index split_date X_train, X_test X[train_mask], X[test_mask] y_train, y_test y[train_mask], y[test_mask]我一般会把切分日期放在数据总量后 20% 到 30% 的位置比如 2018 到 2024 的数据切分点设在 2023 年正好留下近两年的样本做验证。这样训练集覆盖牛熊周期测试集则是模型「没见过」的最新行情评估结果更有说服力。另外切分时最好用日期篝索引直接过滤不要用iloc的整数位置因为后期如果删除了中间的行位置就全错位了。3.3 LightGBM 参数初始化从一组稳健的默认值出发树模型是结构化数据的首选LightGBM 训练快、内存占用小而且对特征缩放不敏感不需要做标准化特别适合量化这种表格特征场景。LSTM 这类神经网络不是不行而是 demo 阶段数据量小、调参成本高收益不成正比。import lightgbm as lgb model lgb.LGBMClassifier( n_estimators300, # 树的数量 learning_rate0.05, # 学习率 num_leaves31, # 每棵树的叶子数 max_depth5, # 树的最大深度 subsample0.8, # 样本采样比例防过拟合 subsample_freq1, # 每个迭代都采样 colsample_bytree0.8, # 特征采样比例 min_child_samples50, # 叶子最少样本数 reg_alpha0.1, # L1 正则 reg_lambda0.1, # L2 正则 random_state42, ) model.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricauc, callbacks[lgb.early_stopping(50), lgb.log_evaluation(0)], )上面这组参数是一套比较稳的起点。subsample和colsample_bytree都是 0.8相当于每次迭代只用 80% 的样本和 80% 的特征能显著降低过拟合。min_child_samples50是防止叶子节点落到太少的样本上在金融数据这种噪音很大的场景里很重要。early_stopping(50)的意思是测试集 AUC 连续 50 轮不提升就停省时间的同时也能防止训练过头。这里有个值得说的点eval_set传的是测试集但它的作用只是用来监控过拟合、触发早停模型并不「学习」测试集。实际做生产级模型时应该再切一份验证集出来做早停监控测试集留着最终评估。demo 阶段为了简单可以只用训练/测试两段但心里要清楚这个简化会带来轻微乐观偏差。4. 回测与评估别让净值曲线骗了你4.1 回测引擎用向量化操作模拟完整交易流程模型训练完真正的考验是回测。最简单的回测不是写循环模拟每天买卖而是向量化计算——用 Pandas 的shift来对齐信号和收益一次性算出整个测试期的每日收益。这么做的好处是代码短、速度快而且不容易在循环里埋 bug。# 获取模型对测试期的预测概率 test_prob model.predict_proba(X_test)[:, 1] # 构建测试期 DataFrame test_df df.loc[test_mask].copy() test_df[pred_prob] test_prob test_df[daily_ret] test_df[close].pct_change(1) # 信号预测概率大于 0.5 就持有否则空仓 test_df[position] (test_df[pred_prob] 0.5).astype(int) # 关键用 shift(1) 实现 T1 成交避免用当日信号买当日收益 test_df[strategy_ret] test_df[position].shift(1) * test_df[daily_ret] test_df[strategy_ret] test_df[strategy_ret].fillna(0) # 计算净值曲线 test_df[net_value] (1 test_df[strategy_ret]).cumprod() print(最终净值:, test_df[net_value].iloc[-1])position.shift(1)是回测里最重要的一行它把信号滞后一天模拟的是「今天收盘后产生信号明天开盘才能买入」的真实约束。如果不做 shift就等于用当天的信号买当天的收益这是典型的幸存者偏差回测收益会被人为放大。A 股是 T1 交易当天买的股票最早第二天才能卖所以信号天然要滞后一拍。0.5 的阈值也不是必须的。如果预测概率集中在 0.4 到 0.6 之间可以画一下概率分布图然后选择一个能让交易次数和胜率平衡的阈值。常见做法是取训练集上预测概率的 60 分位或者 70 分位作为阈值这样信号更「稀缺」交易频率下降但每笔交易的胜率可能更高。4.2 评估指标夏普比率、最大回撤和年化收益要一起看净值曲线给出了一个整体观感但不能只看最终净值上去还是下来要看几个硬指标。年化收益衡量赚钱能力最大回撤衡量风险夏普比率衡量风险调整后的收益——这三个缺一不可。一个策略年化收益 30% 但回撤 40%和年化 15% 回撤 8%后者在实际执行中反而更容易拿住。# 年化收益 days len(test_df) strategy_annual_ret (test_df[net_value].iloc[-1]) ** (252 / days) - 1 # 最大回撤 rolling_max test_df[net_value].cummax() drawdown test_df[net_value] / rolling_max - 1 max_drawdown drawdown.min() # 夏普比率年化收益 / 年化波动 daily_std test_df[strategy_ret].std() sharpe (test_df[strategy_ret].mean() - 0) / daily_std * np.sqrt(252) print(f年化收益: {strategy_annual_ret:.2%}) print(f最大回撤: {max_drawdown:.2%}) print(f夏普比率: {sharpe:.2f})夏普比率里的0是无风险利率demo 阶段可以简化为 0但心里要明白这意味着没有考虑资金成本。当策略的年化收益在 10% 左右波动时无风险利率的影响就开始变得不可忽视了。另外回撤指标用cummax()算的是「从历史最高点到当前的最低点」它衡量的是心理和资金链的承受极值——很多策略不是亏没的而是回撤大到让人拿不住、提前止损出局的。回测到这里算是「能跑」了但距离「可信」还有两个额外检查点。第一个是将策略收益和同期沪深 300 指数对比看超额收益是正的还是负的第二个是检查交易次数——如果整个测试期只交易了不到 20 次统计上这个结果没有意义任何一次大盈亏都会让评估指标失真。这两个检查不需要写复杂代码打印几行数据就能看出来。5. 避坑指南量化 demo 最常见的 5 个翻车现场5.1 未来函数偷跑了数据净值曲线好看到不真实做回测时发现策略净值曲线一路向上、回撤几乎为零先别高兴。这个现象八成是未来函数导致的。最典型的写法是信号计算用到了当天的收盘价而持仓也在当天生效——相当于你收盘时看到了结果再决定这天买不买这在真实世界是不可能的。排查方法是检查策略收益那行有没有用shift(1)把持仓滞后一天另一个隐蔽来源是特征计算里用了未来数据比如用 t1 的成交量算量比。解决的方式就是我的代码里那行position.shift(1)以及所有特征必须保证只用截至 t 时刻的数据。5.2 随机切分训练集模型「见过」未来行情训练时如果把数据随机打乱再切分模型会看到测试集时间段附近的行情片段测试集上的 AUC 会偏高但实盘完全不是这样。现象是训练时 AUC 0.65随机切分后测试 AUC 也是 0.65 左右但一换到按时间切分测试 AUC 直接掉到 0.52。原因是金融数据是时间序列相邻日期的特征高度相关随机切分等于把相似的样本同时放进训练和测试里。解决方式很简单严格按日期切分训练、测试集甚至最好在训练集和测试集之间留出几天的空隙防止滚动窗口特征把信息串过去。5.3 标签不平衡被忽略预测全 0 也能拿到高准确率如果阈值设得过高比如未来 5 日收益 5%正样本比例可能只有 10%。这时代码里光是model.fit、predict跑通准确率轻松超过 90%——因为模型只要全部预测 0 就对了九成。现象是准确率很好看但策略净值基本是平的几乎没有买入信号。原因是分类阈值没有结合样本分布。解决方式是用confusion_matrix看预测分布或者直接计算预测概率的分位数把阈值定在训练集预测概率的 30 到 70 分位之间也可以给少数类加权重比如class_weightbalanced。5.4 手续费和滑点设为零回测收益和实盘差出一个数量级demo 里经常有人忘记加交易成本结果回测年化 30%实盘跑下来只有 8%。现象是策略看起来每笔都在赚钱但交易频率越高实盘和回测的差距越大。原因是 A 股双边手续费加印花税大约千分之二到千分之三高频交易还有滑点和冲击成本。解决方式是在回测收益计算时扣除成本——每次信号从 0 变 1 或者从 1 变 0都扣掉一个固定比例的费率。常见做法是单边扣0.001到0.002然后重新评估年化收益是否还能看。trade_flags test_df[position].diff().abs().fillna(0) cost trade_flags * 0.001 # 单边千分之一成本 test_df[strategy_ret_net] test_df[strategy_ret] - cost5.5 只用一只股票做回测结论没有任何代表性训练和回测都在同一只股票上做换一只股票立刻亏损。这个现象说明模型学到的不是规律而是那只股票特有的历史路径。原因是单只股票的行情受个股事件影响极大模型很容易记住这些特殊情况。解决方式是多选几只不同行业、不同市值的股票分别训练、分别回测看看策略在多数标的上是否还能保持正收益。做 demo 至少要跑 3 到 5 只票再下结论。如果只在某一只票上有效那大概率是过拟合不是策略本身有优势。6. 让 demo 往前走一步滚动训练与信号稳定性验证模型做完回测并不算完demo 要真正有价值还得解决一个关键问题模型会不会「过期」金融市场的统计特征会缓慢漂移一年前有效的特征权重现在可能已经失效。常见做法是把一个 4 年的数据集按时间切成多段每段都训练一个新模型然后用紧接着的下一年做验证。这样能测出模型在不同市场状态下的表现而不只是撞上一个牛市窗口。滚动训练的实现不复杂设定训练窗口长度和步长循环里反复切数据、训练、预测。train_window 700 # 用大约 700 个交易日做训练 step 200 # 每 200 个交易日滚动一次 for start in range(0, len(df) - train_window - 200, step): train_data df.iloc[start:start train_window] test_data df.iloc[start train_window: start train_window 200] X_tr train_data[feature_cols] y_tr train_data[label] X_te test_data[feature_cols] model lgb.LGBMClassifier(n_estimators200, learning_rate0.05, num_leaves31, min_child_samples50, random_state42) model.fit(X_tr, y_tr) pred model.predict_proba(X_te)[:, 1] # 在这里记录每段的 AUC、收益、交易次数滚动训练得到的结果可以画一张分段的年化收益柱状图观察策略在每段的稳定性。如果某一段收益明显为负不要急着否定策略先看那段市场是否处于单边下行——负收益在熊市不是 bug但回撤比指数小就说明有一定防守能力。实操中还有一个具体的验证技巧值得养成习惯每次训练完把特征重要性排序打印出来看看排在前三的特征是否和经济直觉一致。如果 20 日波动率排名第一说明模型在捕捉低波动上涨的规律如果某个随机噪音特征排第一就说明特征工程里有问题。做这个 demo 的过程里我自己栽过最大的跟头就是一开始只盯着回测净值曲线忘了加手续费和做时间切分当时差点把一个纯属未来函数的策略当成「圣杯」上线。后来养成一个习惯任何策略结果出来先在三张表上交叉验证——按时间切分的测试集、扣费后的收益、滚动窗口分段表现。这三张表过不了再多花哨的模型都只是自我感动。这也是我一直对新手工程师重复的建议。希望这篇笔记能帮你在量化这条路上少走几步弯路让第一版 demo 既能跑通、也经得起质疑。本文还有配套的精品资源点击获取
RELATED

相关推荐

人脸关键点检测必备:PFLD轻量模型源码+权重+ONNX部署全攻略

人脸关键点检测必备:PFLD轻量模型源码+权重+ONNX部署全攻略

简介:基于PFLD算法的人脸关键点检测完整工程,提供Python源码、预训练模型与ONNX导出文件,适合算法学习者和有检测落地需求的研究者。压缩包共74个文件,大小约89.88MB,涵盖py源码、pth模型、onnx模型、jpg样例、txt说明…

📅 2026/10/12 14:24:09
Python身份证识别系统源码拆解:从照片到结构化字段的精度优化

Python身份证识别系统源码拆解:从照片到结构化字段的精度优化

简介:这是一份基于Python的身份证识别系统源码,面向希望学习OCR与深度学习落地应用的开发者,尤其适合具备一定图像处理基础、想研究高精度证件识别方案的中高级学习者。资源包共2个文件,包含1个py源码与1个md说明文档,…

📅 2026/10/12 14:19:08
AI一周事件(2025年8月20日-8月26日):TaoToken 统一 Key 接入周报

AI一周事件(2025年8月20日-8月26日):TaoToken 统一 Key 接入周报

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/12 14:19:08
MORE NEWS

更多资讯

📰

Hermes Agent实战:轻量级Agent框架的配置化任务编排与工具调用

如果你正准备上手一个Agent框架,想把任务编排、工具调用、Prompt管理这些事情一次理顺,那Hermes Agent确实值得花点时间了解。这篇内容会从“它到底解决什么问题”讲起,然后带你完成安装、配置,并跑通一个可扩展的Agent实例。全程…

📰

SQL Server 2008R2在Win11安装失败:兼容补丁与排查指南

简介:SQL Server 2005 与 2008 R2 在 Windows 11 下安装时经常出现兼容性拦截、服务无法启动或 ATL 合成失败等报错,这份兼容补丁包正是针对这类问题整理的解决方案,适合需要在 Win11 上部署旧版数据库、开展遗留系统维护或实验学习的开发与运…

📰

openclaw 用例翻译笔记:Event Guest Confirmation 的 Twilio 与 OpenAI 配置拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

FastReport批量打印实战:从循环直打到队列调度的稳定方案

1. 批量打印这件事,为什么值得单独拎出来说做过企业信息化的人都有一个共识:报表模块往往是项目里最不起眼、但上线后挨骂最多的部分。单张打印谁都会做,拖一个控件、绑一个数据源、点一下预览,完事。可一旦业务方甩过来一句“这批…

📰

C++实战:从CSV解析到疫情数据统计管线

1. 从一份CSV到一套分析管线:这个项目的真实定位很多人看到“用C分析疫情数据”这个标题,第一反应是:为什么不用Python?pandas几行代码就能搞定的事,何必用C折腾?这个问题我当初也问过自己。后来真正动手做…

📰

大模型 Tool Use 手写实战:三套实现方案全拆解

面试现场碰到这种题,估计不少人跟我第一反应一样:Tool Use 不就是 Function Calling 吗,直接调接口就行。可真到了“不借助任何框架,不调 SDK,白板角落写一个运行时”的时候,才会发现里面全是细节。 手写一…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬