☰
Python量化筛选创新药低估股:估值分位与研发因子实战
2026/10/15 13:15:37 网站建设 项目流程

医疗创新药“猛猛上涨”,科技板块开始震荡,市场风格是不是真的切到了“大低估时代”?这个问题我判断不了,也不打算判断。但有一件事可以用技术手段解决:把“低估”从口号变成可量化、可批量扫描的数据结果。

今天这篇文章不是荐股文,而是直接写给开发者和研究型投资者的工具文档。我们用 Python 拿到创新药板块的成分股列表,拉取行情、估值、财务和研发投入数据,做一个自动化筛选流程,批量计算每只股票的 PE、PB 历史分位,再结合研发费用和管线进度做二次过滤,最后输出一份结构化的低估候选清单。整个过程可以一键跑完,也能接入定时任务,让数据每天自动更新。

为了不让文章太散,我先把这套方案的关键信息列出来,你看完就知道值不值得跟着做完。

1. 核心能力速览

能力项说明
项目类型金融数据批量采集 + 低估筛选分析工具
主要功能创新药板块成分股获取、行情数据拉取、估值分位计算、研发投入过滤、结果排序输出
数据源方式通过公开 Python 数据接口获取,以 akshare、tushare 等开源库为例
批量任务支持循环遍历全板块成分股,批量获取数据
定时更新可接入 APScheduler 或系统 crontab,每日盘后自动更新
输出格式CSV 文件 + Markdown 报告 + 可视化图表
硬件门槛普通办公电脑即可,无 GPU 要求
启动方式命令行运行 Python 脚本,或通过 Jupyter Notebook 分步执行
是否支持 API需要本地搭建,可封装为 Flask 或 FastAPI 接口
适合场景板块扫描、估值研究、批量数据整理、投资辅助决策(不构成投资建议)

这套方案的核心思路不复杂:把过去需要手动打开行情软件逐个查 PE、PB、翻年报的工作,变成一段代码自动跑完。数据量不大时,普通笔记本几分钟就能完成全板块扫描。

2. 适用场景与使用边界

先说清楚这套工具适合谁。

如果你是做行业研究的分析师,需要每天跟踪创新药板块的估值变化,这个工具能帮你把重复劳动去掉。如果你在维护自己的投资组合,想定期看一下关注池里哪些标的处于历史低估区间,这个工具能给出客观数据。如果你是量化爱好者,想给现有的选股策略加一个“估值过滤条件”,这套流程可以直接嵌入你的策略代码。

它的优势在于三个点:

  • 数据获取自动化,不用手动复制粘贴。
  • 筛选逻辑可重复、可审计,PE、PB、研发费用这些因子全部落表。
  • 输出结构化,方便对接自己的数据库或可视化看板。

但它不适合什么场景?不适合拿来自动下单、无脑跟买。原因很直接:估值低不等于马上涨,创新药板块的定价逻辑大量依赖临床数据、审批进度和商业化预期,这些信息很难完全通过财务指标捕捉。因此,这份工具输出的是“研究线索”,不是“买卖指令”。

还有一个必须强调的边界:合规与授权。使用任何第三方数据接口前,要先确认该数据源的使用条款,尤其是商用场景。个人研究通常没问题,但如果要接入生产环境或对外提供服务,需要评估数据版权和接口限流要求。同时,本文所有代码和流程仅用于技术交流与量化研究,不构成任何投资建议。

3. 环境准备与数据源选择

这套流程不需要 GPU,也不需要高配服务器,一台能跑 Python 的普通电脑就行。建议配置如下:

项目建议
操作系统Windows 10/11、macOS、Ubuntu 20.04+ 均可
Python 版本3.9 及以上
核心依赖pandas、numpy、requests、matplotlib、openpyxl
金融数据接口akshare、tushare(任选其一,需要注册获取 token 的以官方文档为准)
磁盘空间500MB 以内,主要为缓存数据
网络要求能正常访问数据源接口即可

安装依赖的通用命令如下:

pip install pandas numpy requests matplotlib openpyxl pip install akshare

如果你选择 tushare,还需要先到官方网站注册账号并获取 token,然后在脚本中配置:

import tushare as ts ts.set_token("你的token") pro = ts.pro_api()

这里我默认使用 akshare 作为演示数据源,因为它不需要注册 token,免费接口较多,适合个人研究和快速验证。但要注意,akshare 的接口名称和字段会随上游数据源调整而变化,实际运行时要根据当前版本文档调整函数名。

4. 获取创新药板块行情数据

4.1 拉取板块成分股列表

第一步是拿到“创新药板块”到底包含哪些股票。akshare 的板块数据来自主流财经网站,通常有“创新药”概念板块。

import akshare as ak import pandas as pd # 获取A股概念板块列表 board_df = ak.stock_board_concept_name_em() print(board_df.head()) # 筛选出名称包含“创新药”的板块 target_boards = board_df[board_df['板块名称'].str.contains('创新药')] print(target_boards)

筛选到目标板块后,获取其成分股:

code = target_boards.iloc[0]['板块名称'] stocks_df = ak.stock_board_concept_cons_em(symbol=code) print(stocks_df.head()) print("成分股数量:", len(stocks_df))

这一步得到的是股票代码、名称、最新价等基础信息。把代码列保存下来,后续所有批量任务都基于这个列表展开。

如果 akshare 的板块分类里没有直接叫“创新药”的概念,也可以变通处理:用申万行业分类里的“化学制药”“生物制品”,再结合主营业务关键词过滤。这种方法的优点是口径更可控,缺点是数据清洗工作量更大。

4.2 获取个股行情与估值指标

拿到成分股列表后,逐个拉取行情和估值数据。akshare 有专门获取个股财务和估值指标的函数,操作方式如下:

def get_stock_valuation(stock_code): """ 获取单只股票的最新估值和财务指标 注意:akshare 的接口函数名会调整,以官方文档为准 """ try: df = ak.stock_a_indicator_lg(symbol=stock_code) latest = df.iloc[-1] return { "代码": stock_code, "日期": latest["trade_date"], "PE": latest["pe_ttm"], "PB": latest["pb"], "总市值": latest["total_mv"], } except Exception as e: print(f"获取 {stock_code} 失败:{e}") return None

对一个几十只股票的板块来说,单线程循环够用了。如果板块规模超过 100 只,建议加上threading或concurrent.futures做并发请求,但一定要控制并发数,避免触发数据源限流。

这里给出的函数是基于 akshare 常见接口的示意写法,字段名可能随版本变化。运行前先打印一两条数据,确认列名再继续。

5. 低估值筛选逻辑设计

5.1 估值指标选择与分位计算

“低估”这个词很主观,但量化之后可以变成客观标准。最常用的两个指标是 PE-TTM 和 PB。

PE-TTM 适合盈利稳定、已经商业化的药企,但很多创新药公司还处于亏损阶段,PE 为负数或缺失,参考价值有限。这时 PB 更有意义,尤其对研发管线密集、资产较轻的 Biotech 公司,PB 结合研发投入能反映市场定价与账面资产的偏离程度。

更稳妥的做法是看历史分位。所谓“低估”,最好理解成“当前估值处于自身历史区间的较低位置”。

def calc_valuation_percentile(valuation_df): """ 计算 PE、PB 在历史数据中的分位 """ valuation_df["PE分位"] = valuation_df["PE"].rank(pct=True) valuation_df["PB分位"] = valuation_df["PB"].rank(pct=True) return valuation_df

这里用rank(pct=True)得到的是当前值在历史序列中的百分位排名。数值越小,说明当前估值越接近历史底部。

实际操作时,建议至少取 3 到 5 年历史数据,覆盖一轮行业周期。窗口太短,分位参考意义不大。

5.2 研发投入与管线数据清洗

创新药公司不能只看当期利润。很多公司把大笔资金投入研发,当期利润被费用压低,估值自然显得高。只看 PE 会误杀这类公司,所以需要加入研发费用作为过滤条件。

def get_rd_expense(stock_code): """ 获取研发费用数据 """ try: # 以财务指标接口为例,字段名以实际返回为准 df = ak.stock_financial_abstract_ths(symbol=stock_code) rd_col = [col for col in df.columns if "研发" in col] if rd_col: return df[["报告期"] + rd_col].head(1).to_dict("records")[0] return None except Exception as e: print(f"获取研发费用失败:{e}") return None

