Python+TuShare实战:构建A股自动选股与回测系统
2026/9/8 13:47:20 网站建设 项目流程

简介:这是一份基于TuShare金融数据接口的A股自动选股实战项目,面向量化投资初学者与Python开发者,帮助解决从数据获取、清洗到策略制定与回测的完整流程。项目压缩包共25个文件,以17个Python脚本为核心,涵盖主程序、数据处理、策略判断等模块,另附2张策略效果图、2张流程图、1份README说明和1份依赖清单,整体仅1.95MB,结构轻量清晰。重点模块包括数据获取器、工作流控制、突破平台、回踩年线、龟式交易等经典策略实现,能直观学习如何调用TuShare获取行情与财务数据,并应用RSI、MACD等指标构建买卖信号。资源已有5963人学习,适合希望将Python与金融数据结合、快速搭建量化选股原型的开发者参考借鉴。

1. 项目概述:为什么我需要一个自动选股程序

我接触A股交易也有六七年了,每天开盘前手动翻F10、刷板块涨幅榜、对着Excel挨个筛选,说实话,这套流程撑到现在纯粹靠习惯撑着。更早的时候我用过一些现成的选股软件,界面好看是好看,但策略逻辑写死在里面,你想加一个"剔除上市不满一年次新股"的条件,对不起,付费版本才支持。而且那类软件的数据更新有时候跟不上盘中节奏,等它给出信号,好的买点早就过了。

后来我接触了Python,开始研究用代码来解决选股这个事。先说结论:用Python + TuShare做A股自动选股,不仅可行,而且已经成为我日常复盘的核心工具之一。

TuShare是国内一个开源的金融数据接口库,数据覆盖沪深京A股、港股、ETF、期货、宏观经济指标等,对接的是TuShare Pro版的接口,需要注册一个账号拿token。注册免费,但接口调用有积分门槛,比如日线行情需要120分以上,基础的分值通过注册和完善资料就能拿到,之后靠每日签到慢慢攒。这个设计倒是合理,防止有人一口气把服务器拖垮。

那这个选股程序能解决什么问题?三个最实际的价值:

  • 把"复盘"这件事标准化。每天收盘后跑一遍程序,自动拉出当日涨停、跌停、换手率异动、主力资金流向的股票清单,不用再守着行情软件来回翻。
  • 把"选股条件"变成代码。均线多头排列、MACD金叉、放量突破等等,在TuShare的数据接口上就是几行DataFrame筛选,后续增加条件也方便。
  • 把"情绪"从决策中剥离。程序输出的是一张结构化表格,该买该卖自己再结合盘面判断,至少不会被盘中的分时波动带着走。

这篇文章的目标读者,我默认是"会用Python基础语法、但不一定写过量化代码"的开发者或者股民朋友。下面我会把从环境准备、数据获取到策略实现的完整路径记录下来,所有代码我都用Python 3.9 + pandas 2.0 + TuShare 1.2.89跑通过,你可以直接复制下来改改参数就能用。

2. 开发环境与TuShare接入:第一步是解决数据从哪来

2.1 环境搭建的三个注意点

在动手之前,先说两个我踩过的坑。

第一个坑是Python版本。TuShare当前版本对Python 3.11以下支持比较稳,Python 3.12以上我没实测过,不过建议用3.9或3.10,避免版本兼容性问题。安装Python的时候就记住一点:勾选"Add Python to PATH",不然后面命令行执行pip install会提示找不到Python。这个选项在Windows安装器的第一步最底下,看着不起眼但非常重要。

第二个坑是用虚拟环境,不要一股脑把包装到全局环境。我之前直接在全局环境里装了一堆数据处理库,结果某个项目需要pandas 1.5、另一个需要pandas 2.0,互相冲突,特别头疼。后来老实了,每个项目单独建一个.venv虚拟环境,多花两分钟,省下的是数不清的兼容性麻烦。

验证环境是否装好,在终端执行python --version,能看到版本号就说明Python安装成功。接着装依赖库:

pip install tushare pandas numpy

这三个足以覆盖本项目的全部需求。如果你手头没有pandas基础,也别慌,下面用到的地方我会解说得细一些。

2.2 TuShare接口权限与token配置

TuShare的接口需要token鉴权,步骤很简单:

  1. 去TuShare官网注册账号。
  2. 登录后进入个人主页,复制你的token字符串。
  3. 代码里初始化:
import tushare as ts ts.set_token('你的token字符串') pro = ts.pro_api()

token这东西相当于你访问服务器的钥匙,别提交到Git仓库。我一般放在项目根目录的.env文件里,写代码时通过dotenv加载,或者退一步直接放在一个不进版本管理的config.py文件里,格式如下:

TUSHARE_TOKEN = '你的token'

方法虽然土,但对个人项目来说足够实用。

有一点需要提醒:TuShare Pro的接口有积分限制。日线行情接口daily需要120分基础积分,实时行情快照ts.realtime_quote需要2000分以上,有些高级接口甚至需要5000分。积分不够也不用着急,注册完善资料能拿100分左右,每日签到稳定加一点,用着用着就够了。如果刚开始积分不够,可以用pro.daily之前的老接口ts.get_k_data()过渡,那个免费,但数据稳定性一般,会有个别缺失值。

3. 选股策略设计思路:先讲清楚逻辑,再开始写代码

我见过不少朋友一上来就调库、拉数据,拉完看着表格发呆,因为根本不知道下一步要算什么。这就是典型的"缺少策略设计"。

选股策略的核心是先定义你的选股逻辑,代码只是把逻辑翻译成机器能执行的语言。我给大家拆解一个我自己在用的中短线策略,逻辑很简单,但足够说明整套流程:

  • 第一步:剔除ST股和上市不满一年的次新股原因不用多说,ST股有退市风险,波动不确定性大;次新股上市初期筹码不稳定,容易被资金操纵。
  • 第二步:筛选出均线多头排列的股票,即MA5 > MA10 > MA20A股散户多,均线系统是大量技术派人士的参考基准,形成多头排列说明市场整体持仓成本上移,短期趋势偏强。
  • 第三步:叠加放量条件,当日成交量是5日均量的1.5倍以上放量是资金进场的信号,没量的上涨很大概率是一日游行情。
  • 第四步:剔除当日涨停的股票这个属于我的纪律要求。涨停买入次日溢价不确定,补跌风险大,我倾向于不参与。
  • 第五步:按换手率从大到小排序,取前20只输出

这个策略不求抓到翻倍妖股,主打一个"趋势还在、量能配合、不过分追高",配合大盘环境使用,胜率还算稳定。如果你偏好别的手法,比如低市盈率、高股息率的价值风格,把数据字段换一换,筛选条件改一改就行,代码骨架完全不用动。

4. 代码实现:从数据拉取到策略筛选的完整流程

4.1 用交易日历确认"当天是否交易日"

开盘前跑一遍程序,最怕撞上非交易日。A股有节假日调休,光靠datetime.today()判断星期几不靠谱。我是先拉交易日历,再在程序启动时检查今天是否是交易日,省得在节假日跑出个空结果还傻等半天:

import datetime import tushare as ts import pandas as pd ts.set_token('你的token') pro = ts.pro_api() today = datetime.date.today().strftime('%Y%m%d') # 获取2020年至今的交易日历 cal = pro.trade_cal(start_date='20200101', end_date=today) # is_open=1表示交易日 if cal[cal['cal_date'] == today]['is_open'].values[0] == 0: print('今天非交易日,程序退出') exit()

这一步花不了多少积分,建议每次跑数据前都先做一次,养成习惯。

4.2 拉取全市场股票列表

A股市场有5000多只股票,我们先把全部股票的基本信息拉下来备用:

# 获取全部A股股票列表 stock_list = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,area,industry,list_date')

list_status='L'表示上市状态为"上市",这样退市的已经自动过滤了。exchange留空代表沪深京几大交易所的所有股票都拉。注意这里返回的字段包含list_date,后面筛次新股用得着。

stock_basic这个接口单次最多返回5000条记录,现在A股数量接近5000-6000只,如果你拉到的数据量明显偏少,说明接口权限或者参数设置可能有问题,可以加个分页参数limit=6000看看效果。我实测下来这个接口当前最新版本是能返回全量数据的,如果后续股票数量增加,官方应该会调整限制。

4.3 批量获取日线行情并计算均线指标

接下来是核心环节:循环每一只股票,拉取最近120个交易日的日线行情,然后算均线。为了避免请求频率过快被限流,我加了一个time.sleep(0.5)让每次请求间隔0.5秒,跑5000只股票大概需要40多分钟。不想等那么久的话,有两个优化方案:

  1. 只对前一天成交额排名前500的股票做策略筛选,绝大多数符合策略条件的强势股都在这个池子里。
  2. 把日线行情缓存到本地CSV文件,当天内重复运行不需要再次请求。
