
做投资实证的意义不在于每一笔交易都能被短期涨跌证明而在于每一笔交易的背后有没有可以反复打磨的决策逻辑。最近看到一份经济学教师投资实证 DAY61 的记录腾讯中报披露之后组合中原本偏稳健的持仓逻辑受到冲击最终选择买入大唐发电和中国神华。这看起来像一次普通调仓但拆开看里面有“中报事件冲击”“红利因子”“能源板块配置”“持仓逻辑校验”等多个可以量化的点。本文不讨论涨跌预测也不做个股推荐。重点是把这次调仓动作拆成一个数据分析问题用 Python 完成一套可复用的复盘流程先理解稳健派持仓逻辑是什么再量化腾讯中报造成的冲击然后用股息率、财务指标和事件窗口分析复盘大唐发电、中国神华在组合里扮演的角色。无论你是做量化分析还是单纯想用数据管理自己的投资决策都可以参考这套方法。1. 背景与核心概念1.1 什么是投资实证投资实证Investment Journal / Public Diary是一种公开记录投资决策、持仓变化、思考过程的方式。它和普通晒收益不同重点不在“赚了多少钱”而在“为什么买、为什么卖、什么时候发现逻辑错了、后续如何纠正”。经济学教师投资实证 DAY61意思是这个实证已经连续记录到第 61 天。第 61 天这个节点正好遇到腾讯中报披露所以记录里出现了比较明显的决策调整从原来的腾讯相关持仓逻辑转向大唐发电、中国神华这类高股息能源标的。对于数据分析者来说投资实证是一个很好的观察样本。因为它不只是数据还有上下文什么事件触发了调仓调仓背后的变量是什么持仓逻辑有没有被证伪。这些信息比单纯的价格数据更适合用来做复盘。1.2 稳健派持仓逻辑稳健派持仓逻辑通常指以低波动、低估值、高股息、稳定现金流为主要筛选标准的组合思路。相比追逐高景气成长股稳健派更关心的是企业能不能持续赚钱企业愿不愿意把利润分给股东估值是否处于合理偏低区间行业格局是否稳定不容易被技术变革颠覆。在这种逻辑下公用事业、能源、银行、交运等行业是常见的配置方向。因为这些行业商业模式相对成熟资本开支进入稳定期后自由现金流能支撑较高的分红比例。中国神华属于煤炭能源龙头大唐发电属于电力企业。这两类公司都有比较明显的“类债券”属性业绩弹性可能不如科技股但分红稳定下行风险相对可控。因此当腾讯中报动摇了稳健持仓逻辑后组合转向大唐发电和中国神华在风格上是自洽的。1.3 腾讯中报为什么能冲击逻辑腾讯中报冲击稳健派持仓逻辑可能不是指腾讯的基本面变差而是指“腾讯不再是原来的腾讯”。如果中报显示利润增长较快但资本开支、AI 投入、或者回购分红节奏发生变化那么投资者原先对自由现金流的估计就要修正。对于稳健派来说一家公司即使现在盈利但未来资金是被大量消耗还是能持续分给股东会直接影响持股逻辑。腾讯的商业模式偏平台经济增长和投入之间需要动态平衡。如果中报体现出“成长性还在但稳健现金流预期变弱”那么偏稳健的持仓者就不得不重新评估留在组合里的理由是成长还是分红如果是成长那就不是稳健派逻辑如果是分红当期股息率可能不够有吸引力。所以中报冲击的本质不是股价波动而是“持仓理由被数据挑战”。这恰恰是投资实证最有价值的地方。2. 案例背景Day61 的动作拆解2.1 一次调仓的三层含义从公开记录看这次调仓是腾讯中报之后买入大唐发电和中国神华。市场参与者很容易只看“买了什么”但做数据分析更值得关注的是三层结构事件层腾讯中报披露什么数据和预期不一致。决策层稳健派逻辑被冲击后是清仓离场还是调仓换股还是降低仓位。执行层为什么是这两只股票而不是其他红利股。如果只看到第三层会以为这是一次简单的“追高股息”操作。但结合前两层看这更像是组合在面临不确定性时把估值和现金流作为新的锚点。2.2 大唐发电与中国神华的共性大唐发电A 股代码 601991和中国神华A 股代码 601088属于不同细分行业但放在同一张表格里对比它们的共性非常明显行业偏公用事业和能源资源业务模式相对容易理解利润波动受煤价、电价、利用小时数影响但长期现金流可视度高于很多科技公司分红能力较强是 A 股红利策略中经常出现的标的。用数据语言描述就是它们更容易满足“高股息、低波动、低估值”的筛选条件。所以在稳健派持仓逻辑受到冲击时买入这类标的本质上是降低组合对单一成长叙事的依赖把收益来源切换成更分散的分红现金流。2.3 腾讯在事件中的角色腾讯是港股上市公司代码 00700.HK。由于港股和 A 股的数据接口、交易规则、财务披露规则不同我们在做量化复盘时不一定能用一个接口同时拿到所有标的的完全一致数据。更合理的做法是把腾讯作为“事件对象”来分析把大唐发电和中国神华作为“应对动作”来分析。这样拆开之后分析目标就变得清晰了腾讯部分中报发布前后股价和波动率是否发生变化大唐和神华部分它们在红利因子上的表现是否真的优于组合中其他候选标的。这样处理既不会因为数据口径不一致导致分析失真也能把复盘流程模块化。3. 环境准备与数据源说明3.1 分析工具本文以 Python 作为主要分析工具核心库如下库名用途pandas数据处理与表格计算numpy数学计算、百分位排序akshare获取 A 股历史行情、分红财务数据matplotlib可选用于绘制收益曲线和事件窗口图如果你之前没有安装 akshare可以执行pip install akshare pandas numpy matplotlib需要注意akshare 的接口更新速度比较快不同版本的返回字段可能不一致。如果你运行代码后发现列名对不上先打印df.columns查看实际字段再调整代码中的列名映射。3.2 数据源说明本文演示代码中大唐发电、中国神华使用 A 股行情接口腾讯作为港股不做复杂接口对接事件分析中使用可复现的演示数据结构分红数据以“最近四季度每股分红合计”作为示意你需要根据实际财务披露替换。这里的核心原则是不要假设外部接口永远不变也不要假设演示数据等于真实行情。所有涉及具体数字的地方都只是用来展示分析方法。3.3 建议项目结构为了让复盘过程可重复建议把代码和配置分开investment-review/ ├── data/ │ ├── raw/ # 原始行情快照 │ └── processed/ # 清洗后的数据 ├── config.py # 股票池、日期、参数 ├── fetch_data.py # 数据下载 ├── indicators.py # 股息率、财务指标计算 ├── event_study.py # 事件窗口分析 └── review_day61.ipynb # 复盘主文件这种结构的好处是下次想回溯 Day61 的结论时可以看到当时用的数据、参数和代码版本而不是靠记忆还原。4. 股息率计算与红利因子分析4.1 获取 A 股行情数据先以大唐发电为例获取历史日线行情。下面的代码会打印 DataFrame 的列名和前几行数据方便你确认当前版本 akshare 的字段结构。import akshare as ak import pandas as pd # 大唐发电 A 股代码 601991中国神华 A 股代码 601088 # perioddaily 表示日线adjustqfq 表示前复权 df ak.stock_zh_a_hist( symbol601991, perioddaily, start_date20240101, end_date20241231, adjustqfq ) print(df.columns) print(df.head())如果是在本地运行输出的列名通常是中文比如“日期”“开盘”“收盘”“最高”“最低”“成交量”“成交额”“振幅”“涨跌幅”“涨跌额”“换手率”。建议先做一次列名清洗统一成英文字段方便后续计算df df.rename(columns{ 日期: trade_date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 成交额: amount, 涨跌幅: pct_chg, 换手率: turnover }) df[trade_date] pd.to_datetime(df[trade_date]) df df.sort_values(trade_date).reset_index(dropTrue) print(df.tail())这里把trade_date转成 pandas 的时间格式并把数据按日期升序排列。后面计算收益时顺序非常重要一旦排序出错pct_change()算出来的涨跌幅就是错的。4.2 分红数据与 TTM 股息率股息率的常见公式是股息率 每股分红 / 当前股价为了更贴近稳健派逻辑可以使用 TTM 股息率也就是用最近四个季度的每股分红合计作为分子。这样能把分红数据放到同一个时间轴上比较。由于分红数据接口在不同数据源中差异较大这里不写死某个接口而是用 DataFrame 演示计算过程。你需要把“股票代码、每股分红、报告期”三列数据结构化再按年度或季度聚合。# 演示数据非真实分红仅用于展示计算逻辑 dividend_demo pd.DataFrame({ symbol: [601991, 601991, 601991, 601991], report_date: [2023-12-31, 2024-03-31, 2024-06-30, 2024-09-30], dps: [0.10, 0.05, 0.06, 0.05] # 每股股利单位元 }) # 按股票代码计算最近四期累计分红 dps_ttm dividend_demo.groupby(symbol)[dps].sum().reset_index() dps_ttm.columns [symbol, dps_ttm] # 假设当前股价实际应从行情接口获取 price_demo pd.DataFrame({ symbol: [601991, 601088], close: [4.50, 39.80] # 演示值 }) merged pd.merge(price_demo, dps_ttm, onsymbol, howleft) merged[dividend_yield] merged[dps_ttm] / merged[close] print(merged)需要注意如果计算历史股息率应该使用对应历史日期的不复权收盘价而不是前复权价格。因为前复权会把历史价格调整导致股息率失真。上面的代码只是静态快照实操中最好把close替换为“当前价”或“计算日收盘价”。4.3 红利策略筛选逻辑在拿到全市场或股票池数据后红利策略的筛选通常包括以下几步剔除 ST、*ST 和退市整理期股票剔除上市时间不足一年的次新股剔除日均成交额过低、流动性不足的股票按股息率从高到低排序根据行业分散度、波动率、股息率阈值最终选择标的。下面是一个简化的筛选代码示例# stock_pool 为 DataFrame字段包括 symbol, close, dps_ttm, turnover, name # 市值、成交额等字段可根据接口补充 stock_pool pd.DataFrame({ symbol: [601991, 601088, 00700], # 腾讯仅作结构演示 name: [大唐发电, 中国神华, 腾讯控股], close: [4.50, 39.80, 380.00], dps_ttm: [0.26, 2.55, 0.80], turnover: [2.1, 1.5, 0.8], list_days: [5000, 6000, 7000] }) filtered stock_pool[ (~stock_pool[name].str.contains(ST, naFalse)) # 剔除 ST (stock_pool[list_days] 365) # 上市满一年 (stock_pool[turnover] 1.0) # 流动性过滤 ].copy() filtered[dividend_yield] filtered[dps_ttm] / filtered[close] filtered filtered.sort_values(dividend_yield, ascendingFalse) print(filtered[[symbol, name, close, dps_ttm, dividend_yield]])在这个演示数据里腾讯因为流动性过滤条件不同可能不会进入高股息候选池。这不代表腾讯不好而是说明不同逻辑对应不同筛选规则。稳健派调仓时真正想买的是“股息率足够高、经营足够稳”的资产而不是简单的低买高卖。5. 事件冲击量化中报前后波动与收益5.1 事件窗口分析是什么事件冲击不能只靠感觉最好用事件研究法Event Study量化。基本思路是确定事件日腾讯中报披露日确定事件窗口比如事件日前 5 个交易日到后 5 个交易日计算窗口内的累计收益率、日均波动率与更长时间窗口的基准做对比观察是否存在异常波动。这个方法非常适合复盘“中报冲击”。因为中报披露日是一个明确的日期前后收益率和波动率变化可以在一定程度上反映市场对信息的解读。5.2 事件窗口收益计算代码下面用简化数据结构演示。由于腾讯是港股你实际使用时可能需要使用港股数据接口或者从自己的行情数据库导入。关键是只要 DataFrame 里有trade_date和close两列逻辑是通用的。def build_event_window(df, event_date, before5, after5): df: DataFrame必须包含 trade_date 和 close event_date: 事件日期字符串例如 2024-08-14 df df.copy() df[trade_date] pd.to_datetime(df[trade_date]) df df.sort_values(trade_date).reset_index(dropTrue) # 计算日收益率 df[return] df[close].pct_change().fillna(0) # 定位事件日索引 event_positions df.index[df[trade_date] pd.Timestamp(event_date)] if len(event_positions) 0: raise ValueError(事件日不在数据范围中请检查日期是否为交易日) event_idx event_positions[0] start max(0, event_idx - before) end min(len(df), event_idx after 1) window_df df.iloc[start:end].copy() window_df[cum_return] (1 window_df[return]).cumprod() - 1 return window_df # 构造演示日线数据日期从 2024-07-01 到 2024-09-01 trade_calendar pd.date_range(2024-07-01, 2024-09-01, freqB) # 工作日近似交易日 demo_close pd.Series(range(100, 100 len(trade_calendar)), dtypefloat) # 手动加入一些价格波动模拟中报前后变化 for i in range(5, 20): demo_close.iloc[i] i * 0.5 demo_df pd.DataFrame({ trade_date: trade_calendar, close: demo_close }) event_df build_event_window(demo_df, event_date2024-08-14, before5, after5) print(event_df[[trade_date, close, return, cum_return]])这段代码的三个关键点pct_change()会得到每个交易日的相对昨日收益率cumprod()将日收益率复利累乘得到窗口累计收益率如果事件日是非交易日需要向前找最近交易日否则会报错。5.3 如何解读事件冲击结果如果事件窗口的累计收益率明显大于或小于前后一段时间的平均收益说明中报披露可能带来了增量信息。但这不等于基本面变差也可能是一次“预期差”修正。回到 Day61 的案例中报冲击不一定体现在当天大跌更多体现在“波动率放大”和“后续资金流向变化”。稳健派看到的是原本基于稳定现金流假设的估值逻辑可能要被重新定价。因此事件冲击分析的结果应该结合两个维度看收益维度窗口期是否出现显著上涨或下跌波动维度窗口期日均振幅是否明显高于事件前 20 日。如果波动率明显放大无论涨跌对稳健派来说都是组合风险的增加。这可能就是“冲击”的真实含义。6. 从腾讯到电煤持仓逻辑的数据校验6.1 基础财务指标对比投资实证里经常提到“逻辑自洽”但逻辑是否自洽不能只看叙事还要看指标。下面是一张比较常见的稳健持仓对比表你可以根据实际财报披露数据填充。指标腾讯控股港股中国神华大唐发电行业平台科技煤炭能源电力公用ROE 水平较高但波动明显相对稳定受电价和煤价影响自由现金流取决于资本开支成熟期现金流较强有资本开支压力分红潜力中等较高中等偏上估值波动大小小需要注意这张表不是精确财务结论而是提供一个对比维度。实际分析时应该把ROE、资产负债率、经营性现金流净额、资本开支、每股分红、股息率、PE/PB这几个字段统一口径做量化比较。6.2 用现金流与分红指标做比较如果你是稳健派最应该关注的不是利润增速而是“分红是否由现金流支撑”。一个常见的筛选指标是分红保障倍数 每股经营性现金流净额 / 每股分红这个倍数大于 1说明分红有现金流支撑小于 1说明分红可能依赖借款或消耗存量资金。继续沿用演示数据cashflow_demo pd.DataFrame({ symbol: [601991, 601088], ocf_per_share: [0.60, 5.20], # 每股经营性现金流演示值 dps_ttm: [0.26, 2.55] # 每股分红演示值 }) cashflow_demo[payout_coverage] cashflow_demo[ocf_per_share] / cashflow_demo[dps_ttm] print(cashflow_demo)逻辑很简单payout_coverage越高分红越有底气。反过来如果一家公司股息率很高但现金流远远覆盖不足那高分红可能不可持续。这也是稳健派在中报季最关心的风险之一。6.3 组合层面的分散度评估买入大唐发电和中国神华除了个股逻辑外还有组合分散度考量。可以把持仓看作一个简化的风险因子暴露组合腾讯平台经济、互联网流量、AI 资本开支大唐发电电力价格、煤炭成本、新能源转型中国神华煤价周期、长协比例、分红率。这三个标的的行业风险来源并不同。当中报季出现市场对科技成长股重新定价时能源公用板块往往受影响较小。这种“低相关”属性才是稳健派调仓最重要的原因。数据上可以用历史收益率的相关系数矩阵来验证corr_data pd.DataFrame({ date: pd.date_range(2024-01-01, 2024-06-30, freqB), }) import numpy as np np.random.seed(42) corr_data[tencent_ret] np.random.normal(0.001, 0.02, len(corr_data)) corr_data[datang_ret] np.random.normal(0.0005, 0.01, len(corr_data)) corr_data[shenhua_ret] np.random.normal(0.0008, 0.012, len(corr_data)) print(corr_data[[tencent_ret, datang_ret, shenhua_ret]].corr())如果计算出来的相关系数比较低说明组合确实起到了分散效果。需要注意的是这是用模拟数据算的真实分析时应使用实际行情并处理时间戳对齐的问题。7. 常见问题与排查思路在实际复盘中很多人会遇到下面这些坑。这里整理成一张排查表方便直接对照。问题现象可能原因解决思路akshare 导入或调用失败版本过旧或接口更新升级 akshare查看官方文档打印实际字段收益率为 NA数据没有按日期排序对日期升序排序后再计算pct_change()股息率明显异常分红数据日期、复权价格口径错误明确最近四季度分红使用不复权价格事件日找不到当日停牌或非交易日向前找最近交易日或判断事件窗口日期港股和 A 股无法统一分析不同市场数据接口不同分别处理拆分事件分析和持仓分析相关系数矩阵有缺失日期没有对齐使用merge或join先统一交易日历这些问题的共同根源是数据口径不一致。所以我在实际复盘中会先记录一份“数据口径说明”包括数据源、接口版本、复权方式、报告期、交易日历等。这样过了一个月再回看依然知道当初算的是什么。8. 投资实证复盘的工程化最佳实践8.1 每次复盘都要留参数快照投资实证 DAY61 的价值不只是第 61 天发生了什么而是第 1 天到第 61 天之间决策和参数是如何演变的。建议在代码里把参数存成 JSON 或 YAML 文件event_date: 2024-08-14 stock_pool: - 601991 - 601088 - 00700.HK dividend_ttm_periods: 4 risk_free_rate: 0.02 data_source: akshare_demo comment: 腾讯中报冲击稳健派调仓复盘这样每次运行代码前先读取配置文件。如果某一天想复现 Day61 的结果直接使用同一份配置即可。8.2 不要只看收益还要记录“为什么”投资实证中人最容易犯的错是“赚钱时总结太多亏钱时无话可说”。更客观的做法是写一张操作日志记录持仓变动前市场发生了什么这次变动基于哪条因子逻辑哪些数据支持哪些数据不支持如果后续出现什么信号说明逻辑错了。把操作日志和代码放在一起比单独写“感悟”有用得多。因为代码描述“怎么做”日志描述“为什么”两者结合才是完整复盘。8.3 模拟盘和真实仓位分离如果你刚开始做红利策略或事件驱动分析建议先在模拟盘上跑完整流程。Day61 的实证可以同时记录“真实持仓”和“模拟策略”但两者不要混在一起。真实仓位需要考虑滑点、手续费、停牌、涨跌停限制等模拟盘更容易验证因子本身是否有效。分离之后你才能知道某次调仓到底贡献了多少收益还是只是运气。8.4 安全与合规提醒不管是分析腾讯中报还是买入大唐发电、中国神华都要注意以下几点所有数据来源应合法、可追溯不未经授权使用付费数据接口不把个人投资实证分析包装成投资建议不通过代码自动下单时必须有严格的风控参数涉及真实交易操作时务必先小仓位测试设置止损和仓位上限。投资实证是个人记录不是荐股工具。数据分析能提高决策质量但不能消除风险。9. 风险提示与最后的话腾讯中报、大唐发电、中国神华这些标的本身的名字并不重要重要的是在每一次冲击面前持仓逻辑是否能被数据解释。稳健派调仓不是“看到涨了就买”而是先确认原来的逻辑是否还成立再寻找替代资产。本文的代码和分析框架可以直接复制到本地替换成真实行情和财务数据后就能复现类似 Day61 的复盘流程。如果你刚开始做投资实证不用急着搭很复杂的系统先从一张持仓表、一份数据快照、一段决策日志开始。下一回再遇到某家公司财报披露时试着用事件窗口分析、股息率对比和现金流指标把自己从“感觉受影响”变成“知道受什么影响”。投资实证的长期价值就在这些看似笨拙的复盘细节里。继续保持记录继续用数据检验自己的判断比短期涨跌更有意义。