☰
TuShare A股自动选股程序:从数据拉取到信号落地的完整拆解
2026/10/17 22:04:22 网站建设 项目流程

简介:这是一份面向量化投资初学者与金融数据分析爱好者的Python实战项目,围绕TuShare财经数据接口构建A股自动选股系统,帮助读者打通数据获取、清洗、策略制定到回测评估的完整链路。资源包共25个文件,以17个py脚本为核心,涵盖数据抓取、策略实现与回测模块,另含requirements.txt依赖清单、README说明文档及少量jpg、png图表,压缩包约1.95MB,结构清晰便于按模块研读。项目内置多种选股思路,如移动平均线交叉、RSI、MACD等技术指标,以及突破平台、低ATR、海龟交易、持续增长等策略脚本,并配有回测与退出逻辑,可直观观察策略在不同市场环境下的表现。目前已有6025人学习下载,适合希望将Python编程与金融知识结合、动手搭建个人量化选股工具的开发者参考实践。

1. 用 TuShare 搭一套 A 股自动选股程序:从数据拉取到信号落地的完整拆解

很多人第一次听到「自动选股」,脑子里浮现的是满屏 K 线加神秘指标,其实落到代码层面,它就是把「拉数据 → 算因子 → 过滤 → 输出候选池」这条链路用 Python 串起来。这份基于 TuShare 的 A 股自动选股程序,核心价值就在于把这条链路做成了一个能跑、能改、能复现的工程骨架,而不是一段只能看不能用的演示脚本。它适合两类人:一类是刚接触量化、想搞明白选股程序到底怎么搭的开发者;另一类是手里已经有交易逻辑,但缺一套稳定数据管道和筛选框架的从业者。下面我按自己拆包复现的顺序,把这份资源讲透。

2. TuShare 数据接口与选股链路:先搞清楚数据从哪来、因子怎么算

2.1 为什么选 TuShare 而不是自己爬行情

做 A 股选股,第一道坎不是策略,是数据。自己爬行情页面,短期能跑,长期必然翻车:字段结构一变、请求频率一高,脚本就废了。TuShare 这类标准化数据接口的价值,是把「日线行情、复权因子、财务指标、股票列表」这些基础数据用统一格式吐出来,你只需要关心怎么用,不用天天修解析器。

这份程序选 TuShare 作为数据源,逻辑上是合理的。它覆盖了选股最常用的几类数据:日线行情(开高低收、成交量额)、复权信息、基本面指标(市盈率、市净率、市值等)、以及股票基础列表。对一套自动选股程序来说,这四类数据基本够搭出一个多因子筛选框架。

需要提前说清楚一个边界:TuShare 的接口有积分门槛,不同接口对积分要求不一样。常见做法是先用免费额度能拿到的接口把流程跑通,比如日线行情和股票列表,等策略验证有效再考虑更高频或更细的财务数据。我一般会建议先把「能拿到什么数据」列成一张表,再决定因子怎么设计,而不是先想一堆指标最后发现数据拿不到。

数据类别典型用途更新频率注意点
股票列表确定选股池范围低频注意退市、ST 标记
日线行情价格、成交量因子每日需处理停牌与复权
复权因子计算真实涨跌幅每日不复权会算出假信号
基本面指标估值、市值筛选季度/每日注意财报披露时点

2.2 选股链路的四个环节

一套自动选股程序,拆开看就是四步:数据获取、因子计算、条件过滤、结果输出。这份资源的骨架也是按这个顺序组织的,我复现时基本没改结构,只替换了因子部分。

第一步数据获取,负责把股票列表和行情拉下来,落到本地做缓存。第二步因子计算,把原始行情转成可比较的指标,比如 N 日涨幅、换手率、均线偏离度。第三步条件过滤,用阈值或排序把候选池缩小。第四步输出,把结果写成 CSV 或打印出来,方便人工复核。

import tushare as ts import pandas as pd # 初始化接口,token 需替换为自己的 pro = ts.pro_api("your_token_here") # 拉取股票基础列表,fields 指定需要的字段,减少无效传输 stock_basic = pro.stock_basic( exchange="", list_status="L", # L 上市 D 退市 P 暂停 fields="ts_code,symbol,name,area,industry,list_date" ) # 拉取某交易日全部日线行情 daily = pro.daily(trade_date="20240102") # 合并基础信息与行情,得到带行业标签的行情表 merged = pd.merge(daily, stock_basic, on="ts_code", how="left") print(merged[["ts_code", "name", "industry", "close", "pct_chg"]].head())

