← 返回博客列表

AI+量化实战

【AI+量化实战 #04】研报预测能信吗?让AI当你的业绩预告审计员

2026年10月15日 07:57 · 麦蕊智数 MyData

系列进度:AI+量化实战 #04 / 共 12 篇

痛点开场

每到财报季,A股就进入"预告轰炸"模式。一家公司可能发好几条业绩预告——先是"预增",后来修正为"预降";或者预告说"净利润 10 亿到 12 亿",实际出来 8 亿。

你手动核对这些数字,需要: - 把预告文本里的数字抠出来; - 找到对应期间的实际财报; - 对比增速方向是否一致; - 检查预告类型(预增/预亏)和数字是否矛盾。

6 只股票就有上百条预告,手动核对根本来不及。能不能用 Python + AI 自动做这件事?

能。而且这恰恰是 LLM 最擅长的——不是预测股价,而是做一致性检查。

本文你将得到什么

  1. company_earnings_forecast 和 stock_income 接口的字段说明。
  2. 一个用正则从预告文本中提取净利润和增速的解析器。
  3. 一段已跑通的代码:批量拉预告 → 解析数字 → 交叉校验实际财报 → 生成审计报告。
  4. 一个 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("<你的证书>")  # 演示证书,请替换成你自己的正式证书
# 业绩预告
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

规则化审计能发现两类问题: 1. 利润偏离:实际利润落在预告区间外超过 10%; 2. 类型矛盾:预告类型(预增/预亏)与增速方向不一致。

四、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),使用规则化审计结果。

六、常见坑

  1. 正则覆盖率有限:部分旧预告文本格式不规范(如"大幅下降"无具体数字),解析成功率约 34%。可以补充更多正则模式,或直接用 LLM 做非结构化文本提取。
  2. 实际数据时间窗口短:stock_income 默认只返回最近 4 期,历史预告大多无法交叉校验。需要配合 stock_history 拉取更多历史数据,或调用其他财务接口。
  3. "减亏"不是"预增":亏损减少时增速为正,但预告类型是"减亏"而非"预增"。规则审计需要区分这种情况,否则会误报矛盾。
  4. 预告修正:同一报告期可能有多条预告(首次 + 修正),审计时需要按发布日期排序,只取最新一条。
  5. LLM 输出不稳定:即使给了 JSON 输出格式,LLM 仍可能返回非标准 JSON。生产环境需要做格式校验和重试。

七、从规则到 LLM:什么时候该升级

规则化审计 vs LLM 审计,不是非此即彼:

维度 规则化 LLM
速度 毫秒级 秒级
成本 零 按 token 计费
数字校验 精确 可能算错
语义矛盾 无法发现 擅长
隐含假设 无法发现 能推理
跨期连贯性 需手写规则 能理解

推荐方案:先用规则化跑全量(181 条预告秒级完成),筛出可疑记录,再用 LLM 做精审(只审 10 条可疑的,省 token)。

小结与下篇预告

今天我们搭了一条业绩预告审计流水线:

  • 用正则从预告文本中提取净利润和增速;
  • 用规则化检查发现利润偏离和类型矛盾;
  • 构建了 LLM 审计 Prompt 模板(5 项审计任务,缺 key 时自动 fallback);
  • 讨论了规则化与 LLM 的分工策略。

下篇(#05)进入财报季信息洪流:用 LLM 给业绩预告分类,并算事件窗口收益——把预告类型变成可回测的事件信号。


风险提示:本文仅供技术学习交流,不构成投资建议。业绩预告审计存在数据覆盖不足、解析误差和规则局限等风险,历史结果不代表未来收益。

标签: Python量化
QQ 客服 3826425416 咨询时请提供证书号或订单号,便于快速处理
咨询