因子投资-实验08-完整A股因子研究报告

QUANT RESEARCH NOTE LAB 08

用公开在文内的 6 股票、5 月末小样本完成一份最小研究报告:研究问题、字段字典、历史股票池、EP 信号、未来收益、IC、三分组 H-L、成本、子样本、失败条件和三态结论。它是工程模板,不是 A 股实证结论。

类型动手实验
模块研究实验
阅读约 13 分钟
材料9 节 · 2 段代码

问题

用公开在文内的 6 股票、5 月末小样本完成一份最小研究报告:研究问题、字段字典、历史股票池、EP 信号、未来收益、IC、三分组 H-L、成本、子样本、失败条件和三态结论。它是工程模板,不是 A 股实证结论。

研究问题:形成日已公告的 EP 越高,下一月收益是否更高,扣除保守线性成本后是否仍成立?

输入数据

真实数据接口固定如下,后续可替换数据源但不得改变字段含义:

PYTHON 可复现实验
1
2
3
4
# kb-skip
prices = pd.DataFrame(columns=["date", "asset", "close", "adj_factor", "volume", "amount"])
fundamentals = pd.DataFrame(columns=["report_period", "announce_date", "asset", "ep", "roe", "asset_growth"])
universe = pd.DataFrame(columns=["date", "asset", "eligible", "industry", "market_cap"])

字段字典:close 为未复权元/股,adj_factor 为复权因子,volume 为股,amount 为元;announce_date 是首次公告日;EP、ROE、资产增长均为小数;eligible 是当日研究股票池;market_cap 为形成日元。

逐步实现

  1. 生成点时三表并检查唯一键。
  2. 用复权价计算下一月简单收益。
  3. 只连接 announce_date≤signal_date 的最新财报。
  4. 在历史 eligible 股票池内按 EP 三分组。
  5. 每月计算 Spearman IC 与 High-Low。
  6. 从毛 H-L 扣保守固定线性成本。
  7. 比较前后两个子样本。
  8. 按预注册规则输出“因子支持、证伪或证据不足”。

完整代码

PYTHON 可复现实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import numpy as np
import pandas as pd

prices = pd.DataFrame(columns=["date", "asset", "close", "adj_factor", "volume", "amount"])
fundamentals = pd.DataFrame(columns=["report_period", "announce_date", "asset", "ep", "roe", "asset_growth"])
universe = pd.DataFrame(columns=["date", "asset", "eligible", "industry", "market_cap"])

assets = list("ABCDEF")
dates = pd.date_range("2026-01-31", periods=5, freq="ME")
ep_values = np.array([0.03, 0.04, 0.05, 0.06, 0.07, 0.08])

fundamentals = pd.DataFrame({
"report_period": pd.to_datetime(["2025-12-31"] * 6),
"announce_date": pd.to_datetime(["2026-01-15"] * 6),
"asset": assets,
"ep": ep_values,
"roe": [0.08, 0.10, 0.12, 0.09, 0.15, 0.18],
"asset_growth": [0.10, 0.08, 0.06, 0.12, 0.04, 0.02],
})

universe_rows = []
for date in dates[:-1]:
for i, asset in enumerate(assets):
universe_rows.append({
"date": date,
"asset": asset,
"eligible": True,
"industry": "X" if i < 3 else "Y",
"market_cap": (100 + 20 * i) * 1_000_000,
})
universe = pd.DataFrame(universe_rows, columns=universe.columns)

z_ep = (ep_values - ep_values.mean()) / ep_values.std(ddof=0)
close = np.repeat(100.0, 6)
price_rows = []
for t, date in enumerate(dates):
for i, asset in enumerate(assets):
price_rows.append({
"date": date,
"asset": asset,
"close": close[i],
"adj_factor": 1.0,
"volume": 1_000_000 + 10_000 * i,
"amount": close[i] * (1_000_000 + 10_000 * i),
})
if t < len(dates) - 1:
next_return = 0.002 + 0.006 * z_ep + 0.001 * np.sin(t + np.arange(6))
close = close * (1 + next_return)
prices = pd.DataFrame(price_rows, columns=prices.columns)

assert not prices.duplicated(["date", "asset"]).any()
assert not fundamentals.duplicated(["report_period", "announce_date", "asset"]).any()
assert not universe.duplicated(["date", "asset"]).any()