这段代码的逻辑很直白:先拿股票池,再拿当日行情,最后按ts_code合并。参数上,list_status="L"只保留正常上市股票,避免把退市股混进选股池;fields显式指定字段,能减少返回体积,也避免接口字段变动时整段代码崩掉。trade_date用YYYYMMDD字符串格式,这是 TuShare 的约定,写成日期对象会报错,这点新手很容易踩。

2.3 因子计算:把行情转成能排序的数字

拿到行情只是原料,选股真正依赖的是因子。这份程序里常见的因子包括动量类(近 N 日涨幅)、流动性类(换手率、成交额)、估值类(市盈率、市净率)。计算时最关键的一点是复权,不复权的价格在除权除息日会出现跳空,直接算涨幅会得到假信号。

# 计算 20 日动量因子:当前收盘价相对 20 日前收盘价的涨幅 def momentum_factor(df, window=20): df = df.sort_values("trade_date") df["mom20"] = df["close"].pct_change(window) return df # 按股票分组计算,避免不同股票之间串行 daily_grouped = daily.sort_values(["ts_code", "trade_date"]).groupby("ts_code") daily_grouped = daily_grouped.apply(lambda x: momentum_factor(x)).reset_index(drop=True) # 过滤掉动量因子为空(上市不足 20 日)的股票 candidates = daily_grouped.dropna(subset=["mom20"])

这里pct_change(window)算的是相对 window 期前的涨幅,groupby("ts_code")保证每只股票单独计算,不会把 A 股票的价格和 B 股票混在一起。dropna那一步是必须的,新股上市不足 20 日时因子为空,不处理会在排序时出问题。参数window我一般设 20 或 60,对应一个月和一个季度的动量,具体取值要看策略周期,没有万能值。

3. 从脚本到可复用程序:模块拆分与批量选股落地

3.1 把单文件脚本拆成三层结构

原始脚本能跑,但所有逻辑堆在一个文件里,改一处容易牵动全身。我复现时做的第一件事是拆成三层:数据层、因子层、策略层。数据层只管拉数和缓存,因子层只管把行情转成指标,策略层只管按条件筛。这样换数据源只动数据层,换因子只动因子层,互不干扰。

# data_layer.py import tushare as ts import pandas as pd import os CACHE_DIR = "./cache" def get_daily(trade_date, token): """带本地缓存的日线获取,避免重复请求""" cache_file = os.path.join(CACHE_DIR, f"daily_{trade_date}.csv") if os.path.exists(cache_file): return pd.read_csv(cache_file) pro = ts.pro_api(token) df = pro.daily(trade_date=trade_date) os.makedirs(CACHE_DIR, exist_ok=True) df.to_csv(cache_file, index=False) return df

缓存这一步看着简单,实际很关键。TuShare 接口有调用频率限制,反复拉同一天数据既浪费额度又慢。用trade_date做文件名缓存,第二次直接读本地,速度差一个数量级。参数上,CACHE_DIR建议放在项目目录下,别放临时目录,否则重启就没了。

3.2 批量选股:一次跑多天、多因子

单日选股只能看一天结果,实际用的时候往往要回看一段时间,验证策略稳定性。批量选股的做法是把交易日列表循环起来,每天跑一遍筛选,最后汇总。

# strategy_layer.py import pandas as pd def select_stocks(daily_df, top_n=20): """按动量因子排序,取前 top_n 只""" df = daily_df.dropna(subset=["mom20"]) # 先按动量降序,再按成交额降序,避免同动量时无法区分 df = df.sort_values(["mom20", "amount"], ascending=[False, False]) return df.head(top_n)[["ts_code", "name", "industry", "close", "mom20"]] def run_backtest(trade_dates, token): """批量跑多个交易日,汇总每日候选池""" results = {} for date in trade_dates: daily = get_daily(date, token) # 这里需要先补算因子,实际项目里因子计算单独一层 picked = select_stocks(daily) results[date] = picked return results

select_stocks里用mom20主排序、amount次排序,是为了处理动量相同的情况,否则排序结果不稳定,每次跑出来顺序可能不一样。top_n控制候选池大小,设太小容易漏掉机会,设太大失去筛选意义,我一般设 20 到 50 之间。批量跑的时候要注意,因子计算需要历史数据,单日行情算不出 20 日动量,所以真实项目里因子层要单独维护一个滚动窗口,不能只拿当天数据。

3.3 输出与复核:别让程序替你拍板