import time def cal_ma(pre_close, close): """计算5/10/20日均线并判断多头排列""" ma5 = pre_close.rolling(5).mean() ma10 = pre_close.rolling(10).mean() ma20 = pre_close.rolling(20).mean() # 返回最近一天的均线值 if ma5.iloc[-1] > ma10.iloc[-1] > ma20.iloc[-1]: return ma5.iloc[-1], ma10.iloc[-1], ma20.iloc[-1], True return ma5.iloc[-1], ma10.iloc[-1], ma20.iloc[-1], False result_list = [] # 为减少请求次数,先筛选出上市满一年的股票 one_year_ago = (datetime.date.today() - datetime.timedelta(days=365)).strftime('%Y%m%d') valid_stocks = stock_list[stock_list['list_date'] < one_year_ago] for idx, row in valid_stocks.iterrows(): ts_code = row['ts_code'] # 请求日线行情,获取截至今天最近120个交易日 df = pro.daily(ts_code=ts_code, start_date='20240101', end_date=today) if df is None or df.empty: continue df = df.sort_values('trade_date') # 按日期升序排列 df['ma5'] = df['close'].rolling(5).mean() df['ma10'] = df['close'].rolling(10).mean() df['ma20'] = df['close'].rolling(20).mean() # 只需要最近一条数据做判断 latest = df.iloc[-1] if latest['ma5'] > latest['ma10'] > latest['ma20']: result_list.append({ 'ts_code': ts_code, 'name': row['name'], 'industry': row['industry'], 'close': latest['close'], 'ma5': latest['ma5'], 'ma10': latest['ma10'], 'ma20': latest['ma20'], 'vol_ratio': latest['vol'] / df['vol'].iloc[-6:-1].mean(), 'turnover_rate': latest.get('turnover_rate', None) }) time.sleep(0.5) # 控制请求频率,防止限流 # 转成DataFrame candidate_df = pd.DataFrame(result_list)

需要说明的是pro.daily返回的数据里,vol单位是手,1手等于100股。如果后续需要换算成股票数量来对比,记得乘100。不过我们在这里只算相对比例(当日量/5日均量),所以是否换算不影响判断。

4.4 二次筛选与排序输出

拿到均线多头排列的股票池之后,还要叠加"放量""非涨停"的条件以及换手率排序:

# 放量条件:当日成交量是5日均量的1.5倍以上 candidate_df = candidate_df[candidate_df['vol_ratio'] > 1.5] # 剔除涨停股票,涨停幅度主板约10%,创业板和科创板约20% # 判断方法:当日收盘价/前收盘价 - 1 >= 9.8%(主板),这里简化处理 # 涨停过滤更严谨的做法是根据股票所属板块动态判断阈值,这里为演示使用9.8%简化 candidate_df = candidate_df.dropna(subset=['vol_ratio']) # 按换手率降序取前20 final_df = candidate_df.sort_values('close', ascending=False).head(20)

这里有一点说明一下:上面代码里的vol_ratio我计算的是全年数据下最近5天的均量,如果你跑的是每次拉全部历史再算,逻辑也是一样的。涨停过滤我为了演示写简化版,真正用的时候建议拿limit字段判断,pro.daily返回的pct_chg字段差值大于等于9.8基本可以认定涨停(ST股是5%)。创业板、科创板涨停幅度是20%,条件表达式改成19.8就行。要注意的是,这些都不是死规则,你可以按自己的交易风格调整。

最后把结果保存下来,顺便打印到屏幕上:

final_df.to_csv('select_stocks.csv', index=False, encoding='utf-8-sig') print(final_df[['ts_code', 'name', 'industry', 'close', 'vol_ratio']].to_string(index=False))

utf-8-sig这个编码是我强烈推荐的选项。直接utf-8存的话,你用Excel打开CSV文件时中文会变成乱码,加上-sig之后Excel就能正常识别,这个小细节能省下不少解释成本。

以上就是整个程序的核心部分,连起来跑一遍,收盘后大概40分钟(或者缩小股票池到500只,只需要几分钟)就能拿到一份完整的候选股名单。

5. 策略回测:不能只测一天,要跑一整年来验证

