Python+TuShare实现A股自动选股:从数据获取到策略筛选的完整指南

📅 发布时间:2026/9/8 13:47:30
Python+TuShare实现A股自动选股:从数据获取到策略筛选的完整指南 简介面向量化投资初学者与Python开发者的实战型资源聚焦如何利用TuShare开源财经数据接口构建A股自动选股系统。项目完整覆盖数据获取、清洗处理、指标计算、策略回测等核心环节策略示例涉及移动平均线交叉、RSI、MACD等常用技术指标也包含基于财务报表的基本面分析思路并提供了平台突破、均线回归、ATR波动、乌龟交易、持续上涨等多种买卖信号模块适合想将编程技能应用到金融场景的读者。资源压缩包共25个文件其中17个Python脚本为主要代码涵盖数据抓取、策略编写、工作流调度与回测工具另有Markdown说明、文本配置以及统计图表图片便于理解工程结构和策略表现整体仅1.95MB轻量易部署。已有5963人学习下载代码模块划分清晰、可直接运行调试能帮助读者快速搭起自己的量化选股框架。 打开TuShare的文档页面之前我一直觉得“自动选股”是很玄的事情直到我用Python把它拆成了“拉数据—算指标—做过滤—出结果”四步事情一下就变得极其朴素。这篇文章就是想把我这套基于TuShare的A股自动选股程序完整摊开讲一遍整体怎么设计、数据怎么接、规则怎么落成代码、实际跑起来会踩哪些坑。适合已经会一点Python、想把自己那套选股逻辑“代码化”的朋友也适合刚开始接触量化、但不想一上来就碰重型框架的人。1. 整体设计思路与方案选型1.1 先想清楚选股不等于交易我见过不少人一提到量化就想着全自动买卖这是最大的误区。我这套程序只解决“选股池怎么来”这件事也就是每天收盘后自动跑一遍全市场数据筛出符合规则的股票名单。最后的买点、仓位、下单全部人工确认。原因很简单A股有涨跌停、T1、停复牌、ST摘帽等各种规则实盘自动交易还涉及券商接口、风控、滑点处理复杂度是指数级上升。做自动选股则安全得多——它是“人做决策机器做数据整理”就算代码有bug最坏的结果就是名单不准不会造成资金损失。1.2 为什么是TuShare而不是别的数据源市面上的A股数据源我基本都试过简单说下我的对比感受数据源优点缺点我的评价TuShare Pro字段全行情/财务/每日指标都有社区成熟部分接口有积分门槛选股场景最均衡首选AkShare免费、接口非常多文档跳跃部分接口依赖爬虫稳定性一般适合做研究不适合做定时任务Baostock免费、行情稳定财务指标少更新偏基础只做行情回测可以第三方Excel插件/软件省事、可视化黑盒、不可定制、关键逻辑看不到不满足“固化成代码”的需求TuShare Pro的核心接口正好覆盖选股所需stock_basic拿股票列表daily和pro_bar拿行情daily_basic拿每日估值数据fina_indicator拿财务指标。这些都是程序化筛选的“原材”。虽然有积分限制但如果你只是做基础选股注册后默认的积分额度基本够用后面我会专门讲积分不够怎么办。1.3 选股逻辑怎么设计一套选股程序最关键的其实不是代码是规则。我把规则分成三层基本面定范围估值不能太贵PE、PB盈利能力要过得去ROE市值要适中太小有退市风险太大涨不动。技术面找时机均线多头排列、不能下跌趋势、成交量不能太低。风险面做排除剔除ST股、剔除上市不满一年的次新股、剔除当天一字涨停买不进去的票。设计逻辑的时候要记住一个原则规则宁少勿多。规则越多筛完往往一只不剩反而没法用。我一开始加了十几条过滤条件回测三天全是空仓后来砍到六条核心规则效果才正常。1.4 程序模块怎么划分我习惯按数据流分层每个模块只干一件事数据获取层从TuShare拉取原始数据做本地缓存。数据清洗层处理缺失值、复权、日期对齐。指标计算层计算均线、PE、ROE等技术面和基本面因子。策略筛选层把选股规则固化成多个过滤函数。输出层把符合条件的股票写入CSV或Excel方便第二天直接看。分层的好处是以后想改逻辑只动筛选层不用碰数据层。这个程序我后来迭代了四五个版本每次都是这种结构省下来的时间。2. 环境准备与TuShare接入2.1 Python环境与依赖我用的是Python 3.10理论上3.8以上都能跑。建议用虚拟环境别把库装到系统Python里不然哪天系统Python被别的项目搞坏你的选股程序也跟着遭殃。python -m venv stockenv source stockenv/bin/activate # Windows下执行 stockenv\Scripts\activate pip install pandas numpy tushareTushare要装最新版老版本很多Pro接口不兼容pip install --upgrade tushare2.2 注册TuShare Pro与Token配置打开TuShare官网注册账号然后在“个人主页”里找到“接口TOKEN”复制那一串token字符串。程序里通过ts.pro_api(token)完成身份认证。有一个新手特别容易犯的错把token直接硬编码在代码里。一旦你把代码传到GitHub或者发给别人token就泄露了。哪怕只是自己本地跑我也建议放在单独的配置文件里# config.py TUSHARE_TOKEN 你的token放这里这个文件不要提交到Gitimport config import tushare as ts pro ts.pro_api(config.TUSHARE_TOKEN)初始化之后后续所有数据接口都通过pro这个对象调用。2.3 数据本地缓存设计TuShare的行情接口有频率限制每天调用次数也有限。如果每次跑选股都现拉几年历史行情接口分分钟被限流。我的办法是拉下来的日线数据全部存本地CSV下次启动时先读本地文件只在本地数据缺失时才去请求。import os import pandas as pd def get_daily_from_cache(ts_code, start_date, end_date): cache_dir data/daily os.makedirs(cache_dir, exist_okTrue) cache_file os.path.join(cache_dir, f{ts_code}.csv) if os.path.exists(cache_file): df pd.read_csv(cache_file, dtype{ts_code: str}) df[trade_date] df[trade_date].astype(str) # 缺哪段补哪段增量的概念 need_start df[trade_date].max() if len(df) else start_date if need_start end_date: return df[(df[trade_date] start_date) (df[trade_date] end_date)] new_df ts.pro_bar(ts_codets_code, adjqfq, start_dateneed_start, end_dateend_date) if new_df is not None and not new_df.empty: df pd.concat([df, new_df.drop_duplicates(subset[trade_date])], ignore_indexTrue) df df.drop_duplicates(subset[trade_date]).sort_values(trade_date) df.to_csv(cache_file, indexFalse) return df[(df[trade_date] start_date) (df[trade_date] end_date)] else: df ts.pro_bar(ts_codets_code, adjqfq, start_datestart_date, end_dateend_date) if df is not None and not df.empty: df.to_csv(cache_file, indexFalse) return df这个函数后来成了整个程序的地基没有它选股跑一遍要十分钟有了它基本十几秒出结果。3. 核心代码实现3.1 股票池构建拿到全市场A股列表第一步是获取所有正常上市的A股用stock_basic接口def get_stock_pool(): df pro.stock_basic(exchange, list_statusL, fieldsts_code,name,industry,market,list_date) # 剔除ST、退市整理、上市不满一年 df df[~df[name].str.contains(ST, naFalse)] df df[df[market].isin([主板, 创业板])] # 按自己需求保留板块 df[list_date] pd.to_datetime(df[list_date]) df df[df[list_date] (pd.Timestamp.now() - pd.Timedelta(days365))] return df我在这里剔除ST股用的是名称过滤简单粗暴但有效。板块我保留了主板和创业板科创板当时没纳入是因为我自己的交易习惯你可以改成[主板, 创业板, 科创板]。3.2 行情获取复权是必须处理的坑A股动不动分红送股不复权的价格K线会有跳空。做量化筛选时算均线、涨幅一定要用前复权数据不然均线形态完全失真。def get_kline(ts_code, start_date, end_date): # 前复权 df ts.pro_bar(ts_codets_code, adjqfq, start_datestart_date, end_dateend_date) if df is None or df.empty: return pd.DataFrame() df df.sort_values(trade_date).reset_index(dropTrue) return df这里有个小技巧pro_bar是TuShare提供的复合接口比直接用pro.daily加pro.adj_factor方便很多一次就能拿到复权后的行情。3.3 基本面指标用每日估值接口还是财务报告接口基本面过滤我用了两个接口daily_basic每天更新的估值数据包含pe_ttm滚动市盈率、pb市净率、total_mv总市值单位万元。fina_indicator财务指标我主要取roe。关键是这个财务指标用ann_date公告日期对齐不要用end_date报告期对齐。因为报告期是“这个数据描述的是哪个时间段”但数据真正公布出来要晚一两个月。如果用报告期对齐做选股等于用了未来数据——这就是量化里常说的“未来函数”。def get_fundamental(ts_code, trade_date): # 取trade_date之前最近一期公告的财务指标 fin pro.fina_indicator(ts_codets_code, start_date20200101, end_datetrade_date, fieldsts_code,ann_date,roe) if fin is None or fin.empty: return None fin fin[fin[ann_date] trade_date].sort_values(ann_date) return fin.iloc[-1] if not fin.empty else None3.4 技术面过滤均线多头排列我的核心技术条件很简单收盘价在20日均线之上且5日、10日、20日均线呈现多头排列。这个条件表达的是“中期趋势向上短期动能还在”。def calculate_ma(df): df df.copy() df[ma5] df[close].rolling(5).mean() df[ma10] df[close].rolling(10).mean() df[ma20] df[close].rolling(20).mean() return df def check_trend(df): if len(df) 25: return False last df.iloc[-1] return last[close] last[ma20] and last[ma5] last[ma10] last[ma20]均线算完之后注意把最新一天的数据取出来判断不要用整段数据做bool判断否则容易踩到pandas对齐的坑。3.5 主流程整合跑一遍全市场把上面的函数串起来核心选股流程如下def run_select(trade_date): pool get_stock_pool() results [] for idx, row in pool.iterrows(): ts_code row[ts_code] try: df get_daily_from_cache(ts_code, 20240101, trade_date) if df.empty or len(df) 60: continue # 技术面过滤 df_ma calculate_ma(df) if not check_trend(df_ma): continue # 基本面过滤 daily_basic pro.daily_basic(ts_codets_code, trade_datetrade_date, fieldsts_code,pe_ttm,pb,total_mv,turnover_rate) if daily_basic is None or daily_basic.empty: continue pe daily_basic.iloc[0][pe_ttm] pb daily_basic.iloc[0][pb] mv daily_basic.iloc[0][total_mv] # 单位万元 turnover daily_basic.iloc[0][turnover_rate] if not (0 pe 40 and mv 500000 and mv 5000000 and turnover 1): continue # 财务指标过滤 fin get_fundamental(ts_code, trade_date) if fin is None or fin[roe] 10: continue results.append({ ts_code: ts_code, name: row[name], pe_ttm: pe, pb: pb, roe: fin[roe], total_mv: mv / 10000, # 转成亿 turnover_rate: turnover }) except Exception as e: # 单只股票出错不中断整个程序 print(f{ts_code} error: {e}) continue res_df pd.DataFrame(results).sort_values(roe, ascendingFalse) res_df.to_csv(selected_stocks.csv, indexFalse, encodingutf-8-sig) return res_df这个主流程有几个值得说的地方单只股票出错用try...except包住继续下一只。全市场五千多只股票偶尔一只数据异常很正常不能让一只票搞挂整个任务。估值和市值条件写在同一行利用Python短路求值减少多余调用。最后按ROE降序排序因为ROE代表了盈利能力选股池内部的排序也要有意义。3.6 定期运行的实现思路我不想每天手动跑一遍所以加了个定时触发。Windows下用“任务计划程序”Linux/Mac下用cron本质都是每天收盘后比如15:30执行一次python main.py。# crontab示例每个交易日15:30运行 30 15 * * 1-5 cd /path/to/stock-selector python main.py不过要注意交易日和自然日不是一回事节假日A股不开盘。简单处理是让程序自己判断当天是不是交易日TuShare有trade_cal接口def is_trade_date(date_str): cal pro.trade_cal(exchangeSSE, start_datedate_str, end_datedate_str, fieldscal_date,is_open) return cal.iloc[0][is_open] 14. 常见问题与排查技巧实录4.1 积分不足最常见的拦路虎TuShare Pro的接口有积分门槛注册后你默认有一定积分但一些接口需要更高积分才能调。实际跑的时候最常见的报错是抱歉您没有访问该接口的权限。我的应对思路分三步优先用低积分接口stock_basic、daily、trade_cal这些基础接口积分要求低优先保障。积分不够时换替代接口比如拿不到fina_indicator的ROE可以从daily_basic先用pe_ttm、pb做简易版基本面过滤。攒积分TuShare的积分可以通过完善资料、每日签到等方式累积等积分够了再逐步解锁更多字段。说白了积分体系本质上是限制高频调用选股是低频任务只要合理设计缓存和请求次数低积分档也能跑得很舒服。4.2 拉回来的数据是空的先查这三个地方我调试阶段遇到最多的问题就是接口返回空数据排查顺序永远是股票代码的交易所后缀是否正确平安银行是000001.SZ浦发银行是600000.SH后缀写错基本拉不到数据。交易日和参数范围是否正确比如trade_date传了非交易日接口自然返回空。是否停牌停牌期间没有行情数据合理。4.3 未来函数和数据对齐这个坑我在3.3已经重点提过再补充一个场景你在跑历史回测或者历史某一天的选股时一定要用ann_date对齐财务数据而不是end_date。我之前做回测时直接取了最新一期财报的ROE去筛三个月前的股票结果回测曲线漂亮得不行后来才发现这是典型的未来函数真实盘根本不可能赚到这个钱。4.4 循环慢到怀疑人生批量请求优于单只请求最开始的版本我是在循环里一只一只调日线接口5000只股票跑下来加上网络延迟四十分钟都跑不完。后来优化成两个方向按交易日批量拉全市场行情一次请求拿到几千只股票的数据。本地缓存做增量更新只在首次运行或者数据缺失时发请求。优化之后选股时间从四十分钟缩短到二十秒以内这个提升非常明显。4.5 选出来的票到底能不能买实盘验证下来我想强调一点程序选出来的是一篮子“符合规则的候选股”不是“马上就能买入的信号”。原因有几点技术面过滤使用的是收盘后数据第二天开盘很可能直接高开买点完全不同。基本面数据天然滞后财报是季度快照不能反映最新变化。单日选股结果受大盘情绪影响很大极端行情下选出的票可能会集体失真。所以我的习惯是程序选出候选池后再人工叠加一个简单的“等回调再进”的判断。自动化的价值在于帮你把几千只股票压缩成十几只把精力花在真正值得看的目标上。5. 实操经验与后续扩展方向5.1 代码组织的三个小建议第一所有接口请求封装成独立函数加缓存逻辑时只要改一处。第二选股规则写成配置比如PE区间、市值区间、ROE阈值都放进config.py改参数不需要动逻辑代码。第三输出结果带更新时间戳不然第二天看到昨天的名单容易误判。5.2 后续可以扩展的方向这套程序的架构留了很好的扩展空间。比如我在考虑加一个简单的回测模块用历史数据模拟“如果按这个选股规则每天换仓一年收益如何”。逻辑上就是对历史每一天都跑一次选股、计算组合收益难点在于历史财务数据的对齐和交易成本的估算。另外很多人会把选股结果推送到微信或者钉钉做法也不复杂在主流程结束的地方加一个requests.post把结果文本发到webhook地址。我目前是用CSV文件落地因为看表格更直观推送到手机适合出差时应急看一眼。5.3 一个个人心得我这套程序跑了大半年最大的体会不是代码本身而是选股程序让我的决策从“凭感觉”变成了“有依据”。以前复盘一支股票为什么买只能说自己“当时觉得会涨”。现在不一样了我可以明确说出当时满足哪几条规则、数据支撑是什么。即便最终结果不理想复盘也有迹可循。另外参数千万不要过度优化。我一开始把PE区间调到20到25之间回测结果特别好看但实盘一跑这样极端的区间往往筛不出几只票样本量太小统计意义不大。后来我把区间放宽到0到40搭配ROE和技术面条件选股池的稳定性和实用性反而更好。自动选股的目标不是找到“最完美的参数”而是把纪律执行下去让交易体系保持稳定。本文还有配套的精品资源点击获取