【AI+量化实战 #04】研报预测能信吗?让AI当你的业绩预告审计员
系列进度:AI+量化实战 #04 / 共 12 篇
痛点开场
每到财报季,A股就进入"预告轰炸"模式。一家公司可能发好几条业绩预告——先是"预增",后来修正为"预降";或者预告说"净利润 10 亿到 12 亿",实际出来 8 亿。
你手动核对这些数字,需要:
- 把预告文本里的数字抠出来;
- 找到对应期间的实际财报;
- 对比增速方向是否一致;
- 检查预告类型(预增/预亏)和数字是否矛盾。
6 只股票就有上百条预告,手动核对根本来不及。能不能用 Python + AI 自动做这件事?
能。而且这恰恰是 LLM 最擅长的——不是预测股价,而是做一致性检查。
本文你将得到什么
- company_earnings_forecast 和 stock_income 接口的字段说明。
- 一个用正则从预告文本中提取净利润和增速的解析器。
- 一段已跑通的代码:批量拉预告 → 解析数字 → 交叉校验实际财报 → 生成审计报告。
- 一个 LLM 审计 Prompt 模板(含 5 项审计任务,缺 key 时自动 fallback)。
一、数据从哪来
业绩预告:company_earnings_forecast
字段 含义
abs 预告正文(含预测利润和增速)
type 预告类型(预增/预升/预亏/减亏等)
pdate 预告发布日期
rdate 报告期(如 2024-12-31)
old 上年同期 EPS
预告正文 abs 是关键,它通常长这样:
预计2024年1-12月归属于上市公司股东的净利润为:857亿元,与上年同期相比变动幅度:14.67%。
实际财报:stock_income
字段 含义
jlr 净利润(元)
yysr 营业收入(元)
jzrq 报告截止日期
plrq 披露日期
调用示例:
import mairui
import os
api = mairui.Client("LICENCE-66D8-9F96-0C7F0FBCD073") # 演示证书,演示证书(读者复制即用,无需替换)
# 业绩预告
forecasts = api.company_earnings_forecast("600519")
for f in forecasts[:3]:
print(f["rdate"], f["type"], f["abs"][:60])
实际利润
income = api.stock_income("600519", lt=4)
for i in income:
print(i["jzrq"], round(float(i["jlr"]) / 1e8, 2), "亿元")
二、用正则把数字从文本里抠出来
预告文本是半结构化的——有固定句式,但数字格式多变(单值/区间、亿元/万元)。用正则可以覆盖大部分情况:
import re
_PROFIT_RE = re.compile(
r"净利润为[::]\s([-]?\d+(?:.\d+)?)\s(亿元|万元)"
r"(?:至([-]?\d+(?:.\d+)?)\s(亿元|万元))?"
)
_GROWTH_RE = re.compile(
r"(?:变动幅度|增长|增幅)[::]\s([-]?\d+(?:.\d+)?)\s%"
r"(?:至([-]?\d+(?:.\d+)?)\s%)?"
)
def parse_forecast(abs_text: str) -> dict:
result = {"profit_low": None, "profit_high": None,
"growth_low": None, "growth_high": None}
if not abs_text:
return result
m = _PROFIT_RE.search(abs_text)
if m:
low_val = float(m.group(1))
low_unit = m.group(2)
result["profit_low"] = low_val * (10000 if low_unit == "亿元" else 1)
if m.group(3):
high_val = float(m.group(3))
high_unit = m.group(4)
result["profit_high"] = high_val * (10000 if high_unit == "亿元" else 1)
else:
result["profit_high"] = result["profit_low"]
m = _GROWTH_RE.search(abs_text)
if m:
result["growth_low"] = float(m.group(1))
result["growth_high"] = float(m.group(2)) if m.group(2) else result["growth_low"]
return result
这个解析器能处理:
- 单值:净利润为:857亿元 → profit_low=profit_high=8570000万
- 区间:净利润为:850000万元至1000000万元 → profit_low=850000, profit_high=1000000
- 亏损:净利润为:-34亿元至-38亿元 → profit_low=-380000, profit_high=-340000
三、规则化审计:先跑通,再交给 AI
有了结构化数据,就可以做规则检查:
def audit_forecasts(forecast_df, actual_df):
audits = []
for _, f in forecast_df.iterrows():
if not f["profit_low_wan"]:
continue
# 找同期实际利润
rdate = f["rdate"]
actual = actual_df[
(actual_df["code"] == f["code"]) &
(actual_df["report_date"].str.startswith(rdate[:4] if rdate else ""))
]
issues = []
if len(actual) > 0:
actual_profit = actual.iloc[0]["net_profit_wan"]
f_low = f["profit_low_wan"]
f_high = f["profit_high_wan"] or f_low
# 检查实际利润是否在预告区间内
if actual_profit < f_low * 0.9 or actual_profit > f_high * 1.1:
issues.append(
f"实际利润 {actual_profit:.0f}万 偏离预告区间 "
f"[{f_low:.0f}, {f_high:.0f}]万 超过10%")
else:
issues.append(
f"实际利润 {actual_profit:.0f}万 在预告区间内")
else:
issues.append("无同期实际数据,无法校验")
检查增速方向与预告类型是否矛盾
if f["growth_low_pct"] is not None:
g = f["growth_low_pct"]
if f["type"] == "预亏" and g > 0:
issues.append(f"预告类型'预亏'但增速为正({g}%),存在矛盾")
elif f["type"] == "预增" and g < 0:
issues.append(f"预告类型'预增'但增速为负({g}%),存在矛盾")
audits.append({
"code": f["code"], "name": f["name"],
"rdate": rdate, "type": f["type"],
"audit_result": "; ".join(issues),
})
return audits
规则化审计能发现两类问题:
- 利润偏离:实际利润落在预告区间外超过 10%;
- 类型矛盾:预告类型(预增/预亏)与增速方向不一致。
四、LLM 审计 Prompt:让 AI 做更深层的检查
规则检查只能发现数字层面的矛盾。LLM 能做的更多——理解隐含假设、跨期连贯性、语义矛盾。
def build_llm_prompt(stock_name, forecasts, actuals):
prompt = f"""你是一位严谨的金融分析师审计员。请审计以下 {stock_name} 的业绩预告一致性。
业绩预告数据
"""
for _, f in forecasts.iterrows():
prompt += f"\n- {f['rdate']} | 类型:{f['type']} | 预告文本:{f['abs_text']}"
prompt += "\n\n## 实际财报数据\n"
for _, a in actuals.iterrows():
prompt += f"\n- {a['report_date']} | 净利润:{a['net_profit_wan']:.0f}万 | 营收:{a['revenue_wan']:.0f}万"
prompt += """
审计任务
1. 预告增速与实际增速是否一致?偏差超过 10% 请标红。
2. 预告利润区间是否包含实际利润?不在区间内请标红。
3. 预告类型(预增/预升/预亏)与数字方向是否矛盾?
4. 是否存在隐含假设(如"上年同期"基数是否被调整过)?
5. 跨期预告是否连贯(如 Q1 预告 vs 半年报预告的增速是否递进)?
输出格式
JSON: [{"issue": "...", "severity": "high/medium/low", "explanation": "..."}]
"""
return prompt
LLM 调用设计为可选:优先读环境变量 LLM_API_KEY 和 LLM_BASE_URL,缺失时自动 fallback 到规则化结果。这样脚本在任何环境下都能跑通。
五、真实运行结果
以 6 只股票为例,共拉取 181 条业绩预告:
股票 预告数 成功解析利润 典型预告
贵州茅台 21 18 2024年报: 预计净利润 857 亿元
五粮液 16 12 2026半年报: 预计 87-92 亿元,增 89-99%
比亚迪 61 28 2025Q1: 预计 85-100 亿元,增 86-119%
隆基绿能 45 15 2026半年报: 预计亏损 34-38 亿元
招商银行 32 8 多条旧格式无数字
美的集团 6 3 预告数量较少
规则化审计发现 4 条偏离问题(实际利润落在预告区间外超过 10%)。大部分历史预告因实际财报数据仅含最近 4 期,无法交叉校验。
LLM 审计 Prompt 以比亚迪为例生成,包含 3 条预告 + 2 期实际数据 + 5 项审计任务,共 702 字符。LLM 调用走 fallback 模式(未配置 API key),使用规则化审计结果。
六、常见坑
- 正则覆盖率有限:部分旧预告文本格式不规范(如"大幅下降"无具体数字),解析成功率约 34%。可以补充更多正则模式,或直接用 LLM 做非结构化文本提取。
- 实际数据时间窗口短:stock_income 默认只返回最近 4 期,历史预告大多无法交叉校验。需要配合 stock_history 拉取更多历史数据,或调用其他财务接口。
- "减亏"不是"预增":亏损减少时增速为正,但预告类型是"减亏"而非"预增"。规则审计需要区分这种情况,否则会误报矛盾。
- 预告修正:同一报告期可能有多条预告(首次 + 修正),审计时需要按发布日期排序,只取最新一条。
- LLM 输出不稳定:即使给了 JSON 输出格式,LLM 仍可能返回非标准 JSON。生产环境需要做格式校验和重试。
七、从规则到 LLM:什么时候该升级
规则化审计 vs LLM 审计,不是非此即彼:
维度 规则化 LLM
速度 毫秒级 秒级
成本 零 按 token 计费
数字校验 精确 可能算错
语义矛盾 无法发现 擅长
隐含假设 无法发现 能推理
跨期连贯性 需手写规则 能理解
推荐方案:先用规则化跑全量(181 条预告秒级完成),筛出可疑记录,再用 LLM 做精审(只审 10 条可疑的,省 token)。
小结与下篇预告
今天我们搭了一条业绩预告审计流水线:
- 用正则从预告文本中提取净利润和增速;
- 用规则化检查发现利润偏离和类型矛盾;
- 构建了 LLM 审计 Prompt 模板(5 项审计任务,缺 key 时自动 fallback);
- 讨论了规则化与 LLM 的分工策略。
下篇(#05)进入财报季信息洪流:用 LLM 给业绩预告分类,并算事件窗口收益——把预告类型变成可回测的事件信号。
风险提示:本文仅供技术学习交流,不构成投资建议。业绩预告审计存在数据覆盖不足、解析误差和规则局限等风险,历史结果不代表未来收益。
API 文档与证书获取:https://github.com/MaiRuiApi

浙公网安备 33010602011771号