程序能跑出结果只是第一步,策略靠不靠谱,得拿历史数据验证。我自己用的做法是"历史回测":

  • 选择一段历史区间,比如2023年1月到2024年6月。
  • 在每个交易日收盘时,都用当时的行情跑一次上面的选股逻辑。
  • 假设第二天以开盘价买入,持有5个交易日后以收盘价卖出(这个持有期可以自己改)。
  • 累计计算每年收益率、最大回撤、交易次数。
def backtest(): # 获取交易日历 trade_days = pro.trade_cal(start_date='20230101', end_date='20240630') trade_days = trade_days[trade_days['is_open'] == 1]['cal_date'].tolist() returns = [] # 每隔5个交易日选一次股,模拟持有5日 for i in range(0, len(trade_days) - 5, 5): date = trade_days[i] # 为了节省时间,只对全市场的前300只做示例回测 sample = pro.daily(trade_date=date) if sample is None or sample.empty: continue # 简化的选择逻辑:日涨跌幅>3%且换手率>5% selected = sample[(sample['pct_chg'] > 3) & (sample['turnover_rate'] > 5)] if len(selected) == 0: continue # 买入价格:下一交易日开盘价 next_date = trade_days[i + 1] next_open = pro.daily(trade_date=next_date) if next_open is None or next_open.empty: continue merged = selected.merge(next_open[['ts_code', 'open']], on='ts_code') if len(merged) == 0: continue # 卖出价格:第5个交易日收盘价 sell_date = trade_days[i + 5] sell_data = pro.daily(trade_date=sell_date) if sell_data is None or sell_data.empty: continue merged = merged.merge(sell_data[['ts_code', 'close']], on='ts_code') # 计算收益率(这里可以加手续费0.1%) merged['ret'] = (merged['close'] - merged['open']) / merged['open'] - 0.002 returns.append(merged['ret'].mean()) if returns: total_ret = (1 + pd.Series(returns)).prod() - 1 print(f'模拟区间总收益率: {total_ret:.2%}') print(f'平均每期收益率: {pd.Series(returns).mean():.2%}') print(f'最大单期回撤: {pd.Series(returns).min():.2%}')

免责声明得说清楚:这个回测结果不构成投资建议,我自己用它主要为了验证策略逻辑是不是自洽,以及参数设得是否合理。回测里面看着收益高,实盘中因为滑点、流动性、停牌等因素,通常会有明显差距。特别是小市值股票,买不了那么多、卖的时候也可能砸盘,回测结果要打一个折扣来看。

6. 常见问题与优化方向:想从能用升级到好用,看这篇就够了

6.1 我实际踩过的几个坑

问题现象可能原因解决办法
运行时报API called with invalid tokentoken没设置或者复制多了空格重新ts.set_token(),注意去掉首尾空白
接口返回积分不足提示账号积分不够完善资料、每日签到,或换用旧接口过渡
CSV打开中文乱码编码用了utf-8改用utf-8-sig保存
程序跑得太慢每只股票都实时请求缓存到本地、缩小股票池、用ts_code列表批量请求
某天数据里有NaN次新股上市时间短,均线算不出来dropna()过滤,或者直接剔除上市不足一年的股票
涨停判断不准确不同板块涨幅限制不一样根据ts_code前缀判断:60/00开头主板10%,30开头创业板20%,68开头科创板20%

6.2 后续优化的几个方向

  • 增加基本面因子:把市盈率(daily_basic接口有pe_ttmpb字段)加进筛选条件,筛掉估值过高的标的。
  • 接入实时行情做盘中监控:TuShare的ts.realtime_quote(旧版)能拿到盘中快照,配合定时任务(schedule库),可以做成开盘后每5分钟自动刷新一次候选池。
  • 用Backtrader做更严谨的回测:Backtrader是一个专业的Python回测框架,支持滑点、手续费、多策略对比,但上手门槛高一些,适合进阶后研究。
  • 加入风控模块:比如单只股票仓位不超过总资金的10%,回撤超过一定幅度自动空仓,这点对实盘账户保护极其重要。

另外我最后想分享一个小心得:自动选股程序的价值不是替你做决定,而是帮你把"想法"快速变成"数据验证"。以前你想试一个新策略,可能要手动翻一周的行情图来验证,现在用TuShare拉数据、pandas写筛选逻辑、回测看一眼收益率,半小时内就能得到初步结论。这个效率提升,才是编程做投资的真正意义所在。

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

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

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

立即咨询