AI+金融:智能投研系统设计

灵阙学院 | 行业 AI 系列


引言:研究员的信息战争

周一早晨 8 点,某券商研究所的分析师小李打开电脑。等待他的是:28 家覆盖公司的周末公告、147 条财经新闻、3 份行业报告、以及刚出炉的 PMI 数据。市场 9:30 开盘,他需要在 90 分钟内消化这些信息,判断哪些会影响覆盖标的,并在团队晨会上给出观点。

他不可能全部读完。于是他凭经验扫标题、挑重点——但上个月他就因为漏看了一份供应商的监管函,错过了某公司的供应链风险信号。

这就是智能投研系统要解决的问题:不是替代分析师的判断力,而是让他在信息采集和初筛环节不再"靠经验扫标题"。把人从重复性信息处理中解放出来,集中精力做真正需要深度思考的工作。

必须先说清楚一个前提:本文讨论的系统专注于投研辅助,不输出交易信号,不构成投资建议。这是监管合规的基本底线。


一、系统架构概览

┌──────────────────────────────────────────────────────────┐
│                   智能投研系统架构                         │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  数据采集层                                               │
│  +--------+ +--------+ +--------+ +--------+ +--------+ │
│  |A股公告 | |财经新闻| |研究报告| |宏观数据 | |社交媒体| │
│  |巨潮/上交| |RSS/爬虫| |PDF解析 | |Wind/iFind| |雪球/东财| │
│  +---+----+ +---+----+ +---+----+ +---+-----+ +---+----+ │
│      |          |          |          |            |      │
│      +----------+----------+----------+------------+      │
│                         |                                │
│  Kafka 实时分发          v                                │
│                                                          │
│  +------------------------------------------------------+│
│  |                NLP 处理层                              |│
│  | +----------+ +----------+ +----------+ +------------+ |│
│  | |文本分类  | |情感分析  | |事件抽取  | |知识图谱更新| |│
│  | |去重过滤  | |细粒度    | |结构化    | |关系网络    | |│
│  | +----------+ +----------+ +----------+ +------------+ |│
│  +-------------------------+----------------------------+ │
│                            |                              │
│  +-------------------------v----------------------------+ │
│  |              量化信号生成层                            | │
│  |  情感信号 -> 多因子合成 -> 信号强度 -> 回测验证       | │
│  +-------------------------+----------------------------+ │
│                            |                              │
│  +-------------------------v----------------------------+ │
│  |          投研工作台 (Research Workbench)               | │
│  |  个股报告  事件跟踪  竞对分析  行业图谱  预警推送     | │
│  +------------------------------------------------------+ │
└──────────────────────────────────────────────────────────┘

二、财经文本分类与优先级分层

投研场景的第一步是把海量信息按重要性快速分层。

2.1 文档分类与优先级

文档类型 重要性权重 延迟要求
监管公告(问询函/立案调查) 1.0 实时
股权变动(大额增减持/举牌) 0.95 实时
并购重组 0.90 < 5min
业绩报告/预告 0.85 < 5min
重大合同 0.80 < 10min
管理层变动 0.75 < 10min
宏观政策 0.60 < 30min
行业新闻 0.30 小时级

2.2 规则 + 模型混合分类器

"""
财经文档分类器:规则优先(快+准),模型兜底(覆盖长尾)
"""
from dataclasses import dataclass
from enum import Enum
import re


class DocCategory(Enum):
    REGULATORY = "regulatory_filing"
    EQUITY_CHANGE = "equity_change"
    MERGER = "merger_acquisition"
    EARNINGS = "earnings_report"
    CONTRACT = "major_contract"
    MANAGEMENT = "management_change"
    MACRO = "macro_policy"
    INDUSTRY = "industry_news"
    GENERAL = "general_news"


CATEGORY_IMPORTANCE = {
    DocCategory.REGULATORY: 1.0,
    DocCategory.EQUITY_CHANGE: 0.95,
    DocCategory.MERGER: 0.90,
    DocCategory.EARNINGS: 0.85,
    DocCategory.CONTRACT: 0.80,
    DocCategory.MANAGEMENT: 0.75,
    DocCategory.MACRO: 0.60,
    DocCategory.INDUSTRY: 0.30,
    DocCategory.GENERAL: 0.10,
}


