ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python机器学习量化策略源码:从Tushare取数到回测的完整实践

Python机器学习量化策略源码:从Tushare取数到回测的完整实践 简介Python基于机器学习的量化投资策略项目适合毕业设计、期末大作业与课程设计场景完整覆盖数据获取、特征工程、模型训练、策略回测和结果可视化等环节。压缩包共15个文件、1.14MB其中5个Python脚本分别对应数据下载、特征构建、模型预测、主流程和回测6张图表展示柱状图、K线、炒股界面、最大回撤及决策树预测股价涨幅等结果2个文本文件提供股票列表与依赖约束另有docx操作手册辅助部署。代码中的注释对新手友好可以按main、feature、model、backtest的顺序理解量化策略从数据到验证的完整链路同时提供tushare token配置、股票列表与依赖清单便于快速接入数据并复现结果。目前已有286人学习使用下载后简单配置即可运行也适合用于答辩演示或作为量化入门参考。1. 用 Python 把量化策略跑通这份机器学习源码包能帮你省掉哪些事量化的第一道坎不是策略而是把数据、特征、模型、回测这条链路完整拼起来。这份基于 Python 和机器学习的量化投资策略源码包恰好就是针对这个痛点来的它从 Tushare 拉历史行情用特征工程加工出均线、波动率、量比这类因子再交给决策树预测股价涨幅最后在 backtest.py 里跑回测输出资金曲线、最大回撤和 K 线图。对正在做毕业设计、课程设计或者期末大作业的人这是一个能直接当主线骨架的完整项目代码带注释、目录规整数据到模型到回测的每一步都看得见。你不用从零开始写数据清洗和回测框架把精力放在理解策略逻辑和调参上比对着空文档死磕效率高得多。2. 项目结构与数据链路从源码包目录到 Tushare 取数2.1 源码包结构与各模块职责先说目录。源码包解压后主目录下是这几个文件我把职责列在表格里方便你定位改哪里文件职责main.py主入口串联取数→特征→模型→回测data.py行情数据获取与缓存封装 Tushare 接口feature.py特征工程均线、收益率、波动率、量比等model.py决策树模型训练与预测backtest.py回测引擎计算净值、最大回撤、年化收益stock_list.txt股票池按行放 tushare 标准代码requirements.txtPython 依赖清单手册.1.docx使用说明与实验报告参考img/运行截图K线图、柱状图、最大回撤、炒股界面等这个结构是典型的数据-特征-模型-回测四段式也是量化项目最常见的工程划分。main.py 只做编排真正的逻辑拆在 feature 和 model 里——这样做的好处是你后面想换模型、换数据源都只用动对应的模块backtest.py 的净值计算逻辑可以原样复用。我把 stock_list.txt 特意放在最后说因为它决定了数据下载的股票池范围。一般格式是每行一个代码像 000001.SZ、600519.SH 这样的 tushare 标准代码。如果你想控制运行时间第一件事就是把这里面的股票数量往下降先拿 2-3 只跑通全流程再扩大到全池子。2.2 Tushare 数据获取与 token 配置data.py 里封装的取数逻辑核心就是 tushare 的 pro 接口。第一次运行之前需要先完成 token 初始化。常见做法是去 tushare.pro 注册账号在个人主页拿到一串 token然后在代码里调用 ts.set_token()。项目里那张 tushare_token.png 就是作者给你对照看的token 该填在什么位置一目了然。import tushare as ts # 设置 token在 tushare.pro 个人主页获取本质是一串密文 ts.set_token(你的token粘贴到这里) pro ts.pro_api() # 按股票代码和起止日期拉日线 df pro.daily(ts_code000001.SZ, start_date20200101, end_date20231231) print(df.head())这里有几个参数值得强调。ts_code 是 tushare 标准代码格式是代码.交易所SZ 是深交所、SH 是上交所写错直接返回空数据start_date 和 end_date 要求是 YYYYMMDD 格式的字符串不是 datetime 对象传错类型会直接报错。返回的 DataFrame 里trade_date 字段是 int 类型的日期比如 20231231做排序和索引之前最好先统一转成字符串。还有一个容易踩的坑pro.daily() 返回的是未复权行情遇到分红除权股价会产生跳空直接拿它算收益率会把除权当成大跌。我一般会在特征工程之前就解决复权问题要么用前复权口径的数据要么单独拉复权因子具体操作放到第 5 章细说。data.py 里通常还会做一层本地缓存把拉下来的数据存成 csv 或 pickle。这个设计很实用——Tushare pro 接口对调用频率有限制每次都全量重拉既慢又容易触发限流。你改成自己代码的时候这层缓存建议保留它可以让你反复调参时不用重复等接口。2.3 特征工程feature.py 里在算什么这一节是整个机器学习部分的地基。feature.py 的作用是把原始 OHLCV 数据加工成模型能吃的特征矩阵同时生成训练用的标签。项目里预测的对象是股价涨幅所以标签是未来一段时间的收益率而不是简单的涨跌分类。import pandas as pd def make_features(df): df df.sort_values(trade_date).copy() # 收益率当日收盘相对昨收的变化 df[ret] df[close].pct_change() # 简单均线5 日和 10 日均线捕捉中期趋势 df[ma5] df[close].rolling(5).mean() df[ma10] df[close].rolling(10).mean() # 量比当日成交量 / 5 日均量反映资金活跃度 df[vol_ratio] df[vol] / df[vol].rolling(5).mean() # 波动率20 日收益标准差刻画风险水平 df[volatility] df[ret].rolling(20).std() # 标签下一交易日收益率用 shift(-1) 把未来挪到今天这一行 df[label] df[ret].shift(-1) return df.dropna()参数含义要讲清楚。rolling(5) 和 rolling(10) 是滑动窗口大小对应 5 日均线和 10 日均线窗口越大曲线越平滑、对短期波动越迟钝volatility 用 20 日窗口这是金融里计算历史波动率的常见默认值。最后一行 dropna() 会把序列开头没有足够窗口的数据全部丢掉因为前几行的 rolling 结果是 NaN模型不能吃空值。最关键的逻辑在 label 那一行。shift(-1) 表示把明天的收益率往前挪一天让今天的特征对应明天的结果这样模型学到的是今天的特征 → 明天的涨跌。方向不能搞反如果写成 shift(1)等于用未来的特征预测过去的收益整个训练集是错位的后面回测再好看也是废的。特征数量不用贪多。ret、ma5、ma10、vol_ratio、volatility 已经能组成一个可解释的因子集。想加特征的话常见方向是 RSI、MACD 这类技术指标但每加一个就要考虑计算窗口和缺失值特征间的相关性也会涨。决策树对冗余特征还算耐受但特征多了训练时间和过拟合风险会一起上来。3. 模型与策略落地决策树预测股价涨幅与买卖信号生成3.1 为什么选决策树可解释性与小样本友好model.py 用的模型是决策树这不是随便选的。量化策略里模型的可解释性很重要——回测亏了、实盘翻了车你得能说清楚是哪些因子、哪些阈值导致的。决策树天然能把决策路径可视化每一条分裂规则都可以翻译成如果量比大于某值且均线多头则预测上涨这种透明性是神经网络给不了的在答辩和报告里也特别好讲故事。另一个原因是小样本友好。训练数据本质上是一只股票的历史行情样本量通常只有几千行特征也就个位数。这个规模下XGBoost、深度学习反而容易过拟合决策树配合合适的深度限制能在小样本上训出稳定的效果。课程设计里展示决策树的分裂路径也比展示一个黑匣子神经网络更让导师信服。model.py 里常见的做法是把预测涨幅当成回归问题或分类问题。回归版本直接输出下一天的预测收益率分类版本则把收益按正负切成上涨/下跌两类。项目截图里写的是决策树预测股价涨幅本质上是回归思路但信号生成阶段依然是按预测涨跌幅的正负来转成买卖指令。这种基于机器学习的量化投资方法核心就是让模型自己从历史数据里找特征与未来收益的关系而不是靠人定死板的均线金叉规则。3.2 训练与预测model.py 的建模流程from sklearn.tree import DecisionTreeRegressor def train_model(df): feature_cols [ret, ma5, ma10, vol_ratio, volatility] X df[feature_cols].values y df[label].values # 按时间顺序切分前 80% 训练、后 20% 测试 split_idx int(len(df) * 0.8) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] # 限制深度和叶子节点数防止学到噪声 model DecisionTreeRegressor(max_depth5, min_samples_leaf30) model.fit(X_train, y_train) return model这段代码有两个关键点。第一是切分方式我这里没有用 train_test_split 的默认随机切分而是按时间顺序做了 8:2 的硬切分。金融数据是强时序依赖的随机切分会把未来的数据混进训练集让模型偷看未来回测结果虚高——这是初学者最容易翻车的点没有之一。第二是模型参数max_depth5 限制树深度不超过 5 层min_samples_leaf30 要求每个叶子节点至少有 30 个样本这两个参数直接控制模型复杂度。深度太深、叶子太小时决策树会去记每一天的噪声训练集完美、测试集惨败。预测阶段就很简单了拿到训练好的模型对全量特征调用 predict得到每一行的预测涨幅。这里训练集和全量数据用的是同一套特征列顺序不能乱否则 predict 的数据是错位的。3.3 从预测到信号main.py 的编排逻辑main.py 是整个项目的主流程职责是把前面的模块串起来再把预测结果转成策略信号。信号规则是策略的核心决策逻辑通常写成下面这样。import numpy as np def generate_signal(df, model): pred model.predict(df[feature_cols].values) df[pred] pred # 预测涨幅大于 0 则持仓否则空仓 # 信号仅依赖当日可得特征不引用任何未来信息 df[signal] np.where(df[pred] 0, 1, 0) return df信号生成有两个细节值得注意。第一是阈值选择pred 0 表示明天预测上涨就持仓否则空仓。如果你想过滤掉更多噪声可以把阈值提高到 0.005要求预测涨幅超过 0.5% 才买入代价是会错过一部分小幅上涨。第二是信号只依赖当日特征算出的预测值这一点是回测可信的前提第 4 章的回测代码里还会再做一层滞后对齐。main.py 的运行顺序一般是先 data.py 拿数据再 feature.py 造特征接着 model.py 训练最后 generate_signal 生成信号并交给 backtest.py。建议第一次跑不要直接执行整个 main.py先打开文件看一遍调用顺序再逐个 import 对应函数定位问题会快很多。另外提一下 img 目录里的炒股界面.png它其实是回测完成后用 matplotlib 把净值曲线和买卖点画在 K 线图上的效果不是实时交易的终端界面。这个误会挺常见的先铺垫一下下一章展开回测和可视化。4. 回测框架与风险指标最大回撤怎么算、怎么读4.1 backtest.py 到底做了什么回测是所有量化项目里最不能含糊的一环。backtest.py 做的事情是拿到上一章生成的信号模拟从第一天到最后一天的持仓过程算出每天的账户净值再汇总成关键指标。def run_backtest(df): # 策略收益 昨日信号 * 今日收益率 # 避免用当天信号去成交当天收益那属于未来函数 df[strategy_ret] df[signal].shift(1) * df[ret] # 净值曲线每天累乘 df[net_value] (1 df[strategy_ret]).cumprod() # 基准买入持有 df[buy_hold] (1 df[ret]).cumprod() # 年化收益按 252 个交易日折算 total_ret df[net_value].iloc[-1] - 1 days len(df) annual_ret (1 total_ret) ** (252 / days) - 1 return df, annual_ret这段代码的核心就是 signal.shift(1)。假设你在 5 月 10 日收盘后根据特征算出买入信号这个信号最早只能在 5 月 11 日执行。不做这层 shift直接拿当天信号乘当天收益等于假设你能在信号产生的同一刻成交这在现实中不存在。回测里但凡出现收益高得离谱的曲线第一怀疑目标就是这个。252 是 A 股一年的交易日数量近似值年化收益就是用这个数字折算的。如果你换了市场或者数据里夹着非交易日这个数字要对应调整不然年化收益会失真。4.2 最大回撤和夏普比率风险指标的读法项目里单独放了一张最大回撤.png说明作者把最大回撤当成核心风险指标。最大回撤描述的是从某个净值高点跌到之后的最低点账户最多亏了多少。这个数字直接关系到策略能不能拿得住。def max_drawdown(net_value): # 截至当天为止的历史最高净值 rolling_max net_value.cummax() # 回撤 当前净值相对历史高点的跌幅 drawdown net_value / rolling_max - 1 return drawdown.min()cummax() 计算到当天为止的净值最高点net_value / rolling_max - 1 得到的是一个负数比如 -0.18含义是从高点回撤了 18%。最大回撤越大策略的波动越剧烈——即使最终赚钱中间的过程也可能让你在最低点割肉离场。一个回撤 40% 的策略和一个回撤 10% 的策略即使年化收益相同实际持仓体验完全不一样。做毕业设计时建议把这几个指标都放进报告里指标计算口径怎么读累计收益期末净值 - 1看策略整体赚没赚钱年化收益按 252 交易日折算不同策略横向对比最大回撤净值高点至后续低点的最大跌幅越小说明风险控制越好夏普比率(策略收益 - 无风险利率) / 收益波动率每单位风险换来多少超额收益夏普比率在这个项目里不是重点但答辩时老师很爱问。它的核心思想是看收益的质量年化 20% 但曲线乱蹦和年化 15% 但曲线平滑后者的夏普可能反而更高因为分母里的波动率更小。4.3 回测可视化K 线图、柱状图和资金曲线img 目录下的几张图对应回测输出的不同视图。k_chart.png 是带买卖点的 K 线图柱状图.png 一般是每日收益或回撤分布最大回撤.png 画的是回撤曲线炒股界面.png 则是把净值曲线、收益柱状图、交易信号组合在一个画布里的总览。常见做法是用 matplotlib 的 subplots 一次铺四张子图最上面是 K 线加买卖点标记第二张是净值曲线 vs 基准收益下面两张分别是每日收益柱状图和回撤曲线。如果想省事可以用 mplfinance 专门画 K 线但它对数据列名有要求必须把列改成 Open/High/Low/Close 的标准命名报错时先检查这一层。我一般会在回测输出里固定打印四行数字累计收益、年化收益、最大回撤、交易次数。交易次数特别容易被忽略但极其关键——信号天天翻转说明策略噪声大每次调仓都要付手续费和滑点交易成本能把一个原本盈利的策略拖成亏损。5. 避坑指南环境部署、数据权限与回测里的五个常见翻车点5.1 现象pip install 后 import 直接报 ModuleNotFoundError: No module named tushare原因只装了 tushare 一个包没装全依赖。这个项目的核心依赖是 pandas、scikit-learn、matplotlib、tushare而且新版本 Python 环境里 scikit-learn 的 API 和旧版本有差异缺一环就挂。解决严格按 requirements.txt 安装装完先做一次 import 冒烟测试确认四个核心包都能进来再跑 main.py。pip install -r requirements.txt python -c import tushare as ts; import sklearn; import pandas; import matplotlib; print(ok)这条命令一次验证四个依赖任何一个缺失都会在这一步报出来而不是等 main.py 跑到一半才炸。requirements.txt 本身是对应作者当时的版本环境写的如果你用的是最新版 Python个别包可能需要手动调整版本号但先按清单装是成本最低的起点。5.2 现象pro.daily() 报错抱歉您没有访问该接口的权限原因tushare 的 pro 接口按用户积分分级新注册账号积分不够daily 接口权限没开。解决两个办法。一是换成老接口 ts.get_k_data()它不需要积分适合先把流程跑通二是去 tushare.pro 完善资料、积攒积分等权限开了再切回 pro 接口。建议前期用 get_k_data 跑通整个项目后面做正式报告时再切 pro 接口两者返回的数据列名略有差异切换时记得更新 feature.py 里的字段名。提示不管用哪个接口token 不要硬编码在代码里提交到公开仓库容易泄露我一般放到本地配置文件或环境变量里。5.3 现象回测年化收益 300% 以上曲线美得不像真的原因未来函数而且是最常见的两种。一是信号没做 shift用当天信号成交当天收益二是标签生成时把未来信息卷进了特征。这个项目原版代码结构是没问题的但你自己改动后很容易在这两个位置出岔子。解决从头排查两行代码。backtest.py 里 signal 必须 shift(1)feature.py 里 label 用 shift(-1)。任何看起来要用未来数据的指标都要确认它在当天收盘那一刻是拿不全的。检查方法很简单把信号曲线和 K 线叠在一起看如果买入点总是出现在大涨当天的最低价附近几乎可以断定有未来函数。5.4 现象训练集得分 98%测试集和回测却亏损原因过拟合决策树深度太大、叶子节点太少把历史噪声当成了规律。解决把 max_depth 压到 3-5min_samples_leaf 调到 20-50用交叉验证看稳定性。我的习惯是先把深度压到 3如果回测还能赚钱再逐步放开一旦测试集表现跟着恶化立刻退回上一档参数。决策树这种模型复杂度和泛化能力是直接对着干的不要追求训练集上的高准确率。5.5 现象除权日出现巨大跳空均线被拉断买卖信号频繁翻转原因用未复权数据算均线和收益率。分红送股后股价向下跳空策略误判为大跌触发一堆错误信号。解决取数时用前复权口径。tushare 里可以用 ts.pro_bar(adjqfq) 直接拿前复权数据或者用 pro.adj_factor() 拉复权因子自己折算。关键是整个链路要统一特征、回测、K 线图必须用同一套复权价格混着用就会对不上回测里会出现假信号。提示复权方式必须全局统一中途换口径等于换了数据源前后结果没有任何可比性。6. 从复现到改进换模型、滚动验证与实盘前的检查清单6.1 把决策树换成随机森林或 LightGBM模型替换是这个项目里性价比最高的改进。单棵决策树容易过拟合随机森林通过多棵树投票缓解这个问题LightGBM 在梯度提升框架下效果更强、但参数也更多。from sklearn.ensemble import RandomForestRegressor model RandomForestRegressor(n_estimators200, max_depth5, min_samples_leaf30) model.fit(X_train, y_train)n_estimators 是树的数量200 是个常见起点再大训练变慢但精度提升有限max_depth 和 min_samples_leaf 沿用决策树的那套经验控制复杂度。替换后重点关注训练集和测试集得分的差距差距缩小说明泛化变好如果测试集不升反降问题不在模型要回去查特征和标签。6.2 用滚动窗口替代单次切分单次 8:2 切分只能证明策略在某一小段历史上有用换成滚动回测才能验证稳定性。推荐用 sklearn 的 TimeSeriesSplit。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(X): X_train, X_test X[train_idx], X[test_idx] model.fit(X_train, y_train) # 记录每次测试集的收益和回撤观察稳定性n_splits5 表示切 5 段每段都用前面的样本训练、后面的样本验证。这五次结果如果忽好忽坏说明策略对时间段极其敏感这种策略放到实盘里大概率亏钱。6.3 实盘前的三件事回测通过只是第一步我自己的习惯是再做三件事。第一把手续费和滑点加进回测A 股双边手续费加上滑点按 0.1%-0.2% 估算高频调仓的策略会被这笔成本吃掉一大截。第二看调仓频率一周只交易一两次的策略比每天翻仓的策略抗造得多。第三用小资金跑一段模拟盘验证数据源、信号计算和下单流程在真实环境里没出岔子再考虑放大资金。这个项目作为毕业设计或课程设计的骨架是够用的你完全可以把 6.1 和 6.2 的改进写进论文的实验章节答辩时这就是加分项。说实话我第一次跑通回测看到年化 200% 的成绩时也兴奋过后来把信号 shift 一步加上收益直接掉到 30%那一刻才真正理解了未来函数这四个字的杀伤力。从那以后我每次拿到回测曲线第一件事就是检查信号有没有对齐、标签有没有泄题先排雷再看收益。希望这份避坑经验能帮到你少走点我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表