如果数据源不直接提供研发费用,也可以从利润表的三费构成中拆分,或者用“研发费用占营业总收入比例”作为替代指标。更精确的管线数据,比如临床阶段、适应症、靶点,公开数据源覆盖不全,这部分可以作为人工复核项。

5.3 筛选流程实现

完整筛选流程分四步:

  1. 拉取板块成分股。
  2. 获取每只股票的 PE、PB、历史分位。
  3. 获取研发费用和研发营收比。
  4. 过滤出“PB 分位 < 20% 且研发营收比 > 10%”的标的,输出候选列表。
import pandas as pd from concurrent.futures import ThreadPoolExecutor def screening_flow(stock_list): results = [] with ThreadPoolExecutor(max_workers=4) as executor: futures = {executor.submit(get_stock_valuation, code): code for code in stock_list} for future in as_completed(futures): data = future.result() if data: results.append(data) df = pd.DataFrame(results) # 清洗无效值 df = df.dropna(subset=["PE", "PB"]) # 计算分位 df["PB分位"] = df["PB"].rank(pct=True) df["PE分位"] = df["PE"].rank(pct=True) # 筛选条件:PB分位低于20% low_screened = df[df["PB分位"] < 0.20].copy() # 二次筛选:研发营收比高于10%(此处研发字段按实际数据源调整) # low_screened = low_screened[low_screened["研发营收比"] > 0.10] return low_screened.sort_values("PB分位")

筛选条件不是死的。如果你更看重盈利稳定性,可以改成“PE 分位 < 30% 且净利润为正”;如果你只看 Biotech,可以去掉 PE 条件,更多依赖 PB 分位和管线数据。核心思路是:把估值因子变成分位数,再结合行业特色因子做条件过滤。

6. 批量任务与数据更新设计

6.1 批量处理策略

整个流程天然适合批量执行。把股票代码列表存成 CSV 文件,循环或并发拉取数据,最后汇总输出。

# 保存成分股列表 stock_list = stocks_df["代码"].tolist() pd.DataFrame({"代码": stock_list}).to_csv("./output/创新药成分股.csv", index=False) # 批量获取估值数据 result_df = screening_flow(stock_list) result_df.to_csv("./output/低估筛选结果.csv", index=False, encoding="utf-8-sig")

建议在项目目录下建一个清晰的结构:

innovation_drug_screener/ ├── input/ # 输入数据,如成分股列表 ├── output/ # 筛选结果、报告 ├── cache/ # 接口缓存,避免重复请求 ├── scripts/ # Python 脚本 ├── config.yaml # 配置文件 └── requirements.txt # 依赖清单

6.2 定时更新任务

研究低频数据,比如每日估值更新,不需要实时流式处理。用系统定时任务就够了。

Linux/macOS 下使用 crontab:

0 18 * * 1-5 cd /path/to/project && python scripts/run_screener.py >> logs/screener.log 2>&1

Windows 下可以用任务计划程序,或用 Python 的 APScheduler:

from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() @scheduler.scheduled_job("cron", day_of_week="mon-fri", hour=18, minute=0) def daily_screening(): run_screener() scheduler.start()

批量任务跑起来后,输出文件会每天更新,你就有一个持续跟踪板块低估标的的数据底座。

7. 结果输出与可视化

筛选结果不能只是一堆数字。为了让最终的 CSV 可读、可分享,可以增加两个输出:Markdown 报告和估值分布图。

def generate_markdown_report(ranked_df): """生成 Markdown 格式的低估候选清单""" lines = ["| 排名 | 代码 | 最新价 | PE | PB | PB分位 |", "| --- | --- | --- | --- | --- | --- |"] for i, row in ranked_df.iterrows(): lines.append(f"| {i+1} | {row['代码']} | {row['最新价']} | {row['PE']:.2f} | {row['PB']:.2f} | {row['PB分位']:.2%} |") return "\n".join(lines)
import matplotlib.pyplot as plt def plot_pb_distribution(df): """绘制 PB 分布散点图""" plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False plt.figure(figsize=(10, 6)) plt.scatter(df["总市值"], df["PB"], alpha=0.6) plt.xlabel("总市值") plt.ylabel("PB") plt.title("创新药板块估值分布") plt.grid(True, linestyle="--", alpha=0.5) plt.savefig("./output/创新药板块估值分布.png", dpi=150)