KEYWORD_RULES = {
    DocCategory.REGULATORY: [
        r"(证监会|交易所).{0,20}(问询|关注|立案|调查|处罚)",
        r"(收到|收函).{0,10}(监管|问询|调查)函",
        r"(违规|违法|行政处罚|责令改正)",
    ],
    DocCategory.EQUITY_CHANGE: [
        r"(增持|减持|举牌|权益变动|股权转让).{0,30}(公告|提示)",
        r"(实际控制人|控股股东).{0,20}(拟|计划|已)(增持|减持)",
    ],
    DocCategory.EARNINGS: [
        r"(年度|半年|季度).{0,10}(报告|报)",
        r"(净利润|营业收入|扣非).{0,20}(同比|增长|下降)",
        r"(业绩预告|业绩快报|业绩修正)",
    ],
}


@dataclass
class ClassifiedDoc:
    doc_id: str
    title: str
    category: DocCategory
    importance: float
    companies: list[str]     # 涉及的股票代码
    source: str


def classify(doc: dict) -> ClassifiedDoc:
    """规则优先,BERT 兜底"""
    text = doc["title"] + " " + doc.get("content", "")[:500]

    for cat, patterns in KEYWORD_RULES.items():
        for p in patterns:
            if re.search(p, text):
                return _build(doc, cat)

    # 降级到 BERT 分类
    cat = bert_classify(text)
    return _build(doc, cat)


def _build(doc, cat):
    return ClassifiedDoc(
        doc_id=doc["id"], title=doc["title"],
        category=cat, importance=CATEGORY_IMPORTANCE[cat],
        companies=extract_codes(doc.get("content", "")),
        source=doc["source"],
    )

三、细粒度金融情感分析

金融情感分析不能直接用通用模型。"营收大幅下滑"在通用语境下不算什么,但在财报语境下是强烈负面信号。

3.1 金融情感词典

class FinSentimentAnalyzer:
    """
    细粒度金融情感分析。
    区分:对哪个实体?短期还是长期影响?强度如何?
    """

    POSITIVE = {
        "strong": [
            "超预期", "创历史新高", "大幅增长", "利润倍增",
            "获得重大订单", "突破性进展",
        ],
        "mild": [
            "略超预期", "稳健增长", "符合预期", "业绩改善",
            "市场份额提升", "获批",
        ],
    }
    NEGATIVE = {
        "strong": [
            "净利润大幅下滑", "亏损", "被立案调查", "客户流失",
            "债务违约", "商誉减值", "核心管理层离职",
        ],
        "mild": [
            "低于预期", "业绩承压", "竞争加剧", "毛利率下降",
            "成本上涨", "产能利用率下降",
        ],
    }

    def analyze(self, text: str, target: str) -> dict:
        """分析文本对目标实体的情感"""
        contexts = self._extract_contexts(text, target, window=100)
        if not contexts:
            return {"sentiment": "neutral", "score": 0.0}

        scores = [self._score(ctx) for ctx in contexts]
        avg = sum(scores) / len(scores)

        return {
            "sentiment": "positive" if avg > 0.1 else "negative" if avg < -0.1 else "neutral",
            "score": round(avg, 3),
            "intensity": "strong" if abs(avg) > 0.6 else "mild",
            "horizon": self._infer_horizon(text),
        }

    def _score(self, ctx: str) -> float:
        s = 0.0
        for phrase in self.POSITIVE["strong"]:
            if phrase in ctx: s += 0.8
        for phrase in self.POSITIVE["mild"]:
            if phrase in ctx: s += 0.4
        for phrase in self.NEGATIVE["strong"]:
            if phrase in ctx: s -= 0.8
        for phrase in self.NEGATIVE["mild"]:
            if phrase in ctx: s -= 0.4
        # 否定词翻转
        if re.search(r"(未|没有|并非|不存在).{0,5}", ctx):
            s = -s * 0.8
        return max(-1.0, min(1.0, s))

四、事件抽取与知识图谱

4.1 结构化事件抽取

