ARTICLE DETAIL

资讯详情

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

Python量化筛选创新药低估股:估值分位与研发因子实战

Python量化筛选创新药低估股:估值分位与研发因子实战 医疗创新药“猛猛上涨”科技板块开始震荡市场风格是不是真的切到了“大低估时代”这个问题我判断不了也不打算判断。但有一件事可以用技术手段解决把“低估”从口号变成可量化、可批量扫描的数据结果。今天这篇文章不是荐股文而是直接写给开发者和研究型投资者的工具文档。我们用 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(symbolcode) 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(symbolstock_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(pctTrue) valuation_df[PB分位] valuation_df[PB].rank(pctTrue) return valuation_df这里用rank(pctTrue)得到的是当前值在历史序列中的百分位排名。数值越小说明当前估值越接近历史底部。实际操作时建议至少取 3 到 5 年历史数据覆盖一轮行业周期。窗口太短分位参考意义不大。5.2 研发投入与管线数据清洗创新药公司不能只看当期利润。很多公司把大笔资金投入研发当期利润被费用压低估值自然显得高。只看 PE 会误杀这类公司所以需要加入研发费用作为过滤条件。def get_rd_expense(stock_code): 获取研发费用数据 try: # 以财务指标接口为例字段名以实际返回为准 df ak.stock_financial_abstract_ths(symbolstock_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 筛选流程实现完整筛选流程分四步拉取板块成分股。获取每只股票的 PE、PB、历史分位。获取研发费用和研发营收比。过滤出“PB 分位 20% 且研发营收比 10%”的标的输出候选列表。import pandas as pd from concurrent.futures import ThreadPoolExecutor def screening_flow(stock_list): results [] with ThreadPoolExecutor(max_workers4) 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(pctTrue) df[PE分位] df[PE].rank(pctTrue) # 筛选条件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, indexFalse) # 批量获取估值数据 result_df screening_flow(stock_list) result_df.to_csv(./output/低估筛选结果.csv, indexFalse, encodingutf-8-sig)建议在项目目录下建一个清晰的结构innovation_drug_screener/ ├── input/ # 输入数据如成分股列表 ├── output/ # 筛选结果、报告 ├── cache/ # 接口缓存避免重复请求 ├── scripts/ # Python 脚本 ├── config.yaml # 配置文件 └── requirements.txt # 依赖清单6.2 定时更新任务研究低频数据比如每日估值更新不需要实时流式处理。用系统定时任务就够了。Linux/macOS 下使用 crontab0 18 * * 1-5 cd /path/to/project python scripts/run_screener.py logs/screener.log 21Windows 下可以用任务计划程序或用 Python 的 APSchedulerfrom apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler() scheduler.scheduled_job(cron, day_of_weekmon-fri, hour18, minute0) 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| {i1} | {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], alpha0.6) plt.xlabel(总市值) plt.ylabel(PB) plt.title(创新药板块估值分布) plt.grid(True, linestyle--, alpha0.5) plt.savefig(./output/创新药板块估值分布.png, dpi150)报告和图表保存后可以直接丢进自己的笔记系统也可以作为研究底稿留存。唯一要注意的是图表和报告只是辅助工具它们不能替代最终的逐票人工复核。8. 常见问题与排查方法问题现象可能原因排查方式解决方案接口返回空数据接口路径变更或上游数据源改版打印返回结果查看文档更新 akshare 版本或改用备用数据接口某只股票 PE 为 NaN公司亏损没有正的每股收益检查财务数据不删除该股票用 PB 替代 PE 做筛选请求频繁被限流短时间大量请求公共接口检查返回状态码和错误信息降低并发数加随机延时板块名称找不到概念板块名称变化打印全量板块列表搜索改用行业分类或自定义股票池数据源接口变更上游财经网站接口改版查看最新 akshare 文档和 issue替换为对应新接口函数CSV 中文乱码编码问题用文本编辑器打开查看保存时使用utf-8-sig编码定时任务不执行cron 环境变量或路径问题查看 cron 日志手动执行脚本在脚本中使用绝对路径最常踩的坑是接口字段名变化。akshare 这类库依赖上游数据源的页面结构页面一改函数和字段就会跟着变。所以建议在代码里加一层“字段名检查”发现 ValueError 或空 DataFrame 时先打印原始列名不要盲目改代码。9. 最佳实践与合规提醒再强调一遍任何估值筛选工具都只是研究辅助不是投资决策系统。以下几个工程化建议能让这套流程更可靠第一数据接口要有多路备份。akshare 挂了就切 tusharetushare 限流就切 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 历史分位建立低估判断标准把主观感受变成可回测的数字三是把整个流程做成批量任务和定时更新让研究数据每天自动沉淀。最容易踩的坑也提醒到位接口字段名会变缓存一定要加动态配置别写死在代码里。下一步可以继续扩展的方向很明确把财务数据扩展到净利润增速和研发资本化比例叠加临床管线和审批进度数据形成多因子打分模型也可以把筛选结果接入自己的行情终端做每日盘前提示。这个框架本身不挑板块把“创新药”换成“半导体”“消费电子”就是一套通用的行业低估筛选系统。结构不变变的只是过滤因子和数据源。
返回列表