报告和图表保存后,可以直接丢进自己的笔记系统,也可以作为研究底稿留存。唯一要注意的是,图表和报告只是辅助工具,它们不能替代最终的逐票人工复核。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
接口返回空数据接口路径变更或上游数据源改版打印返回结果,查看文档更新 akshare 版本,或改用备用数据接口
某只股票 PE 为 NaN公司亏损,没有正的每股收益检查财务数据不删除该股票,用 PB 替代 PE 做筛选
请求频繁被限流短时间大量请求公共接口检查返回状态码和错误信息降低并发数,加随机延时
板块名称找不到概念板块名称变化打印全量板块列表搜索改用行业分类或自定义股票池
数据源接口变更上游财经网站接口改版查看最新 akshare 文档和 issue替换为对应新接口函数
CSV 中文乱码编码问题用文本编辑器打开查看保存时使用utf-8-sig编码
定时任务不执行cron 环境变量或路径问题查看 cron 日志,手动执行脚本在脚本中使用绝对路径

最常踩的坑是接口字段名变化。akshare 这类库依赖上游数据源的页面结构,页面一改,函数和字段就会跟着变。所以建议在代码里加一层“字段名检查”,发现 ValueError 或空 DataFrame 时,先打印原始列名,不要盲目改代码。

9. 最佳实践与合规提醒

再强调一遍,任何估值筛选工具都只是研究辅助,不是投资决策系统。以下几个工程化建议能让这套流程更可靠:

第一,数据接口要有多路备份。akshare 挂了就切 tushare,tushare 限流就切 CSV 离线数据。工程上不要把单点依赖放到生产环境里。

第二,缓存要充分利用。同类数据当天重复拉取没有意义,把每天的板块成分股、行情数据缓存到本地,既能减少接口压力,也能让回测和复盘有历史数据可用。

第三,过滤条件要可配置。把 PE 分位阈值、PB 分位阈值、研发营收比阈值放到配置文件中,方便动态调整。

# config.yaml 示例 screener: board_name: "创新药" pb_percentile_threshold: 0.20 pe_percentile_threshold: 0.30 min_rd_ratio: 0.10 use_pe: true use_pb: true use_rd: false

第四,合规红线必须清楚。所有数据源只能用于合法授权范围内的研究用途。涉及个股分析结果时,要注明数据来源和分析日期,不要以“保证收益”之类的表述传播。文中提到的所有功能仅用于技术研究,不对任何投资行为负责。

第五,如果你要把这套流程开放给团队或做成服务,建议用 FastAPI 包一层接口,把扫描和筛选逻辑封装成独立模块,前端只负责传参和展示。

10. 总结与下一步

回到标题的问题:医疗创新药猛涨,科技是否过时,是不是“大低估时代”。这些市场判断交给时间去验证,但“低估”两个字完全可以通过数据工具客观量化。

这套基于 Python 的创新药低估筛选工具,值得动手的部分有三个:一是用公开数据接口批量获取板块成分股和估值数据,告别手动查行情的低效操作;二是用 PE、PB 历史分位建立低估判断标准,把主观感受变成可回测的数字;三是把整个流程做成批量任务和定时更新,让研究数据每天自动沉淀。

最容易踩的坑也提醒到位:接口字段名会变,缓存一定要加,动态配置别写死在代码里。

下一步可以继续扩展的方向很明确:把财务数据扩展到净利润增速和研发资本化比例,叠加临床管线和审批进度数据,形成多因子打分模型;也可以把筛选结果接入自己的行情终端,做每日盘前提示。这个框架本身不挑板块,把“创新药”换成“半导体”“消费电子”,就是一套通用的行业低估筛选系统。结构不变,变的只是过滤因子和数据源。

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

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

立即咨询