@dataclass
class FinEvent:
    event_type: str       # earnings_beat / contract_win / regulatory_action
    subject: str          # 公司名称/代码
    predicate: str        # 动作
    objects: list[str]    # 金额/对象方/条款
    timestamp: str
    sentiment_impact: float
    confidence: float


EVENT_PATTERNS = {
    "equity_acquisition": (
        r"(?P<acquirer>.{2,10})(拟|计划|公告)(收购|并购)"
        r"(?P<target>.{2,10})(?P<stake>\d+(?:\.\d+)?%)?(?:股权|股份)"
    ),
    "earnings_disclosure": (
        r"(?P<company>.{2,10})(?:预计)?(?P<period>[\d年季]+)?"
        r"净利润(?P<direction>增长|下降|亏损)"
        r"(?P<magnitude>\d+(?:\.\d+)?%|[\d,.]+亿元)"
    ),
    "regulatory_action": (
        r"(?P<regulator>证监会|上交所|深交所).{0,30}"
        r"(?P<action>立案|问询|处罚|调查)"
        r".{0,20}(?P<company>.{2,10}(?:股份|科技|集团))"
    ),
}

4.2 公司关系知识图谱

金融知识图谱核心关系:

  公司 A --SUPPLIES_TO--> 公司 B    (供应链)
  公司 A --COMPETES_WITH--> 公司 C  (竞争)
  公司 A --CONTROLS--> 子公司 D     (控股)
  人物 X --MANAGES--> 公司 A        (管理)
  事件 E --AFFECTS--> 公司 A        (影响)

供应链风险传导是知识图谱最有价值的应用之一:当某个上游供应商出现负面事件时,自动识别受影响的下游公司。

# Neo4j 查询:供应链风险传导
SUPPLY_CHAIN_RISK_QUERY = """
MATCH path = (c:Company {code: $code})<-[:SUPPLIES_TO*1..3]-(supplier)
WHERE supplier.risk_flag IS NOT NULL
RETURN
    [node in nodes(path) | node.name] AS chain,
    supplier.risk_flag AS risk_type,
    length(path) AS depth
ORDER BY depth
LIMIT 50
"""

五、量化信号生成与回测

5.1 情感动量因子

import pandas as pd
import numpy as np
from scipy import stats


class NLPAlphaGenerator:
    """将情感分析结果转化为可回测的量化因子"""

    def sentiment_momentum(
        self,
        data: pd.DataFrame,  # [date, stock_code, sentiment_score, doc_count]
        halflife: int = 5,
    ) -> pd.DataFrame:
        """
        情感动量因子:
        近期正面情感累积越强,短期表现可能越好。
        使用指数加权移动平均做时间衰减。
        """
        result = []
        alpha = 1 - np.exp(-np.log(2) / halflife)
        for code, grp in data.groupby("stock_code"):
            g = grp.sort_values("date").copy()
            g["sent_ema"] = g["sentiment_score"].ewm(alpha=alpha, adjust=False).mean()
            g["sent_z"] = stats.zscore(g["sent_ema"].fillna(0))
            result.append(g)
        return pd.concat(result)

    def event_surprise(
        self, events: list, baseline_window: int = 20,
    ) -> pd.DataFrame:
        """
        事件惊喜因子:
        事件情感 - 过去 N 天情感基线 = 惊喜度。
        惊喜度显著正/负 -> 信号。
        """
        signals = []
        for evt in events:
            baseline = self._get_baseline(evt.subject, evt.timestamp, baseline_window)
            surprise = evt.sentiment_impact - baseline
            if abs(surprise) > 0.3:  # 阈值过滤
                signals.append({
                    "date": evt.timestamp,
                    "stock_code": evt.subject,
                    "event_type": evt.event_type,
                    "surprise": surprise,
                    "direction": "long" if surprise > 0 else "short",
                })
        return pd.DataFrame(signals)

5.2 回测框架

