skills/financial-report-writing/scripts/generate_report.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

314 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
金融研究报告生成器
整合数据获取、分析和文档生成功能,一键生成完整报告
"""
import sys
import argparse
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional
# 添加脚本目录到路径
sys.path.insert(0, str(Path(__file__).parent))
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from data_fetcher import WindDataFetcher, fetch_stock_report_data
from document_formatter import create_formatted_document, GBT9704Formatter
from analysis_engine import perform_comprehensive_analysis
class ReportGenerator:
"""
金融研究报告生成器
"""
def __init__(self):
self.doc = None
self.formatter = None
self.analysis_results = None
def generate_report(self,
stock_code: str,
stock_name: str = None,
organization: str = "某某证券研究所",
author: str = "",
include_cover: bool = True,
output_path: str = None) -> str:
"""
生成完整的投资研究报告
Args:
stock_code: 股票代码,如 '300866.SZ'
stock_name: 股票名称(如不提供则自动获取)
organization: 研究机构名称
author: 分析师姓名
include_cover: 是否包含封面
output_path: 输出文件路径
Returns:
生成的文件路径
"""
print(f"开始生成 {stock_code} 的投资研究报告...")
# 1. 获取数据
print("正在获取股票数据...")
try:
price_df, financial_data = fetch_stock_report_data(stock_code, days_back=90, weekly=True)
except Exception as e:
print(f"数据获取失败: {e}")
raise
# 如果未提供股票名称,从财务数据获取
if stock_name is None:
stock_name = financial_data.get('sec_name', stock_code)
print(f"获取到 {len(price_df)} 条周线数据")
# 2. 执行分析
print("正在执行投资分析...")
current_price = price_df['close'].iloc[-1]
self.analysis_results = perform_comprehensive_analysis(
price_df, financial_data, current_price
)
# 3. 创建文档
print("正在生成Word文档...")
self.doc, self.formatter = create_formatted_document()
# 4. 添加封面(可选)
if include_cover:
self._add_cover_page(stock_name, organization)
# 5. 添加免责声明
self._add_disclaimer()
# 6. 添加核心提要
self._add_core_summary(stock_name)
# 7. 添加投资评级
self._add_investment_rating(stock_name, current_price)
# 8. 添加核心观点
self._add_core_views()
# 9. 添加市场表现回顾
self._add_market_review(price_df)
# 10. 添加页脚
self.formatter.add_page_number_footer()
# 11. 保存文档
if output_path is None:
date_str = datetime.now().strftime('%Y%m%d')
output_path = f"{stock_name}_{stock_code.replace('.', '_')}_研究报告_{date_str}.docx"
self.doc.save(output_path)
print(f"报告已保存: {output_path}")
return output_path
def _add_cover_page(self, stock_name: str, organization: str):
"""添加封面页"""
date_str = datetime.now().strftime('%Y年%m月%d')
self.formatter.add_cover_page(
title=f"{stock_name}投资研究报告",
organization=organization,
date_str=date_str
)
def _add_disclaimer(self):
"""添加免责声明"""
self.formatter.add_heading('免责声明', level=1)
self.formatter.add_paragraph(
"本报告所载资料的来源及观点皆为公开信息,但并不能保证其准确性和完整性。本报告仅供参考,"
"不构成任何投资建议或承诺,投资者应审慎决策,独立判断,自行承担投资风险。"
)
self.formatter.add_paragraph(
"本报告版权归本公司所有,未经书面许可,任何机构和个人不得以任何形式翻版、复制、刊登、"
"发表或引用。如征得本公司同意进行引用、刊发的,需在允许的范围内使用,并注明出处为"
"'某某证券研究所',且不得对本报告进行任何有悖原意的引用、删节和修改。"
)
def _add_core_summary(self, stock_name: str):
"""添加核心提要"""
self.formatter.add_heading('核心提要', level=1)
summary_points = self.analysis_results['summary_points']
for i, point in enumerate(summary_points, 1):
# 添加项目符号
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(f"{i}. {point}")
self.formatter.set_font(run, '仿宋', 14)
paragraph.paragraph_format.line_spacing = Pt(28)
paragraph.paragraph_format.line_spacing_rule = 2
paragraph.paragraph_format.space_before = Pt(0)
paragraph.paragraph_format.space_after = Pt(0)
def _add_investment_rating(self, stock_name: str, current_price: float):
"""添加投资评级"""
self.formatter.add_heading('一、投资评级', level=1)
# 表格数据
recommendation = self.analysis_results['recommendation']
data = [[
recommendation.rating.value,
f"{recommendation.target_price:.2f}",
(datetime.now() + timedelta(days=365)).strftime('%Y年%m月%d'),
recommendation.risk_level.value
]]
self.formatter.add_three_line_table(
data,
['投资建议', '目标价格', '有效期', '风险等级'],
font_name='仿宋',
size=12
)
# 添加说明文字
self.formatter.add_paragraph(
f"当前股价:{current_price:.2f}元。"
f"目标价较当前价格{'上涨' if recommendation.target_price > current_price else '下跌'}"
f"{abs((recommendation.target_price / current_price - 1) * 100):.2f}%。"
)
def _add_core_views(self):
"""添加核心观点"""
self.formatter.add_heading('二、核心观点', level=1)
# 基本面分析
fundamental = self.analysis_results['fundamental']
self.formatter.add_heading('(一)基本面分析', level=2)
self.formatter.add_paragraph(
f"1. 盈利能力公司最新净资产收益率ROE{fundamental.roe}%"
f"{fundamental.roe_comment}"
)
self.formatter.add_paragraph(
f"2. 估值水平当前市盈率TTM{fundamental.pe_ttm}倍,"
f"市净率LF{fundamental.pb_lf}倍。{fundamental.valuation_comment}"
)
self.formatter.add_paragraph(f"3. 结论:{fundamental.fundamental_summary}")
# 技术面分析
technical = self.analysis_results['technical']
self.formatter.add_heading('(二)技术面分析', level=2)
self.formatter.add_paragraph(
f"1. 价格趋势:近期股价呈现{technical.trend}态势,"
f"{technical.ma_status}"
)
self.formatter.add_paragraph(
f"2. 支撑与阻力:近期支撑位{technical.support_level}元,"
f"阻力位{technical.resistance_level}元。"
)
self.formatter.add_paragraph(
f"3. 波动率:近期年化波动率为{technical.volatility}%"
f"{'属于高波动品种' if technical.volatility > 40 else '波动适中' if technical.volatility > 25 else '波动较低'}"
)
# 投资建议
recommendation = self.analysis_results['recommendation']
self.formatter.add_heading('(三)投资建议', level=2)
self.formatter.add_paragraph(recommendation.recommendation_text)
self.formatter.add_paragraph("主要投资逻辑:")
for factor in recommendation.key_factors:
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(f"{factor}")
self.formatter.set_font(run, '仿宋', 14)
paragraph.paragraph_format.line_spacing = Pt(28)
paragraph.paragraph_format.line_spacing_rule = 2
paragraph.paragraph_format.first_line_indent = Pt(28)
def _add_market_review(self, price_df):
"""添加市场表现回顾"""
self.formatter.add_heading('三、市场表现回顾', level=1)
# 价格走势分析
self.formatter.add_heading('(一)价格走势分析', level=2)
price_stats = self.analysis_results['price_stats']
self.formatter.add_paragraph(
f"近期股价最高达到{price_stats['highest_price']}元,"
f"最低下探至{price_stats['lowest_price']}元,"
f"区间累计涨跌幅为{price_stats['total_change']:.2f}%。"
f"最新收盘价为{price_stats['latest_price']}元,"
f"区间平均价格为{price_stats['avg_price']}元。"
)
# K线数据表
self.formatter.add_heading('K线数据', level=2)
# 准备表格数据最近13周
recent_data = price_df.tail(13).copy()
table_data = []
for _, row in recent_data.iterrows():
date_str = row['date'].strftime('%Y-%m-%d') if hasattr(row['date'], 'strftime') else str(row['date'])[:10]
table_data.append([
date_str,
f"{row['open']:.2f}",
f"{row['high']:.2f}",
f"{row['low']:.2f}",
f"{row['close']:.2f}",
f"{row.get('pct_chg', 0):.2f}%",
f"{row.get('volume', 0) / 10000:.0f}"
])
self.formatter.add_three_line_table(
table_data,
['日期', '开盘价', '最高价', '最低价', '收盘价', '涨跌幅', '成交量(万股)'],
font_name='仿宋',
size=12
)
# 数据来源
self.formatter.add_data_source_footnote()
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='生成金融投资研究报告')
parser.add_argument('stock_code', help='股票代码,如 300866.SZ 或 000001')
parser.add_argument('--name', '-n', help='股票名称')
parser.add_argument('--org', '-o', default='某某证券研究所', help='研究机构名称')
parser.add_argument('--author', '-a', default='', help='分析师姓名')
parser.add_argument('--no-cover', action='store_true', help='不包含封面')
parser.add_argument('--output', '-p', help='输出文件路径')
args = parser.parse_args()
# 标准化股票代码
stock_code = args.stock_code
if '.' not in stock_code:
if stock_code.startswith('6'):
stock_code = f"{stock_code}.SH"
else:
stock_code = f"{stock_code}.SZ"
generator = ReportGenerator()
try:
output_path = generator.generate_report(
stock_code=stock_code,
stock_name=args.name,
organization=args.org,
author=args.author,
include_cover=not args.no_cover,
output_path=args.output
)
print(f"\n报告生成成功: {output_path}")
except Exception as e:
print(f"\n报告生成失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()