程序输出的是候选池,不是买入指令。我习惯把结果写成 CSV,带上股票代码、名称、行业、因子值,然后人工过一遍。有些信号明显是数据问题导致的,比如某只股票因为停牌复牌出现异常涨幅,程序会把它排到前面,但人工一眼就能排除。

# 输出候选池,带日期后缀方便归档 def save_result(picked, date): out_file = f"./output/picked_{date}.csv" picked.to_csv(out_file, index=False, encoding="utf-8-sig") print(f"{date} 候选池已保存,共 {len(picked)} 只")

encoding="utf-8-sig"是为了 Excel 打开不乱码,这个细节很多人忽略,结果中文股票名全是问号。输出目录建议单独建,别和缓存混在一起,方便清理。

4. 避坑与排查:这套选股程序最容易翻车的五个地方

4.1 现象:选出来的股票全是同一行业

原因通常是因子单一,只用了动量或涨幅,而某个行业当天整体大涨,导致候选池被一个行业包揽。解决方式是在筛选条件里加行业分散约束,比如每个行业最多取 3 只,或者引入行业中性化处理,把行业涨幅从个股因子里剔除。

4.2 现象:回测结果好得离谱

原因多半是用了未来数据。比如用当天收盘价算因子,又用当天收盘价买入,等于偷看了未来。正确做法是 T 日收盘后算因子,T+1 日开盘或收盘执行。检查方法是把因子计算里的数据截断到 T-1 日,看结果是否大幅回落,如果回落明显,说明之前用了未来数据。

4.3 现象:接口报错「抱歉,您没有访问该接口的权限」

原因是 TuShare 部分接口需要积分,免费额度不够。解决方式是先确认当前积分能访问哪些接口,把程序里用到的接口列出来逐个测试。常见做法是先用日线行情和股票列表把流程跑通,财务类接口等积分够了再接入,不要一上来就依赖高门槛接口。

4.4 现象:停牌股票混进候选池

原因是停牌期间行情数据缺失或为 0,因子计算出异常值。解决方式是在筛选前过滤掉成交额为 0 或换手率为 0 的股票,同时检查股票列表里的上市状态。停牌股复牌后往往有补涨补跌,程序如果不排除,会把这类噪声当成信号。

4.5 现象:每天跑出来的结果顺序不一样

原因是排序字段有并列值,Pandas 默认排序不稳定。解决方式是加次级排序键,比如成交额或股票代码,保证同样条件下输出顺序一致。这个坑不影响选股逻辑,但会影响你对比不同日期的结果,排查起来很费时间。

提示:以上五个问题里,未来数据是最隐蔽也最致命的,建议每次改完因子都做一次「截断测试」,确认没有用到当天之后的数据。

5. 进阶技巧:用滚动窗口和因子分层把选股程序做稳

程序能跑之后,下一步是让它稳。我自己的习惯是加两个东西:滚动窗口和因子分层。滚动窗口解决的是「因子需要历史数据」的问题,不能只拿当天行情算。做法是维护一个最近 N 个交易日的行情缓存,每天增量更新,算因子时从缓存里取窗口数据。

# 维护滚动窗口,增量更新最近 60 个交易日行情 def update_window(trade_date, token, window=60): """拉取当日行情并追加到窗口缓存,保留最近 window 天""" daily = get_daily(trade_date, token) window_file = "./cache/window.csv" if os.path.exists(window_file): old = pd.read_csv(window_file) combined = pd.concat([old, daily], ignore_index=True) else: combined = daily # 按交易日去重后保留最近 window 个交易日 dates = sorted(combined["trade_date"].unique())[-window:] combined = combined[combined["trade_date"].isin(dates)] combined.to_csv(window_file, index=False) return combined

这段逻辑的关键是「按交易日去重后截断」,避免重复追加导致窗口膨胀。window=60对应约三个月,够算常见的动量因子。参数上,窗口大小要大于你最大的因子周期,比如用 60 日动量,窗口至少 60 天,建议留 20% 余量。

因子分层是另一个提稳手段。把股票按因子值分成若干层,观察每层的后续表现,比只看 Top N 更能判断因子是否有效。常见做法是分 5 层,看最高层和最低层的收益差,如果差距不明显,说明这个因子在当前市场环境下区分度不够,需要换因子或调参数。

分层因子值范围用途
第 1 层最低 20%对照组,观察反向表现
第 3 层中间 20%基准,判断因子单调性
第 5 层最高 20%候选池来源

从那以后我每次改因子,都强制走一遍「截断测试 + 分层观察」,确认没有未来数据、因子单调性正常,才敢把结果拿去用。希望帮到你。

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

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

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

立即咨询