class Backtester:
    """
    NLP 信号回测。
    关键:严格防止数据窥视(look-ahead bias)。
    """

    def run(
        self,
        signals: pd.DataFrame,
        returns: pd.DataFrame,
        holding_days: int = 5,
        cost: float = 0.001,
    ) -> dict:
        """
        严格执行:
        1. 信号日 T -> T+1 开盘买入(不能用 T 日收盘)
        2. T+1+holding 卖出
        3. 扣除双边手续费
        """
        # ... 对齐 + 计算持有期收益 ...
        return self._metrics(portfolio_returns)

    def _metrics(self, rets: pd.Series) -> dict:
        ann_ret = rets.mean() * 252
        ann_vol = rets.std() * np.sqrt(252)
        sharpe = ann_ret / ann_vol if ann_vol > 0 else 0

        cum = (1 + rets).cumprod()
        peak = cum.expanding().max()
        dd = (cum - peak) / peak
        max_dd = dd.min()

        return {
            "annual_return_pct": round(ann_ret * 100, 2),
            "annual_vol_pct": round(ann_vol * 100, 2),
            "sharpe": round(sharpe, 3),
            "max_drawdown_pct": round(max_dd * 100, 2),
            "win_rate_pct": round((rets > 0).mean() * 100, 1),
            "calmar": round(ann_ret / abs(max_dd), 3) if max_dd != 0 else 0,
        }

六、监管合规要点

在中国,证券分析服务受严格监管。系统设计必须在合规框架内运行。

维度 要求 实现方式
资质 须持牌机构运营或内部使用 系统仅供内部研究
数据合规 不得使用内幕信息 仅接入公开数据源 + 溯源
数据本地化 重要数据境内存储 国内合规区域部署
风险提示 分析须附风险提示 输出自动附免责声明
信息安全 防市场操纵 数据验证 + 异常检测 + 审计日志

法律红线:系统所有输出均为辅助性研究参考,不构成投资建议。技术层面保障:

  1. 所有报告页头强制显示免责声明
  2. 不提供具体买卖点位或仓位建议
  3. 不支持实盘交易接口
  4. 操作日志全量留存

七、可解释性设计

金融场景对可解释性要求极高——分析师需要理解"为什么系统认为这是利空",而不是盲信一个分数。

7.1 解释层级

层级 内容 示例
信号来源 哪篇文档触发了信号 "2026-02-15 XX公司公告,标题..."
关键词句 文档中的决策关键句 "净利润同比下降 42.3%"
情感分解 正面/负面因素拆解 "+0.3(获得合同) -0.8(利润下滑) = -0.5"
历史对比 类似事件的历史表现 "过去 5 次类似业绩预警,3 次 T+5 下跌 > 3%"
关联传导 知识图谱路径 "A 是 B 的核心供应商(占比 30%)"

八、常见错误与避坑指南

错误 后果 正确做法
用通用情感模型 "利润下滑"判为中性 金融领域 fine-tuned 模型
忽略否定词 "未发生违约"判为负面 否定词检测 + 翻转
回测时信号日买入 引入 look-ahead bias T 日信号 -> T+1 开盘买入
不做截面中性化 行业/市值因子污染 Z-score 标准化 + 行业中性
情感词典不更新 新术语覆盖不到 定期从标注数据中扩充
只看 Sharpe 不看 IC 可能是偶然高收益 IC 均值 + ICIR 综合评估
忽略合规约束 法律风险 免责声明 + 审计日志
公告解析不处理表格 财务数据提取遗漏 PDF 表格识别 + 结构化

九、评估指标体系

模块 指标 目标值
文本分类 准确率 >= 92%
情感分析 与人工标注一致率 >= 85%
事件抽取 F1 >= 80%
量化信号 IC 均值 >= 0.05
量化信号 ICIR >= 0.8
回测绩效 Sharpe >= 1.0
系统延迟 公告到分析完成 <= 3 分钟
数据覆盖 A 股上市公司 >= 95%

十、总结

智能投研系统的最终价值,不在于替代研究员,而在于帮助研究员站在更高的信息起点上。技术降低了信息处理的成本,但对商业模式的理解、对管理层的判断、对行业趋势的前瞻——这些始终是人类研究员的核心竞争力。

三条核心建议:

  1. 合规第一:所有设计决策都要先过合规审查
  2. 可解释性:金融从业者不会信任黑盒,每个信号必须可追溯
  3. 闭环验证:NLP 指标再好也要经过回测验证和线上追踪

Maurice | maurice_wen@proton.me