prices["adjusted"] = prices["close"] * prices["adj_factor"]
prices = prices.sort_values(["asset", "date"])
prices["next_return"] = prices.groupby("asset")["adjusted"].pct_change()
prices["signal_date"] = prices.groupby("asset")["date"].shift(1)
returns = prices.dropna(subset=["signal_date"]).rename(columns={"date": "return_date"})

panel = universe.rename(columns={"date": "signal_date"})
panel = panel.loc[panel["eligible"]].merge(
returns[["signal_date", "return_date", "asset", "next_return"]],
on=["signal_date", "asset"],
validate="one_to_one",
)
panel = panel.merge(fundamentals, on="asset", validate="many_to_one")
panel = panel.loc[panel["announce_date"] <= panel["signal_date"]].copy()

assert (panel["announce_date"] <= panel["signal_date"]).all()
assert (panel["return_date"] > panel["signal_date"]).all()

panel["group"] = panel.groupby("signal_date")["ep"].transform(
lambda x: pd.qcut(x.rank(method="first"), 3, labels=False)
)
ic = panel.groupby("signal_date").apply(
lambda x: x["ep"].corr(x["next_return"], method="spearman"),
include_groups=False,
)
group_return = panel.groupby(
["signal_date", "group"], observed=True
)["next_return"].mean().unstack()
gross_hl = group_return[2] - group_return[0]

cost = pd.Series(0.0005, index=gross_hl.index)
net_hl = gross_hl - cost
assert (net_hl <= gross_hl + 1e-12).all()

split = len(net_hl) // 2
subsamples = {
"first_half": float(net_hl.iloc[:split].mean()),
"second_half": float(net_hl.iloc[split:].mean()),
}
mean_ic = float(ic.mean())
mean_net = float(net_hl.mean())
if mean_ic > 0 and mean_net > 0 and min(subsamples.values()) > 0:
conclusion = "因子支持"
elif mean_ic <= 0 and mean_net <= 0:
conclusion = "因子证伪"
else:
conclusion = "证据不足"

report = {
"question": "点时 EP 是否正向预测下一月净收益",
"rows": len(panel),
"coverage": len(panel) / len(universe),
"mean_rank_ic": mean_ic,
"mean_gross_hl": float(gross_hl.mean()),
"mean_net_hl": mean_net,
"subsamples": subsamples,
"failure_conditions": [
"公告时点不合格",
"任一子样本净收益非正",
"成本或微盘排除后结论消失",
],
"conclusion": conclusion,
}
print(report)

预期输出

唯一键、公告时点、收益滞后、净收益≤毛收益四组断言通过。人工数据把较高 EP 与较高未来收益关联,因此通常输出“因子支持”。这只说明报告管线能识别嵌入关系;把生成系数改为零或负时,结论必须能变为“证据不足”或“因子证伪”。

断言

  1. prices 的 date、asset,fundamentals 的报告/公告/asset,universe 的 date、asset 唯一。
  2. 所用公告不晚于 signal_date。
  3. return_date 严格晚于 signal_date。
  4. 每月净 H-L 不高于毛 H-L。

真实报告还要检查历史 ST/停牌/涨跌停、退市收益、复权事件、组非空、权重、换手、成本和容量。

结果解释

报告同时保留问题、行数、覆盖、IC、毛/净收益、子样本、失败条件和结论。任何一个数字都不能单独证明因子。三态结论避免“没有显著支持”被误写成“证明无效”,也避免教学代码预设成功。

真实数据替换时,fundamentals 必须保留多公告版本并做 as-of 选择;本小样本每只股票只有一个已公告版本,所以 many_to_one 足够。

常见故障

  • pct_change 排序不按 asset、date。
  • shift 方向反了,使收益早于信号。
  • merge 后重复行扩大样本。
  • 财报直接按报告期连接。
  • 只报告毛收益和全样本。
  • 结论逻辑硬编码为成功。
  • 字段单位和供应商版本未记录。

练习

  1. 将 EP 生成系数设为零和负,验证三态结论。
  2. 加入第二版财报并实现 announce_date as-of。
  3. 排除最小 30% 市值,复现 CH-3 风格股票池。
  4. 用真实历史制度表加入 ST、停牌和涨跌停。
  5. 用换手替代固定成本,并做容量情景。
  6. 把 report 字典导出为包含参数、环境和数据哈希的可复现报告。

因子投资-实验08-完整A股因子研究报告
https://www.polarishaw.top/2026/07/17/因子投资-实验08-完整A股因子研究报告/
作者
Polaris
发布于
2026年7月17日
许可协议