增加智瞰龙虎板块
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
"""
|
||||
智瞰龙虎PDF报告生成模块
|
||||
"""
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
||||
from datetime import datetime
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
class LonghubangPDFGenerator:
|
||||
"""龙虎榜PDF报告生成器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化PDF生成器"""
|
||||
self.setup_fonts()
|
||||
|
||||
def setup_fonts(self):
|
||||
"""设置中文字体"""
|
||||
try:
|
||||
# 尝试注册常见的中文字体
|
||||
font_paths = [
|
||||
'C:/Windows/Fonts/msyh.ttc', # 微软雅黑
|
||||
'C:/Windows/Fonts/simsun.ttc', # 宋体
|
||||
'C:/Windows/Fonts/simhei.ttf', # 黑体
|
||||
]
|
||||
|
||||
for font_path in font_paths:
|
||||
if os.path.exists(font_path):
|
||||
try:
|
||||
pdfmetrics.registerFont(TTFont('ChineseFont', font_path))
|
||||
self.chinese_font = 'ChineseFont'
|
||||
print(f"[PDF] 成功加载字体: {font_path}")
|
||||
return
|
||||
except:
|
||||
continue
|
||||
|
||||
# 如果都失败,使用默认字体
|
||||
self.chinese_font = 'Helvetica'
|
||||
print("[PDF] 警告: 未找到中文字体,使用默认字体")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PDF] 字体设置失败: {e}")
|
||||
self.chinese_font = 'Helvetica'
|
||||
|
||||
def generate_pdf(self, result_data: dict, output_path: str = None) -> str:
|
||||
"""
|
||||
生成龙虎榜分析PDF报告
|
||||
|
||||
Args:
|
||||
result_data: 分析结果数据
|
||||
output_path: 输出路径,如果为None则生成临时文件
|
||||
|
||||
Returns:
|
||||
PDF文件路径
|
||||
"""
|
||||
try:
|
||||
# 如果没有指定输出路径,创建临时文件
|
||||
if output_path is None:
|
||||
temp_dir = tempfile.gettempdir()
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_path = os.path.join(temp_dir, f"智瞰龙虎报告_{timestamp}.pdf")
|
||||
|
||||
# 创建PDF文档
|
||||
doc = SimpleDocTemplate(
|
||||
output_path,
|
||||
pagesize=A4,
|
||||
rightMargin=0.5*inch,
|
||||
leftMargin=0.5*inch,
|
||||
topMargin=0.5*inch,
|
||||
bottomMargin=0.5*inch
|
||||
)
|
||||
|
||||
# 构建内容
|
||||
story = []
|
||||
|
||||
# 添加标题页
|
||||
story.extend(self._create_title_page(result_data))
|
||||
story.append(PageBreak())
|
||||
|
||||
# 添加数据概况
|
||||
story.extend(self._create_data_overview(result_data))
|
||||
story.append(PageBreak())
|
||||
|
||||
# 添加推荐股票
|
||||
story.extend(self._create_recommended_stocks(result_data))
|
||||
story.append(PageBreak())
|
||||
|
||||
# 添加AI分析师报告
|
||||
story.extend(self._create_agents_analysis(result_data))
|
||||
|
||||
# 生成PDF
|
||||
doc.build(story)
|
||||
|
||||
print(f"[PDF] 龙虎榜报告生成成功: {output_path}")
|
||||
return output_path
|
||||
|
||||
except Exception as e:
|
||||
print(f"[PDF] 生成失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def _get_styles(self) -> dict:
|
||||
"""获取样式"""
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# 自定义样式
|
||||
custom_styles = {
|
||||
'Title': ParagraphStyle(
|
||||
'CustomTitle',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=24,
|
||||
textColor=colors.HexColor('#1f4788'),
|
||||
spaceAfter=30,
|
||||
alignment=TA_CENTER,
|
||||
fontName=self.chinese_font
|
||||
),
|
||||
'Heading1': ParagraphStyle(
|
||||
'CustomHeading1',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=16,
|
||||
textColor=colors.HexColor('#1f4788'),
|
||||
spaceAfter=12,
|
||||
spaceBefore=12,
|
||||
fontName=self.chinese_font
|
||||
),
|
||||
'Heading2': ParagraphStyle(
|
||||
'CustomHeading2',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=14,
|
||||
textColor=colors.HexColor('#2c5aa0'),
|
||||
spaceAfter=10,
|
||||
spaceBefore=10,
|
||||
fontName=self.chinese_font
|
||||
),
|
||||
'Heading3': ParagraphStyle(
|
||||
'CustomHeading3',
|
||||
parent=styles['Heading3'],
|
||||
fontSize=12,
|
||||
textColor=colors.HexColor('#3d6db5'),
|
||||
spaceAfter=8,
|
||||
spaceBefore=8,
|
||||
fontName=self.chinese_font
|
||||
),
|
||||
'Normal': ParagraphStyle(
|
||||
'CustomNormal',
|
||||
parent=styles['Normal'],
|
||||
fontSize=10,
|
||||
leading=14,
|
||||
fontName=self.chinese_font,
|
||||
alignment=TA_JUSTIFY
|
||||
),
|
||||
'Small': ParagraphStyle(
|
||||
'CustomSmall',
|
||||
parent=styles['Normal'],
|
||||
fontSize=9,
|
||||
leading=12,
|
||||
fontName=self.chinese_font
|
||||
)
|
||||
}
|
||||
|
||||
return custom_styles
|
||||
|
||||
def _create_title_page(self, data: dict) -> list:
|
||||
"""创建标题页"""
|
||||
styles = self._get_styles()
|
||||
elements = []
|
||||
|
||||
# 添加空白
|
||||
elements.append(Spacer(1, 2*inch))
|
||||
|
||||
# 主标题
|
||||
title = Paragraph("智瞰龙虎榜分析报告", styles['Title'])
|
||||
elements.append(title)
|
||||
elements.append(Spacer(1, 0.5*inch))
|
||||
|
||||
# 副标题
|
||||
subtitle = Paragraph("AI驱动的龙虎榜多维度分析系统", styles['Heading2'])
|
||||
elements.append(subtitle)
|
||||
elements.append(Spacer(1, 1*inch))
|
||||
|
||||
# 报告信息
|
||||
timestamp = data.get('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
||||
data_info = data.get('data_info', {})
|
||||
|
||||
info_text = f"""
|
||||
<para align=center>
|
||||
<b>生成时间:</b> {timestamp}<br/>
|
||||
<b>数据记录:</b> {data_info.get('total_records', 0)} 条<br/>
|
||||
<b>涉及股票:</b> {data_info.get('total_stocks', 0)} 只<br/>
|
||||
<b>涉及游资:</b> {data_info.get('total_youzi', 0)} 个<br/>
|
||||
<b>AI分析师:</b> 5位专业分析师团队<br/>
|
||||
<b>分析模型:</b> DeepSeek AI Multi-Agent System
|
||||
</para>
|
||||
"""
|
||||
info = Paragraph(info_text, styles['Normal'])
|
||||
elements.append(info)
|
||||
elements.append(Spacer(1, 1*inch))
|
||||
|
||||
# 免责声明
|
||||
disclaimer = Paragraph(
|
||||
"<para align=center><i>本报告由AI系统基于龙虎榜公开数据自动生成,仅供参考,不构成投资建议。<br/>"
|
||||
"市场有风险,投资需谨慎。请投资者独立判断并承担投资风险。</i></para>",
|
||||
styles['Normal']
|
||||
)
|
||||
elements.append(disclaimer)
|
||||
|
||||
return elements
|
||||
|
||||
def _create_data_overview(self, data: dict) -> list:
|
||||
"""创建数据概况部分"""
|
||||
styles = self._get_styles()
|
||||
elements = []
|
||||
|
||||
# 标题
|
||||
elements.append(Paragraph("一、数据概况", styles['Heading1']))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
data_info = data.get('data_info', {})
|
||||
summary = data_info.get('summary', {})
|
||||
|
||||
# 基本统计
|
||||
overview_text = f"""
|
||||
<para>
|
||||
本次分析共涵盖 <b>{data_info.get('total_records', 0)}</b> 条龙虎榜记录,
|
||||
涉及 <b>{data_info.get('total_stocks', 0)}</b> 只股票和
|
||||
<b>{data_info.get('total_youzi', 0)}</b> 个游资席位。<br/><br/>
|
||||
|
||||
<b>资金概况:</b><br/>
|
||||
• 总买入金额: {summary.get('total_buy_amount', 0):,.2f} 元<br/>
|
||||
• 总卖出金额: {summary.get('total_sell_amount', 0):,.2f} 元<br/>
|
||||
• 净流入金额: {summary.get('total_net_inflow', 0):,.2f} 元
|
||||
</para>
|
||||
"""
|
||||
elements.append(Paragraph(overview_text, styles['Normal']))
|
||||
elements.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# TOP游资
|
||||
if summary.get('top_youzi'):
|
||||
elements.append(Paragraph("1.1 活跃游资 TOP10", styles['Heading2']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
table_data = [['排名', '游资名称', '净流入金额(元)']]
|
||||
for idx, (name, amount) in enumerate(list(summary['top_youzi'].items())[:10], 1):
|
||||
table_data.append([str(idx), name, f"{amount:,.2f}"])
|
||||
|
||||
table = Table(table_data, colWidths=[0.8*inch, 3.5*inch, 2*inch])
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1f4788')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 10),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 9),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||
]))
|
||||
elements.append(table)
|
||||
elements.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# TOP股票
|
||||
if summary.get('top_stocks'):
|
||||
elements.append(Paragraph("1.2 资金净流入 TOP20 股票", styles['Heading2']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
table_data = [['排名', '股票代码', '股票名称', '净流入金额(元)']]
|
||||
for idx, stock in enumerate(summary['top_stocks'][:20], 1):
|
||||
table_data.append([
|
||||
str(idx),
|
||||
stock['code'],
|
||||
stock['name'],
|
||||
f"{stock['net_inflow']:,.2f}"
|
||||
])
|
||||
|
||||
table = Table(table_data, colWidths=[0.8*inch, 1.2*inch, 2*inch, 2.3*inch])
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1f4788')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 10),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 8),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||
]))
|
||||
elements.append(table)
|
||||
elements.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# 热门概念
|
||||
if summary.get('hot_concepts'):
|
||||
elements.append(Paragraph("1.3 热门概念 TOP15", styles['Heading2']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
concepts_text = ""
|
||||
for idx, (concept, count) in enumerate(list(summary['hot_concepts'].items())[:15], 1):
|
||||
concepts_text += f"{idx}. {concept} ({count}次) "
|
||||
if idx % 3 == 0:
|
||||
concepts_text += "<br/>"
|
||||
|
||||
elements.append(Paragraph(concepts_text, styles['Normal']))
|
||||
|
||||
return elements
|
||||
|
||||
def _create_recommended_stocks(self, data: dict) -> list:
|
||||
"""创建推荐股票部分"""
|
||||
styles = self._get_styles()
|
||||
elements = []
|
||||
|
||||
# 标题
|
||||
elements.append(Paragraph("二、AI推荐股票", styles['Heading1']))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
recommended = data.get('recommended_stocks', [])
|
||||
|
||||
if not recommended:
|
||||
elements.append(Paragraph("暂无推荐股票", styles['Normal']))
|
||||
return elements
|
||||
|
||||
# 说明
|
||||
intro_text = f"""
|
||||
<para>
|
||||
基于5位AI分析师的综合分析,系统识别出以下 <b>{len(recommended)}</b> 只潜力股票,
|
||||
这些股票在资金流向、游资关注度、题材热度等多个维度表现突出。
|
||||
</para>
|
||||
"""
|
||||
elements.append(Paragraph(intro_text, styles['Normal']))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# 推荐股票表格
|
||||
table_data = [['排名', '股票代码', '股票名称', '净流入金额', '确定性', '持有周期']]
|
||||
for stock in recommended[:10]:
|
||||
table_data.append([
|
||||
str(stock.get('rank', '-')),
|
||||
stock.get('code', '-'),
|
||||
stock.get('name', '-'),
|
||||
f"{stock.get('net_inflow', 0):,.0f}",
|
||||
stock.get('confidence', '-'),
|
||||
stock.get('hold_period', '-')
|
||||
])
|
||||
|
||||
table = Table(table_data, colWidths=[0.7*inch, 1.2*inch, 1.5*inch, 1.5*inch, 0.9*inch, 1*inch])
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1f4788')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, -1), self.chinese_font),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 9),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 8),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||
]))
|
||||
elements.append(table)
|
||||
elements.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# 详细推荐理由
|
||||
elements.append(Paragraph("2.1 推荐理由详解", styles['Heading2']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
for stock in recommended[:5]: # 只详细展示前5只
|
||||
stock_text = f"""
|
||||
<para>
|
||||
<b>{stock.get('rank', '-')}. {stock.get('name', '-')} ({stock.get('code', '-')})</b><br/>
|
||||
推荐理由: {stock.get('reason', '暂无')}<br/>
|
||||
确定性: {stock.get('confidence', '-')} | 持有周期: {stock.get('hold_period', '-')}
|
||||
</para>
|
||||
"""
|
||||
elements.append(Paragraph(stock_text, styles['Small']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
return elements
|
||||
|
||||
def _create_agents_analysis(self, data: dict) -> list:
|
||||
"""创建AI分析师报告部分"""
|
||||
styles = self._get_styles()
|
||||
elements = []
|
||||
|
||||
agents_analysis = data.get('agents_analysis', {})
|
||||
|
||||
if not agents_analysis:
|
||||
return elements
|
||||
|
||||
# 标题
|
||||
elements.append(Paragraph("三、AI分析师报告", styles['Heading1']))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# 分析师介绍
|
||||
intro_text = """
|
||||
<para>
|
||||
本报告由5位AI专业分析师从不同维度进行分析,综合形成投资建议:<br/>
|
||||
• <b>游资行为分析师</b> - 分析游资操作特征和意图<br/>
|
||||
• <b>个股潜力分析师</b> - 挖掘次日大概率上涨的股票<br/>
|
||||
• <b>题材追踪分析师</b> - 识别热点题材和轮动机会<br/>
|
||||
• <b>风险控制专家</b> - 识别高风险股票和市场陷阱<br/>
|
||||
• <b>首席策略师</b> - 综合研判并给出最终建议
|
||||
</para>
|
||||
"""
|
||||
elements.append(Paragraph(intro_text, styles['Normal']))
|
||||
elements.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# 各分析师报告
|
||||
agent_titles = {
|
||||
'youzi': '3.1 游资行为分析师',
|
||||
'stock': '3.2 个股潜力分析师',
|
||||
'theme': '3.3 题材追踪分析师',
|
||||
'risk': '3.4 风险控制专家',
|
||||
'chief': '3.5 首席策略师综合研判'
|
||||
}
|
||||
|
||||
for agent_key, agent_title in agent_titles.items():
|
||||
agent_data = agents_analysis.get(agent_key, {})
|
||||
if agent_data:
|
||||
elements.append(Paragraph(agent_title, styles['Heading2']))
|
||||
elements.append(Spacer(1, 0.1*inch))
|
||||
|
||||
analysis_text = agent_data.get('analysis', '暂无分析')
|
||||
# 截断过长的文本
|
||||
if len(analysis_text) > 3000:
|
||||
analysis_text = analysis_text[:3000] + "\n...(内容过长,已截断)"
|
||||
|
||||
elements.append(Paragraph(analysis_text, styles['Small']))
|
||||
elements.append(Spacer(1, 0.2*inch))
|
||||
|
||||
if agent_key != 'chief':
|
||||
elements.append(PageBreak())
|
||||
|
||||
return elements
|
||||
|
||||
|
||||
# 测试函数
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("测试智瞰龙虎PDF生成模块")
|
||||
print("=" * 60)
|
||||
|
||||
# 创建测试数据
|
||||
test_data = {
|
||||
'timestamp': '2024-01-15 18:30:00',
|
||||
'data_info': {
|
||||
'total_records': 150,
|
||||
'total_stocks': 50,
|
||||
'total_youzi': 30,
|
||||
'summary': {
|
||||
'total_buy_amount': 500000000,
|
||||
'total_sell_amount': 200000000,
|
||||
'total_net_inflow': 300000000,
|
||||
'top_youzi': {
|
||||
'92科比': 14455321,
|
||||
'赵老哥': 12000000
|
||||
},
|
||||
'top_stocks': [
|
||||
{'code': '001337', 'name': '四川黄金', 'net_inflow': 14455321}
|
||||
],
|
||||
'hot_concepts': {
|
||||
'黄金概念': 10,
|
||||
'新能源': 8
|
||||
}
|
||||
}
|
||||
},
|
||||
'recommended_stocks': [
|
||||
{
|
||||
'rank': 1,
|
||||
'code': '001337',
|
||||
'name': '四川黄金',
|
||||
'net_inflow': 14455321,
|
||||
'reason': '游资大幅买入',
|
||||
'confidence': '高',
|
||||
'hold_period': '短线'
|
||||
}
|
||||
],
|
||||
'agents_analysis': {
|
||||
'chief': {
|
||||
'analysis': '综合分析显示...'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 生成PDF
|
||||
generator = LonghubangPDFGenerator()
|
||||
pdf_path = generator.generate_pdf(test_data)
|
||||
print(f"\nPDF已生成: {pdf_path}")
|
||||
|
||||
Reference in New Issue
Block a user