
简介这份257页PDF文档面向金融科技从业者、量化研究员与AI工程师系统讲解如何用DeepSeek-R1构建证券研报自动化生成方案解决人工研报撰写效率低、数据源异构、专业术语适配难等痛点。内容覆盖金融数据预处理、财经文本清洗、Embedding模型选型、Prompt工程、研报Schema设计、标注体系与数据增强、预训练语料融入、监督微调损失函数定制、分布式训练调度、梯度监控、分任务微调、LoRA与QLoRA低资源适配、术语库与知识库融合以及研报生成质量评估指标等完整链路。资源包共1个PDF文件大小11.71MB支持目录章节跳转与左侧书签大纲快速定位48个大章节条理清晰图表与文字显示正常。已有177人学习适合希望掌握金融数据分析与投资策略自动生成技术的中高级读者可据此搭建从数据到研报的端到端方案并借鉴评估与优化思路。1. 证券研报自动化生成从 257 页方案里拆出能跑的最小闭环一份 257 页的证券研报如果靠人工写从拉数据、算指标、画图、写结论到合规校对一个熟练分析师也要两三天。而 DeepSeek 证券研报自动化生成方案要解决的核心问题就是把这条链路里重复度最高的部分交给模型和脚本人只负责判断和签字。这不是让 AI 替你炒股而是让 AI 替你干那些你已经知道怎么干、但不想再干一遍的活。这套方案适合三类人一是手里有 Wind、Tushare 或聚宽数据源想批量产出行业周报、个股跟踪报告的量化从业者二是想把研报生成能力嵌进自己投研系统的工程师三是想用 DeepSeek API 做金融文本自动化的开发者。它不适合指望模型直接给买卖建议的人——合规红线在那里自动化生成的是分析框架和事实陈述不是投资决策。我见过太多人一上来就想搞全自动结果卡在数据清洗和格式对齐上。正确的做法是先跑通一个最小闭环一只股票、一个季度、一份三页的简版研报。跑通了再扩品种、扩模板、扩输出格式。2. 数据层与指标计算研报的骨架怎么搭2.1 数据源选型与字段对齐证券研报的数据来源通常分三类行情数据价格、成交量、换手率、财务数据三张表、衍生比率、另类数据舆情、研报摘要、公告文本。常见做法是用 Tushare Pro 拉 A 股基础数据用 AKShare 补宏观和行业数据用本地 CSV 存历史快照做回测校验。字段对齐是最容易被低估的环节。不同数据源的股票代码格式不一样Tushare 用000001.SZAKShare 用000001Wind 用000001.SZ。如果不做统一映射后面所有计算都会错位。我一般会在项目根目录建一个config/symbol_map.json把所有数据源的代码格式映射到内部统一格式。import pandas as pd import tushare as ts # 初始化 Tusharetoken 从环境变量读取不要硬编码 ts.set_token(os.environ[TUSHARE_TOKEN]) pro ts.pro_api() # 拉取单只股票最近 8 个季度的财务指标 df pro.fina_indicator( ts_code000001.SZ, start_date20230101, end_date20241231, fieldsts_code,end_date,roe,roa,grossprofit_margin,debt_to_assets,current_ratio ) # 统一列名方便后续模板引用 df df.rename(columns{ end_date: 报告期, roe: 净资产收益率, roa: 总资产收益率, grossprofit_margin: 毛利率, debt_to_assets: 资产负债率, current_ratio: 流动比率 }) # 按报告期升序排列确保时间序列正确 df df.sort_values(报告期).reset_index(dropTrue) print(df.head())这段代码做了三件事拉数据、改列名、排序。参数上注意start_date和end_date的格式是YYYYMMDDfields不指定会返回全部字段但速度慢。fina_indicator接口有频率限制免费账户每分钟调用次数有限批量拉取时要在循环里加time.sleep(0.5)。2.2 核心指标计算与异常值处理研报里真正有价值的不是原始财务数字而是衍生指标和同比环比变化。常见计算包括营收同比增速、净利润环比增速、ROE 三年均值、毛利率与行业均值差。这些计算本身不复杂坑在于异常值——比如某季度因为资产处置导致净利润暴增 500%直接放进研报会误导读者。我一般会设一个阈值规则同比增速超过 200% 或低于 -50% 的标记为异常在生成文本时自动加一句“主要受非经常性损益影响需结合公告进一步判断”。这个规则写在config/outlier_rules.yaml里方便调整。import numpy as np def calc_derived_metrics(df): 计算衍生指标并标记异常值 df[营收同比] df[营业收入].pct_change(4) * 100 # 4 个季度前对比 df[净利润环比] df[净利润].pct_change(1) * 100 # 异常值标记同比绝对值超过阈值 df[营收异常] np.where( df[营收同比].abs() 200, 异常, 正常 ) # ROE 三年滚动均值至少需要 12 个季度数据 df[ROE_3年均值] df[净资产收益率].rolling(window12, min_periods8).mean() return df df calc_derived_metrics(df)pct_change(4)里的 4 表示往前推 4 个季度对应同比。min_periods8是防止数据不足时算出空值。异常标记字段后续会传给 DeepSeek让模型在生成文本时自动带上风险提示。2.3 数据快照与版本管理研报生成最怕的是“上周跑出来的报告和这周数据对不上”。解决办法是每次生成前把原始数据和计算结果存一份带时间戳的快照。我一般用 Parquet 格式存比 CSV 小且读取快。# 按日期建目录存快照 mkdir -p snapshots/2025-01-15 python -c import pandas as pd df.to_parquet(snapshots/2025-01-15/financial_metrics.parquet) 快照目录不要提交到 Git用.gitignore排除。但配置文件、模板文件、提示词文件必须进版本控制这样任何一份历史报告都能追溯到当时的参数和提示词版本。3. DeepSeek 接入与提示词工程让模型说人话3.1 API 调用与参数配置DeepSeek API 的调用方式和 OpenAI 兼容改一下base_url就行。常见做法是用openai这个 Python 包把base_url指向 DeepSeek 的接口地址。关键参数有三个model选deepseek-chat还是deepseek-reasonertemperature控制随机性max_tokens控制输出长度。写研报正文时temperature我一般设 0.3 到 0.5。太低会死板太高会编造数字。max_tokens根据章节长度设单章节 800 到 1500 字比较合适设太大模型会啰嗦。from openai import OpenAI client OpenAI( api_keyos.environ[DEEPSEEK_API_KEY], base_urlhttps://api.deepseek.com/v1 # DeepSeek 兼容接口 ) def generate_section(prompt, max_tokens1200): 调用 DeepSeek 生成单个章节 response client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一名证券分析师输出严谨、客观不构成投资建议。}, {role: user, content: prompt} ], temperature0.4, max_tokensmax_tokens, streamFalse ) return response.choices[0].message.contentsystem消息里必须加合规声明这是硬性要求。streamFalse是因为研报生成是批处理场景不需要流式输出。如果要做交互式编辑可以改成streamTrue并逐块拼接。3.2 提示词模板设计把数据塞进上下文提示词的核心是把结构化数据转成模型能理解的叙述同时约束输出格式。我一般用 Jinja2 模板把指标数据、行业对比、异常标记都填进去。from jinja2 import Template PROMPT_TEMPLATE Template( 你正在撰写一份{{ stock_name }}{{ stock_code }}的季度跟踪研报。 以下是该股票最近 8 个季度的核心财务指标 {{ financial_table }} 行业均值对比 - 毛利率行业均值{{ industry_gross_margin }}% - 资产负债率行业均值{{ industry_debt_ratio }}% 异常标记{{ outlier_notes }} 请撰写「财务表现分析」章节要求 1. 先概述营收和利润的整体趋势 2. 指出至少两个值得关注的变化点 3. 如有异常值必须说明可能原因 4. 不要给出买入/卖出建议 5. 输出 600 到 900 字用段落不用列表 ) prompt PROMPT_TEMPLATE.render( stock_name平安银行, stock_code000001.SZ, financial_tabledf.to_markdown(indexFalse), industry_gross_margin28.5, industry_debt_ratio62.3, outlier_notes2024Q3 净利润同比 210%主要受非经常性损益影响 )to_markdown()把 DataFrame 转成 Markdown 表格模型对表格的理解比纯文本好。异常标记要显式传给模型否则它可能忽略。输出要求里明确“不要给买卖建议”这是合规底线。3.3 输出格式约束与后处理模型输出最大的问题是格式不稳定有时候用列表有时候用标题有时候多写一段。解决办法是在提示词里明确格式要求同时在代码里做后处理校验。import re def postprocess_section(text): 清理模型输出确保格式统一 # 去掉模型可能加的一级标题 text re.sub(r^#\s*, , text, flagsre.MULTILINE) # 去掉多余空行 text re.sub(r\n{3,}, \n\n, text) # 检查是否包含违规词 forbidden [买入, 卖出, 推荐, 目标价] for word in forbidden: if word in text: text text.replace(word, 需结合市场判断) return text.strip()后处理不是万能的但能挡住大部分格式问题和合规风险。更严格的做法是加一层规则引擎对输出做关键词扫描和长度校验不通过就重新生成。4. 报告组装与格式输出从 Markdown 到可交付文档4.1 章节拼接与目录生成单章节生成完后需要拼成完整报告。我一般用 Markdown 做中间格式因为结构清晰、容易转其他格式。拼接时按固定顺序封面、摘要、财务表现、行业对比、风险提示、免责声明。def assemble_report(sections, meta): 拼接完整研报 parts [] parts.append(f# {meta[stock_name]} 季度跟踪研报\n) parts.append(f报告日期{meta[date]}\n) parts.append(## 摘要\n) parts.append(sections.get(摘要, )) parts.append(\n## 财务表现分析\n) parts.append(sections.get(财务表现, )) parts.append(\n## 行业对比\n) parts.append(sections.get(行业对比, )) parts.append(\n## 风险提示\n) parts.append(sections.get(风险提示, )) parts.append(\n---\n免责声明本报告由自动化工具生成不构成投资建议。) return \n.join(parts)章节顺序不能乱摘要在最前免责声明在最后。每份报告必须带生成日期方便追溯。4.2 转 PDF 与 Word 的实操命令Markdown 转 PDF 常用pandoc转 Word 也用pandoc。命令不复杂但中文字体需要额外配置。# Markdown 转 PDF指定中文字体 pandoc report.md -o report.pdf \ --pdf-enginexelatex \ -V mainfontNoto Sans CJK SC \ -V geometry:margin2.5cm # Markdown 转 Word pandoc report.md -o report.docx \ --reference-doctemplate.docx--pdf-enginexelatex是必须的否则中文会乱码。mainfont指定系统中已有的中文字体Linux 上常用Noto Sans CJK SCmacOS 上用PingFang SC。reference-doc指定 Word 模板控制样式。4.3 批量生成与文件命名规范批量生成时文件命名要能一眼看出内容。我一般用{股票代码}_{股票名称}_{报告类型}_{日期}.pdf的格式。import os def batch_generate(stock_list, report_type季度跟踪): 批量生成研报 for stock in stock_list: sections generate_all_sections(stock) report_md assemble_report(sections, stock) filename f{stock[code]}_{stock[name]}_{report_type}_{stock[date]}.md filepath os.path.join(output, filename) with open(filepath, w, encodingutf-8) as f: f.write(report_md) # 转 PDF os.system(fpandoc {filepath} -o {filepath.replace(.md, .pdf)} f--pdf-enginexelatex -V mainfontNoto Sans CJK SC)批量生成时要注意 API 频率限制每只股票之间加time.sleep(2)。输出目录按日期分子目录避免文件太多不好找。5. 避坑与排查那些让我返工三次的问题5.1 模型编造财务数字现象生成的报告里出现了数据表里没有的营收数字且看起来“很合理”。原因提示词里没有明确禁止编造模型在上下文不足时会“补全”缺失数据。解决在 system 消息里加一句“所有数字必须来自用户提供的数据不得自行推算或编造”。同时在提示词里把数据表放在最前面减少模型“猜”的空间。后处理阶段用正则提取所有数字和数据表做交叉校验对不上的直接标红重新生成。5.2 章节之间数据不一致现象摘要里说营收增长 15%财务分析章节说增长 12%。原因每个章节独立调用 API模型每次看到的上下文不同或者数据表在两次调用之间被修改了。解决所有章节共用同一份数据快照生成前把数据冻结。摘要章节不单独计算而是从财务分析章节的输出里提取关键句。更稳妥的做法是先让模型生成一份“关键事实清单”所有章节都基于这份清单写。5.3 API 超时与重试策略现象批量生成时偶尔报Request timed out导致整批中断。原因DeepSeek API 在高负载时响应变慢默认超时时间不够。解决设置timeout60并加指数退避重试。import time from openai import APITimeoutError def call_with_retry(prompt, max_retries3): for attempt in range(max_retries): try: return generate_section(prompt) except APITimeoutError: wait 2 ** attempt print(f超时{wait}秒后重试...) time.sleep(wait) raise RuntimeError(API 连续超时请检查网络或降低并发)重试次数不要超过 3 次否则可能触发限流。并发数控制在 3 到 5 之间比较稳。5.4 PDF 中文乱码现象生成的 PDF 里中文全是方块或问号。原因LaTeX 默认字体不支持中文或者系统没装指定字体。解决先确认系统有中文字体fc-list :langzh。如果没有安装fonts-noto-cjk。pandoc 命令里必须加--pdf-enginexelatex和-V mainfont。如果还不行换用wkhtmltopdf引擎对中文支持更好但排版精度稍差。5.5 合规词漏网现象报告里出现了“建议买入”“目标价 25 元”等违规表述。原因模型在生成时可能忽略 system 消息里的合规约束尤其是当提示词很长时。解决后处理阶段加一层强制替换把“买入”“卖出”“推荐”“目标价”等词替换成中性表述。同时维护一个合规词库每次生成后扫描一遍命中就记录日志并人工复核。不要完全依赖模型自觉。6. 进阶技巧用缓存和并行把生成速度压到三分之一批量生成 50 只股票的研报如果串行调用 API按每只 6 个章节、每章节 3 秒算要 15 分钟。加上重试和转 PDF半小时起步。我后来用了两个技巧把时间压到了 8 分钟左右。第一个是提示词缓存。同一批股票里行业对比章节的提示词结构完全一样只有数据不同。我把提示词拆成“固定模板”和“动态数据”两部分固定部分用 DeepSeek 的上下文缓存机制如果接口支持或者本地缓存。具体做法是把 system 消息和模板骨架缓存起来每次只传变化的数据部分。这样能减少 token 消耗也能让模型更快进入状态。第二个是并行生成。用concurrent.futures的线程池同时跑 3 到 5 个章节。注意不要把所有章节都并行摘要章节依赖财务分析的结果必须等它完成。from concurrent.futures import ThreadPoolExecutor, as_completed def parallel_generate(stock): 并行生成不依赖的章节 tasks { 财务表现: (generate_section, build_financial_prompt(stock)), 行业对比: (generate_section, build_industry_prompt(stock)), 风险提示: (generate_section, build_risk_prompt(stock)) } results {} with ThreadPoolExecutor(max_workers3) as executor: futures { executor.submit(func, prompt): name for name, (func, prompt) in tasks.items() } for future in as_completed(futures): name futures[future] results[name] future.result() # 摘要依赖财务表现单独生成 results[摘要] generate_section( build_summary_prompt(stock, results[财务表现]) ) return resultsmax_workers3是保守设置API 限流严的话降到 2。并行生成时要注意日志输出会交错建议每个线程写自己的日志文件。验证生成质量的方法很简单随机抽 5 份报告人工核对数字和原文数据是否一致检查合规词是否清零看章节之间有没有矛盾。我一般会做一个quality_check.py脚本自动跑这三项检查不通过的报告标红。最后说一个血泪教训不要试图让模型一次生成整份报告。我试过把 257 页方案里的完整提示词塞进去结果模型输出到一半就断了而且前后数据对不上。拆成章节、逐段生成、最后拼接虽然麻烦一点但可控性高得多。自动化生成研报这件事慢就是快。希望帮到你。本文还有配套的精品资源点击获取