
如果你每天需要阅读几十份券商研报多半会遇到一种无声的崩溃真正有价值的不是那些已经被市场充分讨论的结论而是研报里隐藏的“逻辑链”和“预期差”。但人工摘抄、Excel 归档、内部系统沉淀的方式很难把这些碎片信息织成一张可以用来做投资决策的网。大模型出现后很多人以为把研报扔给 ChatGPT 做个摘要就够了但真用到量化策略里会发现摘要只是第一步远远不够。XALPHA 这个方向的思路是把研报当成“记忆源”让 AI 量化研究员带着长期记忆去做研究而不是每次从零开始理解新报告。这篇文章要讨论的不是又一个“研报转摘要”的工具。我们把“研报记忆驱动”拆开看它背后是一套完整的系统设计研报文本解析、事件抽取、逻辑链构建、记忆存储与检索、策略信号生成、回测验证。这里真正的技术难点不是调用大模型本身而是如何让 AI 在“没有记忆”的天然缺陷下通过系统性的记忆架构沉淀出可回溯、可验证、可复用的研究能力。如果你的工作涉及量化投研、AI Agent 开发、RAG 应用或者正在尝试用大模型替代一部分人工研究工作那么这篇文章值得读完。我会从系统架构、核心流程、最小实现、常见坑位、工程实践五个层面展开并把代码示例落地到一个可运行的简化框架中。读完你可以照着搭建一个“研报记忆驱动”的 AI 量化研究员原型也能理解这类系统在生产环境中的真正边界。1. 这篇文章真正要解决的问题1.1 量化研究员的真实困境传统量化研究员做基本面研究时每天面对的信息量极大。一家券商研报可能包含行业景气度判断、公司盈利预测、上下游供需关系、政策影响、管理层表述变化等。研究员真正需要提取的是“什么变了”和“为什么会变”也就是预期差和归因逻辑。但现实是这些信息散落在不同日期的 PDF 文档里格式完全不一致。有的研报用表格表达核心假设有的用图形展示数据链路有的把关键信息藏在“风险提示”里。人工处理时经验和直觉起着决定性作用而经验恰恰是机构最难规模化复制的资产。大模型能做的事情是把这些非结构化文本转化成结构化字段事件、主体、时间、影响方向、置信度。但绝大多数 RAG 方案只做了第一步让模型可以回答“这篇研报说了什么”。而真正的研究工作是回答“这些研报串起来之后正在发生什么”。1.2 为什么“记忆”是 AI 量化研究员的核心我们通常说大模型“没有记忆”指的是模型参数在训练完成后就固定了。即便通过 Prompt 注入长篇上下文系统也无法自动区分哪些信息值得长期保留哪些只是当天的噪声。研报记忆驱动的方法相当于给大模型外加了一个“研究员的笔记本”。系统负责把每次读到的新信息写入笔记本并且在需要决策时只取出与当前问题相关的历史笔记。这里的关键不是简单的文件存储而是要让记忆具备时间维度、逻辑维度和可信度维度。XALPHA 这个名字透露出两层含义X 代表未知因子或未知变量ALPHA 代表超额收益。结合起来看它要研究的是“哪些尚未被市场充分定价的信息能成为未来超额收益的来源”。这正是研报记忆驱动系统最有价值的地方持续记录市场上的预期变化寻找预期差被纠正的窗口。1.3 这类系统适合谁不适合谁先说适合谁。第一类是量化私募或自营团队的研究部门他们有很多存量研报数据但缺少统一的语义层。第二类是 AI Agent 应用开发者他们想构建一个能“长期工作”的行业 Agent研报记忆驱动是一个很好的业务原型。第三类是个人量化爱好者想用大模型辅助自己做基本面研究但不想停留在单轮问答层面。不适合谁呢纯高频量价策略团队不适合。这类系统处理的是研报文本和逻辑推导对 Tick 级行情没有帮助。另外如果研报数据本身没有合法授权或者团队没有基本的数据治理规范也不建议在生产环境使用。2. 核心概念与系统架构2.1 从“检索增强生成”到“记忆驱动”RAGRetrieval-Augmented Generation是目前大模型应用最常见的方式它通过将用户问题去匹配向量数据库中的文档片段再拼接 Prompt 让模型生成答案。RAG 解决的是“让模型知道更多事实”的问题但它不区分哪些文档是“记忆”哪些是“一次性上下文”。在量化研究场景里我们需要三种不同性质的记忆记忆类型说明示例短期事件记忆最近发生的公司动作、行业事件某公司发布定增预案募资 30 亿用于产能扩张中期逻辑记忆行业上下游之间的传导关系光伏装机超预期 → 逆变器需求提升 → 部分公司盈利上修长期认知记忆公司护城河、管理层风格、历史诚信记录某公司历年毛利率高于同行且产能利用率稳定普通 RAG 拿到的是一堆平行文档记忆驱动必须把这些文档组织成有层级的图结构或时间序列结构让 AI 在分析新研报时能自动联想到三年前这家公司管理层说过的话并且判断这次表态和上次是否一致。2.2 XALPHA 的参考架构从工程视角看一个完整的研报记忆驱动 AI 量化研究员包含五个模块数据接入层处理 PDF、HTML、文本文件识别表格、图片、页眉页脚。解析与事件抽取层利用大模型抽取结构化事件输出统一 JSON Schema。记忆管理层负责记忆的写入、更新、过时淘汰和冲突检测。检索与推理层根据用户关注点召回相关记忆拼装成多步推理 Prompt。策略与验证层将推理结果转化为信号连接回测系统检验有效性。这五个模块不是串行的记忆管理层是核心枢纽。每一次新研报写入时可能同时触发旧记忆的更新和淘汰。这个过程不是简单的 SQL 增删改而更像人类研究员在持续跟踪一家公司时的“笔记更新”。2.3 关键设计一切结论都可溯源AI 研报研究员最容易出问题的地方就是一本正经地给出没有出处的结论。如果系统无法回答“你为什么认为这家公司未来毛利率会改善”那么它的信号就不可信。在架构设计上每个记忆条目都必须包含来源文件、解析时间、置信度、关联事件链。例如{ id: event-20250318-001, source: xx证券_公司深度报告_20250318.pdf, parsed_at: 2025-03-18T10:30:00Z, event_type: 产能扩张, subject: xx新能源公司, predicate: 宣布投建新产线, confidence: 0.75, logic_chain: [下游需求高增, 公司扩产, 单位成本下降, 毛利率提升], expire_at: 2026-03-18T10:30:00Z }这样的结构让整个系统具备了“审计”能力。策略出问题的时候可以一步步回溯到最初的那条研报并判断是模型解读错了还是研报信息滞后了。3. 环境准备与前置条件3.1 技术选型建议这个主题本身不绑定特定语言但考虑到量化团队和 AI 生态的现状Python 是最稳妥的选择。大模型推理接口可以兼容 OpenAI 格式或各家国内模型服务的兼容接口记忆存储可以先用 SQLite 或 JSON 文件跑通后续再迁移到 PostgreSQL 和向量数据库。我建议从最小可行架构开始不要一开始就上复杂的分布式框架。你需要准备的东西如下。3.2 基础环境清单操作系统Windows / Linux / macOS 均可。Python 版本3.9 或以上建议 3.10。模型服务可以使用任意提供文本对话接口的大模型服务包括本地部署的 vLLM、Ollama以及云端商用 API。记忆存储先用 SQLite后续可以切换到 Milvus、Qdrant 或 pgvector。依赖库requests、pypdf或pdfplumber、json、sqlite3、re。安装命令示意pip install requests pypdf pdfplumber如果你还没有可用的模型服务也可以在本地用 Ollama 加载一个开源模型做验证。本文的核心逻辑在接口层抽象不绑定具体模型。3.3 配置管理生产环境建议通过环境变量或配置文件管理模型服务的地址、密钥、温度等参数。以下是.env文件示例LLM_API_BASEhttps://your-llm-service.example.com LLM_API_KEYsk-xxxx LLM_MODELyour-model-name LLM_TEMPERATURE0.2 MEMORY_DB_PATH./memory.db注意这里只是结构示例具体接口地址以你实际使用的服务为准。不要把密钥提交到代码仓库更不要写在博客里让别人复制。4. 核心流程拆解4.1 研报文本解析研报格式千奇百怪但大体上可以分成文字型、表格型和图文混排型。文字型研报比较容易抽取表格型研报需要用表格识别模型或pdfplumber的表格抽取功能。图片型研报还需要 OCR成本会明显增加。这一阶段的目标不是“完美还原 PDF”而是提取出研究者需要的关键段落。我的建议是优先处理文本型研报把表格转换成 Markdown 表格忽略图表中的曲线细节除非后续真的要研究图形数据。4.2 事件抽取与逻辑链构建这一步是整个系统的灵魂。要抽取的事件不能只是“公司发布了年报”这种客观事实更要抽取“公司认为下游需求超过此前预期”这种带有预期差信号的表述。逻辑链的构建通常需要两步触发词识别识别盈利上修、产能释放超预期、毛利率下滑等触发词。因果梳理将句子中的因果关系整理成 A→B→C 的链路。这一步主要靠大模型。为了让输出的 JSON 结构稳定建议在 Prompt 中给出严格的输出 Schema 示例并设置较低的温度数值比如 0.10.2减少自由发挥。4.3 记忆写入与更新抽取到结构化事件后不是简单 append 到一个列表里。需要考虑三类操作新增完全新的事件直接写入。更新同一主体、同一事件类型新信息修正了旧结论。过期旧记忆超过有效期或与最新信息冲突且新信息置信度更高。在这一步可以给每条记忆设置一个“置信度衰减”策略。时间越久置信度越低。比如三个月前的盈利预估值如果没有后续验证置信度就从 0.8 降到 0.6。当新研报提到同一个财务指标时用新值覆盖旧值同时保留旧值作为历史版本。4.4 检索与推理当研究员需要回答“当前新能源板块是否有投资机会”时系统会先基于问题拆解出实体和关键词比如“新能源”“产能”“需求”然后去记忆库中检索相关事件。检索结果要做排序不能只按时间倒序还要结合事件影响力和置信度。之后把检索到的记忆条目作为上下文让大模型做多步推理最终输出一个带理由的判断。4.5 从研究结论到量化信号量化策略和主观判断不一样最终必须落在可计算的信号上。这一步我们需要把大模型输出的 BUY/NEUTRAL/SELL 映射成持仓权重或因子值。需要注意的是信号不能直接用于盘中交易必须经过回测和延迟验证。假设系统在 T 日解析了一份研报那么策略最早只能在 T1 日使用该信号。原因是研报公开时间通常早于多数投资者真正读到的时间但为了保守起见加入一日延迟是非常必要的风控措施。5. 完整示例代码实现下面我们用 Python 实现一个简化的“研报记忆驱动 AI 量化研究员”。这个示例不依赖重量级框架只使用标准库加一个call_llm模拟函数方便你在本地跑通流程。实际使用时把这个函数替换成真实模型服务调用即可。5.1 代码结构research_memory/ ├── memory.json # 记忆库文件 ├── researcher.py # 主逻辑 └── reports/ # 存放研报文本5.2 研报解析与事件抽取# researcher.py import json def call_llm(prompt: str) - str: 模拟大模型调用。 真实项目中这里可以替换为 POST {LLM_API_BASE}/chat/completions { model: LLM_MODEL, messages: [{role: user, content: prompt}], temperature: 0.2 } # 这段逻辑只是为了演示实际业务请使用真正的模型接口 if 产能扩张 in prompt: return json.dumps({ events: [ { source: 新能源公司深度报告.pdf, event_type: 产能扩张, subject: 某新能源公司, predicate: 宣布投建新产线, logic_chain: [ 下游需求高增, 公司扩产, 单位成本下降, 毛利率提升 ], confidence: 0.75, timestamp: 2025-03-18T10:30:00Z } ] }) return json.dumps({events: []})这段代码的call_llm并没有连接真实大模型而是用规则模拟了输出结果。这样做的好处是你可以在没有模型的机器上直接看到完整流程。实际项目中它会调用统一的大模型接口并把返回结果解析成同样的 JSON。5.3 记忆存储与检索记忆存储使用 JSON 文件方便调试。生产环境建议换 SQLite 或向量数据库。# researcher.py 续 import json import re from datetime import datetime, timezone MEMORY_FILE memory.json def store_events(events: list) - None: 将结构化事件写入记忆库。 try: with open(MEMORY_FILE, r, encodingutf-8) as f: memory json.load(f) except FileNotFoundError: memory [] now datetime.now(timezone.utc).isoformat() for event in events: event[stored_at] now # 这里可以增加去重逻辑同 subject event_type 且时间接近时合并或更新 memory.append(event) with open(MEMORY_FILE, w, encodingutf-8) as f: json.dump(memory, f, ensure_asciiFalse, indent2) def retrieve_memory(query: str, top_k: int 5) - list: 根据关键词检索记忆库简化版使用字符匹配。 try: with open(MEMORY_FILE, r, encodingutf-8) as f: memory json.load(f) except FileNotFoundError: return [] keywords re.findall(r[\u4e00-\u9fa5A-Za-z0-9], query) scored [] for item in memory: text json.dumps(item, ensure_asciiFalse) score sum(1 for kw in keywords if kw in text) if score 0: scored.append((score, item)) scored.sort(keylambda x: x[0], reverseTrue) return [item for _, item in scored[:top_k]]这里的关键点是把“记忆”当作结构化数据存储而不是原文全文。检索时用关键词匹配只是为了演示实际生产环境应该使用向量检索因为它能处理同义词和语义相关性问题。比如用户搜索“新能源需求”向量检索可以关联到“光伏装机量”相关记忆而关键词匹配做不到。5.4 研究结论生成最后一步是结合记忆库生成投资判断。# researcher.py 续 def generate_signal(query: str) - str: 基于历史记忆生成信号。 related retrieve_memory(query) if not related: return NEUTRAL: 记忆库暂无相关信息 context json.dumps(related, ensure_asciiFalse, indent2) prompt ( 你是一名量化研究员。请基于以下历史研报记忆判断当前问题的投资信号。\n f问题{query}\n f历史记忆\n{context}\n 请只输出三选一BUY / NEUTRAL / SELL并给出不超过两行理由。 ) result call_llm(prompt) # 兼容模拟函数的 JSON 输出 if result.startswith({): data json.loads(result) if data.get(events): return BUY: 产能扩张逻辑链支持盈利改善预期 return result.strip() if __name__ __main__: # 模拟新研报的解析事件 mock_prompt 公司宣布新能源产能扩张预计2025年投产。 extracted json.loads(call_llm(mock_prompt)) store_events(extracted[events]) # 查询并生成信号 query 新能源 产能 signal generate_signal(query) print(生成信号, signal)5.5 如何替换为真实大模型接口上述代码里的call_llm函数是演示用的。真实场景下可以改成如下结构import requests import os def call_llm(prompt: str) - str: api_base os.environ[LLM_API_BASE] api_key os.environ[LLM_API_KEY] model os.environ[LLM_MODEL] resp requests.post( f{api_base}/v1/chat/completions, headers{Authorization: fBearer {api_key}}, json{ model: model, messages: [{role: user, content: prompt}], temperature: 0.2, }, timeout60, ) resp.raise_for_status() return resp.json()[choices][0][message][content]需要强调的是不同模型服务的接口地址、鉴权方式、返回字段可能有差异以你接入的模型服务官方文档为准。在生产环境中建议在call_llm外面加一层超时控制、重试逻辑和 token 用量统计。6. 运行结果与效果验证6.1 运行方式在项目目录下执行python researcher.py如果一切正常你会看到类似输出生成信号 BUY: 产能扩张逻辑链支持盈利改善预期同时项目目录下会生成memory.json里面保存了结构化事件记录。6.2 如何判断流程是否正确第一步检查memory.json中是否新增了一条事件并且字段完整。第二步再次运行脚本观察store_events是否会重复添加。第三步修改查询词为“半导体”看系统是否输出NEUTRAL以此验证记忆检索的过滤能力。这个最小示例并没有接入真实模型所以它验证的是“流程正确性”而不是“策略收益正确性”。如果你想验证模型的判断质量需要用自己的研报数据接入真实模型并建立人工标注集合来评估抽取准确率。6.3 关于“未来信息泄露”的验证如果你把这段代码接入回测系统有一个非常容易犯的错误用当日研报解析出的信号驱动当日开盘的模拟交易。从数据时间线上看这就是典型的未来函数或者叫未来信息泄露。研报的发布时间是一个关键字段。最稳妥的方式是策略只能在研报发布时间之后至少一个交易日才能使用该信号。在这个示例里我们给事件打了时间戳但没有在检索时做时间过滤。生产环境必须加入类似如下判断def is_available_for_trading(event_time, current_time): # 简单策略事件必须早于当前时间至少 1 天 return current_time - event_time timedelta(days1)这只是一个最小示例具体交易日的计算要结合交易所日历和休市时间。7. 常见问题与排查思路问题现象可能原因排查方式解决方案抽取结果经常把无关句子识别为事件Prompt 中事件定义不严格输出 Schema 不清晰查看中间 Prompt 和模型返回人工标定错误样本在 Prompt 中补充正反例采用 Few-shot 方式降低模型温度记忆库中同一事件重复存储缺少去重主键检查store_events中是否存在 subject event_type time 唯一性判断增加唯一索引或以语义向量相似度 0.95 作为合并条件回测结果策略收益极高但不稳定可能使用了未来数据检查信号时间戳和交易时点统计信号产生到执行之间的最小时间差加入足够的信号延迟禁止信号产生当日交易模型生成的研究结论缺少来源检索出的内容没有带上原文引用查看生成 Prompt 和返回内容在记忆条目中强制携带 source 字段并要求模型输出时引用编号记忆检索召回不准确关键词匹配无法处理同义表达用几个同义 query 测试召回结果切换到向量检索并用带标签的训练集评估召回率新研报内容与旧记忆冲突模型信息抽取错误或公司基本面确实变化对比新旧记忆的原文查看事件逻辑链差异引入冲突检测机制人工确认后再覆盖旧记忆8. 最佳实践与工程建议8.1 建立记忆的版本管理记忆和代码一样需要版本管理。当模型升级、Prompt 改写、抽取规则调整后同一份研报可能会产出不同的记忆条目。建议为每条记忆加上schema_version和model_version字段方便批量回放和重算。你可以把整个记忆库导出成 JSON放入 Git 仓库中做版本管理虽然文件大小会增长很快但前期的可追溯性远比存储成本重要。8.2 让人类研究员保持在环路中AI 量化研究员负责“初筛”和“跟踪”但关键结论必须有人类复核。尤其是在模型置信度低于某个阈值时系统应该自动生成待确认任务推送给研究员。这个交互流程类似于代码审查AI 提交 diff人类决定是否合入记忆库。8.3 不要忽略数据合规研报版权、数据来源授权是第一道红线。你必须确认购买的研报数据允许被挖掘和形成派生数据。另外基于内幕信息的交易是明确的违规行为系统设计上应该过滤掉明显涉及非公开信息的文本片段。在合规层面宁可牺牲一些效率也不能让系统变成“内幕信息放大器”。8.4 评估体系要分层对这类系统的评估不能只看最终策略收益。建议拆成三个层抽取层事件识别的精确率和召回率。记忆层记忆更新的准确率和冲突处理效率。决策层信号与未来收益的相关性、换手率和回撤特征。这样拆开之后你才能知道如果策略亏钱是模型读错了研报还是记忆检索没抓到关键信息还是策略本身逻辑就不成立。8.5 从“信号驱动”走向“研究绩效驱动”上线初期系统可能只是帮研究员节省看报告的时间。这时团队容易觉得“AI 不过如此”。更合理的评价标准是AI 是否发现了人工忽略的跨时间、跨行业关联是否在研究员休假时保持了持续的跟踪这些价值比短期策略收益更稳定也更值得投入。9. 总结与后续学习方向研报记忆驱动 AI 量化研究员本质上是用大模型作为“推理引擎”用记忆系统作为“研究底盘”。它不解决“预测股价”的问题而是解决“让研究过程可沉淀、可验证、可规模化”的问题。如果把投资研究比作一场长期战斗这套系统不是给你一把更锋利的刀而是帮你建了一个弹药库每一颗子弹都标记了来源、时间和对目标的杀伤预期。下一步如果你打算真正落地建议从三个方向深入第一把记忆库从 JSON 文件升级为 SQLite 或 PostgreSQL并接入向量检索。第二建立一套人工标注的事件抽取测试集持续评估不同模型在研报场景下的准确率。第三把研报发布时间、交易日历和信号延迟纳入回测框架堵住未来信息泄露的漏洞。AI 量化研究还处在非常早期的阶段研报记忆驱动只是其中一个值得关注的技术方向。真正的难点从来不是调用一次大模型而是让模型产出的每一句话都经得起来源检验和时间检验。从这个角度看XALPHA 代表的是一类系统的理念让 AI 不仅是“会说话的研究员”更是“记得住、想得清、查得到依据”的研究员。