简介:面向中小券商财务分析智能化转型,文档围绕DeepSeek部署与研报自动生成,给出完整架构设计。内容先剖析传统财务分析模式在数据收集、分析方法、人力成本与效率上的局限,以及研报生成流程的问题;接着介绍DeepSeek的神经网络架构、训练过程、语言理解与文本生成能力,并列举财务报表分析、市场趋势预测等金融应用;然后依次设计数据层的数据采集/存储/预处理,模型层的选型/微调/部署,服务层的接口/模块/监控,应用层的界面与功能;同时覆盖项目规划、环境搭建、数据迁移、模型测试、系统运维等实施步骤,并针对数据质量、模型成本、系统安全等挑战给出解决方案,最后通过券商案例展示效率与质量提升效果,并讨论多模态融合等趋势。资源包为1个PDF文件,共31页,容量2.08MB,目录结构清晰完整,文字、图表显示正常,目前已有72人学习查看。适合券商信息技术人员、金融分析师、AI架构师及研究DeepSeek金融落地的读者深入参考。
1. 一份架构设计标题背后的真问题:券商研报生产为什么会卡在「人手」上
中小券商的研究所通常只有十几到几十个分析师,却要覆盖几十个行业、上百只个股。周一开盘前要出晨报,财报季要覆盖批量业绩点评,研报里大量内容其实是固定模板加结构化数据:业绩数据、环比同比、毛利率变化、估值区间。真正耗时间的不是判断,而是「把数据填进模板、再把模板整理成可读的文字」。于是有人开始想:能不能让大模型先把初稿写出来,分析师只做复核和判断?这就是DeepSeek这类可本地部署的模型在券商场景里最自然的切入点——不是替代分析师,而是把找数据、写初稿、格式化这三段最枯燥的环节自动化。这篇架构设计要回答的,正是数据从哪来、模型怎么部署、生成结果怎么管三个问题。适合读它的人有两类:一类是被研报产能压得喘不过气的分析师,另一类是在券商IT部门做大模型落地的工程师。
2. 研报自动生成的五层架构:从行情数据到PDF输出怎么串起来
2.1 为什么中小券商选DeepSeek而不是直接调闭源API
券商场景有个天然约束:持仓数据、客户信息、尚未发布的财务数据都不能出内网。常见做法是直接调云端大模型API,但这在券商合规评审阶段就会被拦下来——数据出境这一条就过不了。所以中小券商要落地研报生成,第一步不是选模型,而是选部署方式。DeepSeek开源权重可以完整跑在内网,这是它被写进这份架构设计的最直接理由。
成本账也要算。闭源API按token计费,研报生成是长文本任务,一份深度研报的提示词加输出常常要消耗上万token。按月度几十份研报、每份多轮修改计算,一年下来的API费用足够买一台能跑量化模型的GPU服务器。本地化部署是一次性硬件投入加电费,长期来看更可控。而且本地部署还能拿到完整的输入输出日志,审计的时候说得出「哪份研报是哪个模型、哪个版本、哪个参数生成的」。
选DeepSeek还有个现实原因:它的权重对中文长文本理解好,研报这种「数据密集、论述偏格式化」的文本正好在它的能力范围内。架构上用「模型服务层」做隔离,后面想换更强的新模型,只替换这一层就行,上层业务代码不用动。
2.2 数据层:行情、财报、公告三类数据源先统一成一份标准格式
研报生成不能让模型凭空写。一份最基础的业绩点评至少需要三类数据:行情数据(股价、涨跌幅)、财务报表数据(营收、净利润、毛利率、EPS)、公告原文(业绩预告、重大合同)。这三类数据来源不同,行情走数据库或行情服务,财报走结构化接口,公告原文档是PDF或HTML。直接把这些异构数据拼进提示词会让模型乱掉,所以要设计一个统一的数据接入层。
我一般会先把三类数据清洗后统一转成JSON结构。字段尽量对齐,再用Renew的代码生成阶段直接从数据层取:
{ "report_meta": { "company": "示例证券", "stock_code": "600000", "report_date": "2025-04-28" }, "market_data": { "close_price": 10.25, "chg_pct": 2.35, "pe_ttm": 15.8, "pb": 1.2 }, "financials": { "period": "2025Q1", "revenue": 1250000000, "revenue_yoy": 0.08, "net_profit": 320000000, "profit_yoy": 0.15, "gross_margin": 0.34, "eps_diluted": 0.21 } }这段结构里,report_meta负责定位一家公司、一份报告的时间窗口;market_data放当日和区间行情;financials放关键财务指标。字段名用英文,值用数字,单位统一成元或百分比数值。这样做的原因是模型对数字的理解比自然语言可靠——你把「毛利率 34%」写成gross_margin: 0.34,模型拼进提示词时不容易产生歧义。
数据层最容易翻车的地方是财务数据的单位不一致:利润表用万元,EPS用元。我的做法是在接入层强制统一单位,并在每个数值字段里保留原始单位备注字段。后面模型生成「净利润同比增长15%」这类表述,靠的就是这份干净的JSON,原始PDF留给人工复核时查阅。
2.3 用vLLM在本地跑通DeepSeek的最小命令集
数据准备好了,模型怎么跑起来?本地化部署DeepSeek,常见做法是选vLLM作为推理引擎。vLLM相比直接把模型权重加载进transformers,优势在吞吐量——研报生成要同时服务多个分析师,吞吐上不去就变成排队。vLLM的PagedAttention对显存管理更好,同样的显卡能塞下更大的batch。
部署时我一般用如下命令集合。先创建Python环境,装vLLM,再用vllm serve启动模型服务。如果只是内网测试,不需要写额外代码,vLLM自带的OpenAI兼容接口就能直接用:
# 建议 Python 3.10 以上,显存不低于 24GB conda create -n deepseek-vllm python=3.10 -y conda activate deepseek-vllm pip install vllm # 启动模型服务,假设权重在本地 /models/deepseek 目录 vllm serve /models/deepseek \ --served-model-name deepseek-report \ --port 8000 \ --max-model-len 32768 \ --gpu-memory-utilization 0.85 \ --tensor-parallel-size 1启动后可以用curl做一次最简单的连通性测试:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-report", "messages": [{"role": "user", "content": "写一段50字的业绩点评"}], "max_tokens": 200, "temperature": 0.3 }'这几行命令里,--max-model-len控制最大上下文长度。研报生成建议至少32768起步,因为提示词里要塞入数据JSON加上研报骨架,输出又是一段长文本,长度太小会直接截断。--gpu-memory-utilization表示显存利用率,0.85是个安全值,给CUDA context和碎片留了余量,直接填0.95在长上下文下容易OOM(显存溢出)。--tensor-parallel-size在多卡机器上可以调成卡数,单卡就保持1。
跑通接口之后,下面的业务层就无需关心模型内部细节了,所有组件只需要往这个HTTP服务发请求。这一步跨过去,后面所有生成功能都在这个服务之上做。
2.4 应用层:研报生成服务的请求链路与任务拆分
模型服务就绪后,接下来是研报生成的应用层设计。这里要避免一个常见误区:不要把「整份研报」当成一次大模型的请求。一份研报三五千字,强行一次生成,输出时间会超过一两分钟,HTTP连接容易超时,中间任何一次生成错误整份都要重跑。
我采用的常见做法是「任务拆分 + 异步队列」。分析师提交一份研报生成请求,系统拆成以下步骤:
- 任务网关接收请求,生成一个
report_task_id,写入任务队列。 - 数据服务根据标的代码和时间窗口拉取行情、财报、公告三类数据,组装成2.2节的JSON。
- 生成编排服务按研报章节列表(摘要素材、业绩分析、行业背景、风险提示)逐个向vLLM发起子任务。
- 每个子任务生成完成后,结果写入临时存储;全部完成后拼接,交给排版服务导出Word或PDF。
- 人工复核通过后再对外发布。
这个链路里任务队列是关键。vLLM同时处理多个长文本生成时,吞吐会明显下降,甚至出现排队阻塞。队列把提交和生成解耦,分析师提交任务后可以去做别的,生成完成再收到通知。report_task_id贯穿全流程,出了问题能定位到具体是哪个环节失败。
3. 让DeepSeek写研报像回事:提示词工程与结构化输出
3.1 研报骨架:把一份研报拆成多段小任务比一次生成长文可靠得多
DeepSeek生成短段落时质量明显优于长文本。长文本生成到后半段容易出现重复论述、数据不一致、甚至主题漂移。研报这种文体有固定骨架,天然适合分节生成。把一份标准业绩点评拆成摘要、业绩分析、财务指标、行业与展望、风险提示几个段落,每段独立生成,最后拼接。
我常用的研报章节骨架如下:
- 摘要:三到五句话概括业绩核心变化和结论
- 业绩概览:营收、净利润、同比环比数字描述
- 财务分析:毛利率、费用率、现金流变化
- 行业与竞争:所在行业景气度、公司位置
- 盈利预测与估值:目标价、估值区间(需人工复核)
- 风险提示:政策风险、行业风险、经营风险
分节生成还有个好处:每一节都可以独立设置temperature和max_tokens。数据描述部分温度调到0.2以下,避免模型自由发挥;风险提示部分用0.5左右,让表述稍微有变化但不出格。对比一眼生成全文,这种小任务方式成功率高出不少。
3.2 用RAG把财报数字喂给模型:降低幻觉最关键的一步
模型不擅长记忆精确数字。你问它「某公司2025年Q1营收」,它可能靠训练数据里的印象回答,而这个印象很可能过时或不准。研报里数字错了是事故,所以生成之前必须把准确的数字塞进上下文。这就要用到RAG(检索增强生成)。
系统可以这样工作:收到生成请求后,先从公司财务数据里检索最近N期的业绩数据,再把检索结果和基础数据JSON一起拼进提示词。注意RAG在研报场景里不一定要用向量数据库——数据表本来就结构化,直接按时间窗口查出来即可。向量检索主要用在公告原文和行业研报的知识库检索上。
下面是用Python封装的生成函数,里面体现「数据 + 提示词模板 + 模型调用」三步:
import json import requests def generate_report_section(company_data, section_name, api_url="http://127.0.0.1:8000/v1/chat/completions"): # 把结构化数据嵌入提示词 data_block = json.dumps(company_data, ensure_ascii=False) template_map = { "summary": "你是券商分析师。基于以下财报数据,写一段150字的业绩摘要,只写事实,不做预测:\n{data}", "financials": "你是券商分析师。基于以下财报数据,分析毛利率和净利润变化原因,200字:\n{data}", "risk": "你是券商分析师。基于以下财报数据,列出三条行业或经营风险,每条约40字:\n{data}" } prompt = template_map[section_name].format(data=data_block) resp = requests.post(api_url, json={ "model": "deepseek-report", "messages": [{"role": "system", "content": "你是一名严谨的证券分析师,输出研究报告片段。"}, {"role": "user", "content": prompt}], "temperature": 0.2, "max_tokens": 600 }, timeout=90) return resp.json()["choices"][0]["message"]["content"]这段代码做了三件事:把结构化数据JSON直接放进提示词,让模型基于具体数字生成;按章节类型选不同模板,摘要、财务分析、风险提示各有侧重;temperature控制在0.2,保证表述稳定。风险提示这类需要一点灵活性的章节,可以在外部单独调高温度。
3.3 结构化输出:让模型先返回JSON,再转成文档格式
研报生成结果要进Word或PDF模板,如果直接让模型输出自然语言段落,排版层还得做一遍解析,容易出错。更可靠的做法是让模型输出JSON,字段对应章节标题和段落内容,排版层拿到JSON直接填充模板。
def generate_structured_report(company_data): prompt = f""" 根据财报数据生成研报,返回JSON格式,不要输出任何其他内容。 财报数据:{json.dumps(company_data, ensure_ascii=False)} 要求JSON结构如下: {{ "title": "研报标题", "summary": "摘要段落", "sections": [ {{"heading": "业绩概览", "body": "内容"}}, {{"heading": "财务分析", "body": "内容"}}, {{"heading": "风险提示", "body": "内容"}} ] }} """ # 调用 vLLM 接口,参数需要加上 response_format 约束 response = requests.post("http://127.0.0.1:8000/v1/chat/completions", json={ "model": "deepseek-report", "messages": [{"role": "user", "content": prompt}], "temperature": 0.2, "max_tokens": 2000, "response_format": {"type": "json_object"} }, timeout=120) return json.loads(response.json()["choices"][0]["message"]["content"])response_format这个参数很关键,vLLM的OpenAI兼容接口支持它约束输出格式。加了之后模型返回的就是合法JSON,省去解析自然语言文本时格式错乱的麻烦。但要注意,JSON约束会增加token消耗,max_tokens要留足余量,不然容易生成一半被截断导致JSON不完整。
3.4 让DeepSeek生成的内容更像分析师写的:去模板化的几个细节
如果模型生成的摘要每次都长一个样,分析师看一眼就知道是AI写的。这里有几个调教细节。一是在提示词里加「不要使用'综上所述''总体而言'等套话」,二是让模型在财务分析部分引用数字时保留分析师习惯的表达,三是每段控制在80到150字之间,避免过短的碎片化句子。生成完还要做一遍文本后处理,去掉AI常用句式,这一步通常用正则加一段清洗函数处理。
4. 中小券商部署DeepSeek的避坑指南:算力、幻觉与合规三座大山
4.1 显存溢出:24GB卡跑长文本翻车的现实原因
现象:vLLM启动成功,但生成到一半报错CUDA out of memory,进程直接崩溃。
原因:--max-model-len设置过大,每个请求都要按最大长度预分配KV Cache显存。假设模型权重占14GB,24GB卡上还想开32768长度上下文,再叠加几个并发请求,显存必然不够。
解决:把--gpu-memory-utilization从0.85降到0.75,--max-model-len降到16384,同时限制最大并发数。量化也是常见做法,用4bit量化能显著降低权重占用。但量化对长文本生成质量有轻微影响,研报场景建议先量化测试几份,对比数字准确性再决定。
vllm serve /models/deepseek \ --served-model-name deepseek-report \ --max-model-len 16384 \ --gpu-memory-utilization 0.75 \ --quantization awq \ --max-num-seqs 8--max-num-seqs限制了同时处理的序列数,超过的请求在队列里等待。它和--gpu-memory-utilization配合,能有效避免高并发下的OOM。显存紧张时,先降并发比降模型长度更安全,因为研报提示词本身就不小。
4.2 研报数字对不上:模型把正确答案写成了「貌似合理」
现象:生成的研报里写着「净利润同比增长15%」,但数据JSON里明明是8%。数字差距不小,但句子读起来通顺,分析师复核时不注意就漏过去了。
原因:大模型本质是概率生成,它会把训练时见过的行业平均增速迁移到当前数据上,从而产生「合理但不正确」的数字。这不是bug,是模型的天然缺陷。
解决:不能靠提示词修复,只能靠校验。常见做法是生成之后跑数字校验脚本,把JSON数据里的关键数值和生成文本里的所有数字做交叉比对。我用一个简单脚本,先用正则提取文本里所有百分比数值,再和数据块里的原值比对,不一致的段落高亮,进入人工复核队列。这样把幻觉问题从「模型不犯错」转成「错误能被发现」。
4.3 合规留痕:系统里每一步都要能回溯
现象:一份研报发出后,被质疑某句结论没有依据,但系统查不到是模型生成的还是分析师写的。
原因:生成链路没有做审计日志,模型服务只记录了prompt,没记录完整上下文和参数版本。一旦出问题,无法定位责任和修复路径。
解决:每条生成记录落库,至少包括report_task_id、模型名称与版本、vLLM参数、提示词原文、完整回复、生成时间、复核人。研报的每一章都要能对应到一次具体的模型调用。合规要求的核心不是「模型不能错」,而是「出问题能追溯」。这是技术系统保证的,不是靠人写保证书。
4.4 长文本截断:生成到一半,段落突然停住
现象:财务分析段落生成到一半直接结束,没有结尾。原因有两个,一是max_tokens设置偏小,二是vLLM服务端max-model-len余量不足。
解决:分节生成后,每节单独检查是否被截断。在分析段落,max_tokens至少给到800;在生成代码里检查返回结果,如果末尾不是句号或合理结束标记,就重新生成一次,并在提示词里注明「续写时从上次断点继续」。这个重试逻辑要写在应用层,因为服务端只看得到单次请求,感知不到研报整体是否完整。
4.5 提示词长度被悄悄截掉:数据JSON塞太多提示词仍被截断
现象:模型输出质量尚可,但摘要里漏掉了部分财务数据,比如毛利率没有提到。
原因:数据JSON包含了很多字段,提示词整体接近上下文长度上限,模型在注意力分布上淡化了一部分字段。
解决:控制每个章节提示词里的数据量,只放该章节关心的字段——写财务分析就只放利润表数据,写估值就把行情数据放前面。提示词越短,模型对关键字段的注意力越集中。这个和RAG的思路一致:不是把所有信息都塞进去,而是只塞当前任务需要的。
5. 验证一套研报生成系统:从单份质检到批量回归
5.1 单份研报的质检清单:数字、逻辑、格式三个维度
模型生成的研报能不能用,要有一个可量化的质检过程。我一般用下面这套质检维度,每份研报生成后自动跑一轮:
| 质检维度 | 检查方式 | 通过标准 |
|---|---|---|
| 数据准确性 | 正则提取数值字段与数据JSON比对 | 关键财务数据错误为0 |
| 逻辑一致性 | 检查摘要与正文结论是否矛盾 | 无自相矛盾表述 |
| 章节完整性 | 检查骨架字段是否齐全 | 所有章节均非空 |
| 格式规范 | 检查是否有中文标点混用、半角数字 | 错误不超过3处 |
| 合规留痕 | 检查复核人字段是否落库 | 每一章都有记录 |
其中「数据准确性」最值得自动化。摘要里每一个百分比数字都要和数据源能对上,对不上的直接reject。做这一步时不要相信模型的自我修正——让模型自己检查自己往往无效。数据比对必须用规则脚本,不走模型。
5.2 批量回归:拿历史研报把生成系统考一遍
单份质检过了不算完,系统的价值要在批量场景验证。常见做法是取过去两年的真实历史研报数据,隐藏结论,把当时的行情和财报数据喂给DeepSeek生成,再对比机器生成版本和真实研报的差异。这个叫「用历史答案给新模型打分」。
回归脚本的逻辑:
# 假设历史报告数据和行情数据已整理 python evaluate_report_generation.py \ --input data/history_reports.json \ --api http://127.0.0.1:8000/v1/chat/completions \ --threshold 0.85脚本对每份历史研报执行三个检查:关键财务数据一致率、章节覆盖完整率、摘要与结论方向一致率。整体一致率低于85%就说明系统尚不可用,需要继续调提示词或数据层。批量回归的价值在于,它让系统改进有了参照——换提示词模板、换量化精度,效果好不好不再是玄学,跑完即知道。
数据分析阶段发现的一致率低的样例,应该单独捡出来看。通常问题出在「数据JSON字段缺失」和「提示词覆盖不全」两类。字段缺失是数据层没取到相应值,提示词覆盖不全是指某些章节没把关键数据放进去。这两类修正都发生在模型调用之前,而不是调模型本身。
5.3 人机复核流程:哪些环节必须人工看,哪些可以放机器
批量回归保证的是模型产出质量整体达标,但单一报告仍须人工复核。我建议按风险等级拆分复核节点。摘要和数据描述交给机器质检,如果一致率和格式都过了,分析师快速扫一遍即可。盈利预测、目标价、投资评级这几个章节,一律强制人工修改和签字,机器生成的内容仅作为底稿。这套划分在系统实现上就是把章节标记为「自动通过」和「必须复核」两类,在任务状态流转里做硬控制。
6. 进阶技巧:让DeepSeek从写初稿到「被追问也不翻车」
分析师拿到初稿之后不会直接接受,总会追问「把毛利率下滑原因再展开一段」「目标价依据重新描述」。这时如果直接把上一轮完整输出拼进提示词,上下文会越来越长,模型注意力被大量历史文本稀释,新生成的段落容易重复或跑偏。我常用的做法是:把上一轮生成的关键结论压缩成三五行摘要,放回系统提示词;新追问的原文片段走RAG检索回对应数据,再重新生成。这样上下文保持在模型稳重的长度区间,输出也不会因为上下文太长而退化。
另一个值得养成的习惯是把每次生成时表现好的提示词版本作为基准保存下来,改动前先复制一份。提示词调优是这个方案里边际收益最高的事情——数据层做好之后,同样的模型权重,提示词版本的差异就能带来20%以上的准确率提升。但要记得每次改动必须跑一遍5.2的批量回归,不能只看一两个样例下结论。我在这个项目上踩过最深的坑就是凭感觉改提示词,结果单份报告好看,批量一测集体翻车,从那以后版本基准和回归测试就成了固定流程。希望这套架构思路对你做券商研报自动化的部署有实际帮助。
本文还有配套的精品资源,点击获取