Python构建投资实证数据工作流:股息率计算与持仓快照
案例背景:经济学教师投资实证DAY61。这一天的记录里有一个非常典型的场景:腾讯中报发布后,市场对成长股盈利预期的讨论明显升温,稳健派的持仓逻辑被重新审视;实际操作上,实证账户买入大唐发电(601991)和中国神华(601088)。这两个动作放在一起看,不是简单的“抄底”或“追涨”,而更像一次从成长中枢向高股息红利端的再平衡。这篇文章不打算复述消息面,而是把 DAY61 这个实证场景拆成一套可复用的技术工作流:用 Python 批量拉取财务和行情数据,计算股息率、PB、PE、ROE 等核心指标,把买入原因写入交易日志,最终生成持仓快照。拿到这套流程后,下一次再遇到中报冲击、行业政策变化或个股估值波动,都能按同一套步骤完成数据复盘,而不是手动贴一堆截图然后凭感觉解释。
这套流程适合这几种读者:一是正在做个人投资实证、想给每笔交易留下数据依据的人;二是持有腾讯、大唐、神华这类标的,想在中报季快速批量更新基本面数据的人;三是做稳健型持仓管理,希望把股息率、ROE、持仓集中度做成固定报表的人。硬性门槛不高,一台普通办公电脑就能跑,不需要显卡,也不需要 GPU 服务器。需要特别提醒的是:本文属于实证记录与数据处理方法分享,不构成任何投资建议,所有代码和参数仅供参考。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 个人投资实证跟踪与复盘流程 |
| 案例场景 | DAY61:腾讯中报冲击稳健持仓逻辑,买入大唐发电、中国神华 |
| 核心技术 | Python、AKShare 数据接口、pandas 指标计算、SQLite 交易日志 |
| 主要功能 | 批量拉取行情/财务数据,计算股息率、PB、PE、ROE,记录买卖日志,生成持仓快照 |
| 硬件要求 | 普通 CPU 即可,无需独立显卡 |
| 运行平台 | Windows / macOS / Linux |
| 启动方式 | 命令行运行脚本,或用 schedule 配置定时任务 |
| 是否支持 API | 数据源提供接口,本地脚本按批次调用 |
| 是否支持批量任务 | 支持,按股票池循环更新 |
| 输出形式 | CSV、SQLite 数据库、控制台报表 |
| 适合场景 | 个人投资复盘、财报季批量跟踪、稳健型持仓管理 |
| 合规边界 | 仅限个人研究,不构成投资建议,不自动化下单 |
这套能力速览的关键信息是:它不是一个自动交易系统,而是一个让投资实证“可回看、可计算、可批量更新”的数据底座。DAY61 只是这套底座上的一个案例。
2. 适用场景与使用边界
这套流程定位很窄,但使用频率很高。它首先服务于“投资实证”这件事本身。经济学教师做到 DAY61,意味着前面已经跑了几十个交易日的连续记录,这种持续记录最怕两件事:第一是数据口径前后不一致,比如今天用不复权价格、明天用前复权价格,股息率就会算乱;第二是操作原因记不清,时间一长就分不清楚当时到底是基于估值还是基于情绪。用数据库和脚本能同时解决这两个问题。
腾讯中报落地后,持仓者真正关心的不是新闻标题,而是三个可计算的问题:自己的持仓估值中枢变了多少、组合里高股息资产的占比够不够、卖出或买入操作是否在数据上站得住脚。本文提供的流程,就是把“腾讯中报 → 预期变化 → 调仓买入大唐和神华”这个链条,转成可查询、可回看的结构化记录。买大唐和神华的时候,至少要在表里写清楚:买的是高股息、低估值的防御属性,还是补仓能源电力板块的仓位。
使用边界也要说清楚。这套流程不适合短线高频交易,因为 AKShare 这类公开财经数据接口的延迟和限频,支撑不了盘中秒级决策;更不适合自动化下单。本文所有代码都不对接券商交易接口,也不会把分析结果直接转成交易指令。还有一个容易被忽略的合规边界:从数据源获取行情和财报数据后,如果要发布到博客、社区或公众号,应当遵循数据源的版权要求,不能把原始数据打包二次分发。交易记录属于个人敏感信息,数据库文件不要公开提交到 GitHub。如果账户涉及他人资金或委托关系,更需要明确授权,并遵守当地证券法规。
3. 环境准备与前置条件
本地环境需要准备的东西不多,核心是 Python、数据科学库和网络。
| 依赖项 | 建议版本 | 用途 |
|---|---|---|
| Python | 3.9 及以上 | 运行脚本 |
| pandas | 1.5 及以上 | 数据清洗与指标计算 |
| AKShare | 最新稳定版 | 获取 A 股/港股行情和财务数据 |
| SQLAlchemy 或 sqlite3 | 内置即可 | 存储交易记录和持仓快照 |
| schedule | 最新版 | 定时批量更新 |
| python-dotenv | 最新版 | 管理数据源 Token 等配置 |
网络方面,只要能访问常用的财经数据接口即可。磁盘占用很小,整个项目加上数据文件通常不超过 200MB。这个项目没有固定的 Web 服务端口,如果以后接可视化面板,再考虑 8000 或 8501 这类端口。
需要提前说明一点:AKShare 的接口名称和返回字段会随上游页面调整,安装时建议锁定版本,并定期查看官方更新。本文代码是流程模板,不是永久有效的接口字典,运行时报错优先检查接口名。
4. 安装部署与启动方式
下面给出一套完整的本地安装流程。先创建项目目录和虚拟环境,再安装依赖。
mkdir invest-tracker cd invest-tracker python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate pip install akshare pandas sqlalchemy schedule python-dotenv建议把环境版本写入 requirements.txt,方便后续重建。
pip freeze > requirements.txt项目目录可以这样组织:
invest-tracker/ ├── data/ │ ├── 601088.csv │ ├── 601991.csv │ └── 00700.csv ├── scripts/ │ ├── fetch_kline.py │ ├── calc_indicators.py │ └── update_daily.py ├── portfolio.db ├── stock_list.csv └── .envdata/放行情数据,scripts/放脚本,portfolio.db是 SQLite 数据库,stock_list.csv放股票池基础信息。
如果不想每次都在命令行敲 Python,可以做双击启动。Windows 下写一个start.bat:
@echo off cd /d %~dp0 call venv\Scripts\activate.bat python scripts\update_daily.py pause首次运行建议直接执行更新脚本,确认股票池里的三只标的能正常取数,再往后续逻辑扩展。
5. 功能测试与效果验证
这一章按功能拆开测。每个功能都给出操作步骤、预期结果和常见排查点。
5.1 拉取腾讯与A股标的核心行情数据
测试目的:确认 AKShare 能正常拿到大唐发电(601991)、中国神华(601088)的 A 股日线数据。腾讯是港股,代码是 00700,港股接口和 A 股接口通常不同,需要单独处理。
import akshare as ak # 示意代码:AKShare 接口版本更新较快,请以当前官方文档为准 # 大唐发电 df_600991 = ak.stock_zh_a_hist( symbol="601991", period="daily", start_date="20240101", end_date="20240901", adjust="qfq" ) print("大唐发电 rows:", len(df_600991)) print(df_600991.tail())预期结果是输出最近若干个交易日的开盘价、收盘价、成交量等字段。如果这里报错,先检查接口名是否已更新,再检查网络是否正常。
港股腾讯类数据建议单独查 AKShare 的港股接口,不要在 A 股接口上硬套,否则会出现代码失效或数据错乱。
5.2 计算稳健持仓核心指标
测试目的:把行情价格和基本面字段组合起来,计算股息率、PB、PE、ROE。下面用一份 CSV 作为输入,字段需要提前准备好:代码、名称、当前价格、每股分红 DPS、每股净资产 BPS、每股收益 EPS。
import pandas as pd df = pd.read_csv("stock_list.csv") df["股息率"] = df["每股分红DPS"] / df["当前价格"] * 100 df["PB"] = df["当前价格"] / df["每股净资产BPS"] df["PE"] = df["当前价格"] / df["每股收益EPS"] df["ROE"] = df["每股收益EPS"] / df["每股净资产BPS"] * 100 result = df.sort_values("股息率", ascending=False) print(result.to_string(index=False))操作时需要注意字段对齐:腾讯的“每股分红DPS”要按港币或人民币口径统一;大唐和神华是 A 股,使用人民币口径。不同市场放在同一张表里比较时,务必先统一币种。
预期结果是一张包含股息率、PB、PE、ROE 的排序表。判断成功与否的标准是:大唐和神华这类高股息标的的股息率排名明显靠前,腾讯则体现出高 ROE、相对低股息率的特征。
5.3 买入日志与持仓快照
测试目的:把 DAY61 的买入操作写入数据库,并生成持仓快照。这个功能是整个实证流程的地基。
import sqlite3 from datetime import date conn = sqlite3.connect("portfolio.db") conn.execute(""" CREATE TABLE IF NOT EXISTS trades ( id INTEGER PRIMARY KEY AUTOINCREMENT, trade_date TEXT NOT NULL, symbol TEXT NOT NULL, name TEXT, side TEXT, price REAL, shares REAL, reason TEXT ) """) conn.execute( "INSERT INTO trades (trade_date, symbol, name, side, price, shares, reason) VALUES (?,?,?,?,?,?,?)", ( "2024-08-15", "601991", "大唐发电", "BUY", 3.50, 10000, "DAY61:中报后高股息逻辑验证,降低组合波动", ) ) conn.commit() for row in conn.execute("SELECT trade_date, symbol, name, side, price, shares FROM trades"): print(row)需要说明的是,上面的价格和股数是示意值。实际运行时应替换成真实成交数据,同时保留买入理由。预期结果是交易记录插入成功,之后可以通过统计函数计算市值占比和持仓集中度。
5.4 中报冲击后的复盘维度
腾讯中报对稳健派持仓逻辑的冲击,常见影响集中在三个维度。
第一,盈利预期变化。将中报披露的净利润同比增速与财报发布前的一致预期增速对比,如果实际增速低于预期,意味着基于未来 EPS 的估值中枢要下调,这时 PE 的参考价值会改变。
第二,股东回报变化。腾讯这类互联网公司现金分红和回购政策对股息率有直接影响。中报如果提高分红或扩大回购,静态股息率会上升;如果资本开支加大、分红收缩,股息率会下降。稳健派持仓关注的是“自由现金流能不能支撑分红”。
第三,组合再平衡。DAY61 买入大唐和神华,一个典型动机是用高股息资产的稳定现金流,对冲成长股盈利波动。验证这一步的数据方法,是把组合中成长股和红利股的权重变化记录下来,并定期对比组合整体的股息率和最大回撤。
针对大唐和神华,复盘时需要关注的数据维度不完全一样。大唐发电这类电力公司的核心跟踪指标是上网电量、电价和燃料成本,这些会直接传导到净利润和股息支付能力;中国神华这种煤电一体化公司,要重点看长协煤比例、分红承诺和 ROE 稳定性。财务数据落地后,当前面的calc_indicators.py重新跑一遍,如果高股息标的的股息率依然稳定,买入逻辑就得到一次数据验证。
5.5 判断买入逻辑是否成立的通用标准
建议用三个标准衡量一次买入是否与实证逻辑一致:
- 股息率是否高于组合现有持仓的中位数,且分红来源具备可持续性。
- 买入后组合的行业集中度是否明显上升,如果超过预设阈值,需要限制仓位。
- 交易日志里是否写清楚了触发条件,例如“中报净利润增速低于预期”“股息率超过 4%”这类可验证条件,而不是“感觉会涨”。
如果三条都符合,这笔操作就是流程化决策;如果一条都不符合,那就说明这次买入更接近情绪驱动,需要单独标记,避免后续复盘时误认为是策略结果。
6. 接口 API 与批量任务
AKShare 本质上是通过 HTTP 接口抓取公开财经数据。对个人实证项目来说,没必要封装成服务,直接写脚本循环调用即可。需要注意限频,批量任务加一点随机等待,避免请求过密。
下面给出一个批量更新股票池的模板:
import schedule import time import akshare as ak import pandas as pd WATCH_LIST = ["601088", "601991", "00700"] def update_daily(): for symbol in WATCH_LIST: try: # 示意代码:实际接口需要按AKShare版本调整 df = ak.stock_zh_a_hist( symbol=symbol, period="daily", start_date="20240101", end_date="20241231", adjust="qfq" ) df.to_csv(f"data/{symbol}.csv", index=False) print(f"{symbol} updated, rows={len(df)}") except Exception as exc: print(f"{symbol} error: {exc}") schedule.every().day.at("09:30").do(update_daily) while True: schedule.run_pending() time.sleep(60)这个脚本有一个明显隐患:港股 00700 不能直接放在 A 股接口里循环。实际使用时,要把港股标的单独走港股接口,或者至少增加一个交易所字段,在循环里判断市场再选接口。
批量任务的工程化建议:
- 更新任务必须写日志,记录每次更新的起止时间、成功数量、失败标的。
- 对失败标的做重试,最多重试两次。
- 把股票池和运行配置分开,不要每次改代码。
- 如果数据源返回字段变化,先停止任务,更新字段映射后再继续。
如果后续要做成 Web API 服务,可以留一个最小接口入口,用 Flask 暴露本地服务:
from flask import Flask, jsonify import pandas as pd app = Flask(__name__) @app.route("/api/indicators") def indicators(): df = pd.read_csv("stock_list.csv") return jsonify(df.to_dict(orient="records")) if __name__ == "__main__": app.run(host="127.0.0.1", port=8000)这里只返回本地计算结果,不接收外部下单指令,安全边界更清晰。
7. 资源占用与性能观察
这个项目不涉及 GPU,所以不存在显存占用问题。资源占用主要体现在三个方面:CPU、网络、磁盘 IO。
单次拉取日线行情,AKShare 接口耗时通常在几百毫秒到几秒之间。批量更新 3 到 50 只股票,如果每只都重新请求,整体耗时会在几分钟以内。CPU 占用率很低,pandas 计算几百行数据基本不构成压力。比较值得关注的是网络请求频率,公开财经接口通常有限频,短时间大量请求容易触发限制或被封。
如果要降低请求压力,可以只在每周实证更新时拉全量数据,平时只增量拉最近几天。增量更新可以减少约九成请求量。磁盘方面,SQLite 数据库加 CSV 文件在几十 MB 以下,部署在轻量服务器上完全没问题。
观察资源占用的方法是在任务运行期间打开系统任务管理器,重点看 Python 进程的 CPU 和内存波动。如果发现内存持续上涨,优先怀疑数据在循环里反复拼接没有释放,建议把每轮处理后的 DataFrame 及时写盘。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| AKShare 接口报错 | 接口名或字段升级 | 查看官方文档和更新日志 | 替换为当前版本接口名 |
| 港股 00700 拉不到数据 | 误用 A 股接口 | 检查代码中的市场判断 | 港股标的走港股接口 |
| 股息率计算偏差 | 价格未复权,币种未统一 | 核对复权字段和币种 | 统一使用前复权价格和人民币口径 |
| 数据库重复记录 | 重复执行插入脚本 | 查询 trades 表 | 增加唯一约束或先查重再插入 |
| 定时任务没跑 | 电脑休眠或 Python 进程退出 | 查看日志文件 | 在服务器或常开电脑上部署 |
| 批量任务被限频 | 请求过于密集 | 查看返回限制提示 | 增加 sleep,降低频率 |
| 收益率口径混乱 | 分红再投逻辑不一致 | 检查计算函数 | 固定收益率口径,在代码注释中写明 |
| 数据源字段缺失 | 股票停牌或新股上市 | 打印原始字段 | 增加字段缺失处理 |
这类问题最常出现在前几次运行。建议每加一个功能,先跑通单只股票,再扩展到全部股票池。
9. 最佳实践与使用建议
第一,先小参数测试。第一次运行先只拉一个星期数据,只算一只股票,确认字段对齐后再上全量数据。投资实证记录的最大敌人是脏数据,不是代码复杂。
第二,保留一套最小可运行配置。把stock_list.csv、数据库建表语句、指标计算脚本固定下来,每次更新只改数据文件,不轻易改计算逻辑。
第三,交易日志要覆盖操作原因。买大唐和神华时,原因写得越具体越好,例如“中报后组合股息率低于 3.5%,卖出部分高波动成长股,买入高股息标的做再平衡”。这种原因在三个月后回看仍然有意义。
第四,批量任务必须加日志和失败重试。哪怕只是个人项目,也要把每次运行结果留档,否则定时任务半夜失败,第二天早上只能看到一堆旧数据。
第五,接口服务要限制访问范围。如果开了 Flask 接口,绑定127.0.0.1即可,不要默认开放到0.0.0.0。数据库文件也不要放在公开目录。
第六,涉及人脸、声音、版权素材的合规提醒在这里不适用,但涉及持仓数据、账户信息和第三方数据源版权时,约束反而是最紧的。不要把原始行情数据打包发布,不要暴露个人交易明细,不要在未授权情况下代客操作。
第七,发布或商用前要做效果复核。如果这篇实证内容要对外发布,所有计算数字都要重新跑一遍,重点检查 A 股和港股市场代码、分红币种、复权方式是否一致。
10. 总结与下一步
DAY61 的实证记录里,真正值得复制的东西不是“买入大唐和神华”这个结论,而是把“腾讯中报 → 稳健持仓受影响 → 买入高股息资产”这个决策过程变成可计算的数据。先用 AKShare 批量拉行情和财务数据,再用 pandas 计算股息率、PB、PE、ROE,最后把买入原因写进 SQLite,这一整套流程在任何一次调仓中都能复用。
如果你准备照这个方向做,建议先验证三件事:第一,AKShare 当前版本的接口是否能正常返回腾讯、大唐、神华的数据;第二,本地calc_indicators.py能不能算出一张字段完整的指标表;第三,交易日志和持仓快照是否能稳定写入 SQLite。这三步通过后,就可以把 DAY61 这种实证日复盘做成每周固定任务了。
最容易踩的坑集中在接口版本和字段对齐。AKShare 接口不是一成不变的,遇到报错优先看官方更新;腾讯是港股,不要强行塞进 A 股接口里。财务字段要统一币种和复权口径,否则股息率算出来虚高或虚低,复盘结论就会出现偏差。
后续的扩展方向可以考虑:接入一致预期数据做业绩对比,增加收益率回测模块,或者用 Grafana 做持仓可视化面板。但核心依然是先保住数据的准确性和可追溯性。下一笔买入之前,先让自己的数据库能回答一个问题:这笔交易的理由,是数据证明过的,还是情绪驱动的。本文全部内容均为投资实证记录与数据处理方法分享,不构成投资建议。
