@@ -7,6 +7,8 @@ from datetime import datetime
|
||||
import time
|
||||
import base64
|
||||
import os
|
||||
# 从新的配置文件导入model_options
|
||||
from model_config import model_options
|
||||
|
||||
from stock_data import StockDataFetcher
|
||||
from ai_agents import StockAnalysisAgents
|
||||
@@ -35,10 +37,7 @@ def model_selector():
|
||||
st.sidebar.markdown("---")
|
||||
st.sidebar.subheader("🤖 AI模型选择")
|
||||
|
||||
model_options = {
|
||||
"deepseek-chat": "DeepSeek Chat (默认)",
|
||||
"deepseek-reasoner": "DeepSeek Reasoner (推理增强)"
|
||||
}
|
||||
|
||||
|
||||
selected_model = st.sidebar.selectbox(
|
||||
"选择AI模型",
|
||||
@@ -2064,6 +2063,10 @@ def display_config_manager():
|
||||
with tab1:
|
||||
st.markdown("### DeepSeek API配置")
|
||||
st.markdown("DeepSeek是系统的核心AI引擎,必须配置才能使用分析功能。")
|
||||
st.markdown("DeepSeek:https://api.deepseek.com/v1")
|
||||
st.markdown("硅基流动:https://api.siliconflow.cn/v1")
|
||||
st.markdown("火山引擎:https://ark.cn-beijing.volces.com/api/v3")
|
||||
st.markdown("阿里:https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
|
||||
# DeepSeek API Key
|
||||
api_key_info = config_info["DEEPSEEK_API_KEY"]
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
container_name: agentsstock1
|
||||
ports:
|
||||
- "8503:8501"
|
||||
- "8503:8503"
|
||||
volumes:
|
||||
# 数据和数据库持久化 - 使用目录挂载而不是文件挂载
|
||||
- ./data:/app/data
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
networks:
|
||||
- agentsstock-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8503/_stcore/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
+155
-4
@@ -136,9 +136,12 @@ def display_analysis_tab():
|
||||
)
|
||||
|
||||
with col3:
|
||||
# 导入model_config.py中定义的model_options
|
||||
from model_config import model_options as app_model_options
|
||||
selected_model = st.selectbox(
|
||||
"AI模型",
|
||||
["deepseek-chat", "deepseek-reasoner"],
|
||||
list(app_model_options.keys()),
|
||||
format_func=lambda x: app_model_options[x],
|
||||
help="Reasoner模型提供更强的推理能力"
|
||||
)
|
||||
|
||||
@@ -721,12 +724,12 @@ def display_visualizations(result):
|
||||
def display_pdf_export_section(result):
|
||||
"""显示PDF导出功能"""
|
||||
|
||||
st.markdown("### 📄 导出PDF报告")
|
||||
st.markdown("### 📄 导出报告")
|
||||
|
||||
col1, col2 = st.columns([3, 1])
|
||||
col1, col2, col3 = st.columns([2, 1, 1])
|
||||
|
||||
with col1:
|
||||
st.info("💡 点击按钮生成并下载专业的PDF分析报告")
|
||||
st.info("💡 点击按钮生成并下载专业分析报告")
|
||||
|
||||
with col2:
|
||||
if st.button("📥 生成PDF", type="primary", width='stretch'):
|
||||
@@ -753,6 +756,154 @@ def display_pdf_export_section(result):
|
||||
except Exception as e:
|
||||
st.error(f"❌ PDF生成失败: {str(e)}")
|
||||
|
||||
with col3:
|
||||
if st.button("📝 生成Markdown", type="secondary", width='stretch'):
|
||||
with st.spinner("正在生成Markdown报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
markdown_content = generate_markdown_report(result)
|
||||
|
||||
# 提供下载
|
||||
st.download_button(
|
||||
label="📥 下载Markdown报告",
|
||||
data=markdown_content,
|
||||
file_name=f"智瞰龙虎报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md",
|
||||
mime="text/markdown",
|
||||
width='stretch'
|
||||
)
|
||||
|
||||
st.success("✅ Markdown报告生成成功!")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ Markdown生成失败: {str(e)}")
|
||||
|
||||
|
||||
def generate_markdown_report(result_data: dict) -> str:
|
||||
"""生成龙虎榜分析Markdown报告"""
|
||||
|
||||
# 获取当前时间
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
|
||||
|
||||
# 标题页
|
||||
markdown_content = f"""# 智瞰龙虎榜分析报告
|
||||
|
||||
**AI驱动的龙虎榜多维度分析系统**
|
||||
|
||||
---
|
||||
|
||||
## 📊 报告概览
|
||||
|
||||
- **生成时间**: {current_time}
|
||||
- **数据记录**: {result_data.get('data_info', {}).get('total_records', 0)} 条
|
||||
- **涉及股票**: {result_data.get('data_info', {}).get('total_stocks', 0)} 只
|
||||
- **涉及游资**: {result_data.get('data_info', {}).get('total_youzi', 0)} 个
|
||||
- **AI分析师**: 5位专业分析师团队
|
||||
- **分析模型**: DeepSeek AI Multi-Agent System
|
||||
|
||||
> ⚠️ 本报告由AI系统基于龙虎榜公开数据自动生成,仅供参考,不构成投资建议。市场有风险,投资需谨慎。
|
||||
|
||||
---
|
||||
|
||||
## 📈 数据概况
|
||||
|
||||
本次分析共涵盖 **{result_data.get('data_info', {}).get('total_records', 0)}** 条龙虎榜记录,
|
||||
涉及 **{result_data.get('data_info', {}).get('total_stocks', 0)}** 只股票和
|
||||
**{result_data.get('data_info', {}).get('total_youzi', 0)}** 个游资席位。
|
||||
|
||||
"""
|
||||
|
||||
# 资金概况
|
||||
summary = result_data.get('data_info', {}).get('summary', {})
|
||||
markdown_content += f"""
|
||||
### 💰 资金概况
|
||||
|
||||
- **总买入金额**: {summary.get('total_buy_amount', 0):,.2f} 元
|
||||
- **总卖出金额**: {summary.get('total_sell_amount', 0):,.2f} 元
|
||||
- **净流入金额**: {summary.get('total_net_inflow', 0):,.2f} 元
|
||||
|
||||
"""
|
||||
|
||||
# TOP游资
|
||||
if summary.get('top_youzi'):
|
||||
markdown_content += "### 🏆 活跃游资 TOP10\n\n| 排名 | 游资名称 | 净流入金额(元) |\n|------|----------|---------------|\n"
|
||||
for idx, (name, amount) in enumerate(list(summary['top_youzi'].items())[:10], 1):
|
||||
markdown_content += f"| {idx} | {name} | {amount:,.2f} |\n"
|
||||
markdown_content += "\n"
|
||||
|
||||
# TOP股票
|
||||
if summary.get('top_stocks'):
|
||||
markdown_content += "### 📈 资金净流入 TOP20 股票\n\n| 排名 | 股票代码 | 股票名称 | 净流入金额(元) |\n|------|----------|----------|---------------|\n"
|
||||
for idx, stock in enumerate(summary['top_stocks'][:20], 1):
|
||||
markdown_content += f"| {idx} | {stock['code']} | {stock['name']} | {stock['net_inflow']:,.2f} |\n"
|
||||
markdown_content += "\n"
|
||||
|
||||
# 热门概念
|
||||
if summary.get('hot_concepts'):
|
||||
markdown_content += "### 🔥 热门概念 TOP15\n\n"
|
||||
for idx, (concept, count) in enumerate(list(summary['hot_concepts'].items())[:15], 1):
|
||||
markdown_content += f"{idx}. {concept} ({count}次) \n"
|
||||
markdown_content += "\n"
|
||||
|
||||
# 推荐股票
|
||||
recommended = result_data.get('recommended_stocks', [])
|
||||
if recommended:
|
||||
markdown_content += f"""
|
||||
## 🎯 AI推荐股票
|
||||
|
||||
基于5位AI分析师的综合分析,系统识别出以下 **{len(recommended)}** 只潜力股票,
|
||||
这些股票在资金流向、游资关注度、题材热度等多个维度表现突出。
|
||||
|
||||
### 推荐股票清单
|
||||
|
||||
| 排名 | 股票代码 | 股票名称 | 净流入金额 | 确定性 | 持有周期 |
|
||||
|------|----------|----------|------------|--------|----------|
|
||||
"""
|
||||
for stock in recommended[:10]:
|
||||
markdown_content += f"| {stock.get('rank', '-')} | {stock.get('code', '-')} | {stock.get('name', '-')} | {stock.get('net_inflow', 0):,.0f} | {stock.get('confidence', '-')} | {stock.get('hold_period', '-')} |\n"
|
||||
|
||||
markdown_content += "\n### 推荐理由详解\n\n"
|
||||
for stock in recommended[:5]: # 只详细展示前5只
|
||||
markdown_content += f"**{stock.get('rank', '-')}. {stock.get('name', '-')} ({stock.get('code', '-')})**\n\n"
|
||||
markdown_content += f"- 推荐理由: {stock.get('reason', '暂无')}\n"
|
||||
markdown_content += f"- 确定性: {stock.get('confidence', '-')}\n"
|
||||
markdown_content += f"- 持有周期: {stock.get('hold_period', '-')}\n\n"
|
||||
|
||||
# AI分析师报告
|
||||
agents_analysis = result_data.get('agents_analysis', {})
|
||||
if agents_analysis:
|
||||
markdown_content += "## 🤖 AI分析师报告\n\n"
|
||||
markdown_content += "本报告由5位AI专业分析师从不同维度进行分析,综合形成投资建议:\n\n"
|
||||
markdown_content += "- **游资行为分析师** - 分析游资操作特征和意图\n"
|
||||
markdown_content += "- **个股潜力分析师** - 挖掘次日大概率上涨的股票\n"
|
||||
markdown_content += "- **题材追踪分析师** - 识别热点题材和轮动机会\n"
|
||||
markdown_content += "- **风险控制专家** - 识别高风险股票和市场陷阱\n"
|
||||
markdown_content += "- **首席策略师** - 综合研判并给出最终建议\n\n"
|
||||
|
||||
agent_titles = {
|
||||
'youzi': '游资行为分析师',
|
||||
'stock': '个股潜力分析师',
|
||||
'theme': '题材追踪分析师',
|
||||
'risk': '风险控制专家',
|
||||
'chief': '首席策略师综合研判'
|
||||
}
|
||||
|
||||
for agent_key, agent_title in agent_titles.items():
|
||||
agent_data = agents_analysis.get(agent_key, {})
|
||||
if agent_data:
|
||||
markdown_content += f"### {agent_title}\n\n"
|
||||
analysis_text = agent_data.get('analysis', '暂无分析')
|
||||
# 处理文本中的换行
|
||||
analysis_text = analysis_text.replace('\n', '\n\n')
|
||||
markdown_content += f"{analysis_text}\n\n"
|
||||
|
||||
markdown_content += """
|
||||
---
|
||||
|
||||
*报告由智瞰龙虎AI系统自动生成*
|
||||
"""
|
||||
|
||||
return markdown_content
|
||||
|
||||
|
||||
def display_history_tab():
|
||||
"""显示历史报告标签页(增强版)"""
|
||||
|
||||
+4
-1
@@ -128,9 +128,12 @@ def display_main_force_selector():
|
||||
)
|
||||
|
||||
# 模型选择
|
||||
# 导入model_config.py中定义的model_options
|
||||
from model_config import model_options as app_model_options
|
||||
model = st.selectbox(
|
||||
"选择AI模型",
|
||||
["deepseek-chat", "deepseek-reasoner"],
|
||||
list(app_model_options.keys()),
|
||||
format_func=lambda x: app_model_options[x],
|
||||
help="deepseek-chat速度快,deepseek-reasoner推理能力强"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
模型配置文件
|
||||
包含所有可用的AI模型选项
|
||||
"""
|
||||
|
||||
model_options = {
|
||||
"deepseek-chat": "DeepSeek Chat (默认)",
|
||||
"deepseek-reasoner": "DeepSeek Reasoner (推理增强)",
|
||||
"qwen-plus": "qwen-plus (阿里百炼)",
|
||||
"qwen-plus-latest": "qwen-plus-latest (阿里百炼)",
|
||||
"qwen-flash": "qwen-flash (阿里百炼)",
|
||||
"qwen-turbo": "qwen-turbo (阿里百炼)",
|
||||
"qwen3-max": "qwen-max (阿里百炼)",
|
||||
"qwen-long": "qwen-long (阿里百炼)",
|
||||
"deepseek-ai/DeepSeek-R1-0528-Qwen3-8B": "DeepSeek-R1 免费(硅基流动)",
|
||||
"Qwen/Qwen2.5-7B-Instruct": "Qwen 免费(硅基流动)",
|
||||
"Pro/deepseek-ai/DeepSeek-V3.1-Terminus": "DeepSeek-V3.1-Terminus (硅基流动)",
|
||||
"deepseek-ai/DeepSeek-R1": "DeepSeek-R1 (硅基流动)",
|
||||
"Qwen/Qwen3-235B-A22B-Thinking-2507": "Qwen3-235B (硅基流动)",
|
||||
"zai-org/GLM-4.6": "智谱(硅基流动)",
|
||||
"moonshotai/Kimi-K2-Instruct-0905": "Kimi (硅基流动)",
|
||||
"Ring-1T": "蚂蚁百灵 (硅基流动)",
|
||||
"step3": "阶跃星辰(硅基流动)"
|
||||
}
|
||||
+160
-2
@@ -259,6 +259,128 @@ def create_download_link(pdf_content, filename):
|
||||
href = f'<a href="data:application/pdf;base64,{b64}" download="{filename}" style="display: inline-block; padding: 15px 30px; background-color: #e74c3c; color: white; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 16px; margin: 10px;">📄 下载PDF报告</a>'
|
||||
return href
|
||||
|
||||
def generate_markdown_report(stock_info, agents_results, discussion_result, final_decision):
|
||||
"""生成Markdown格式的分析报告"""
|
||||
|
||||
# 获取当前时间
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
|
||||
|
||||
markdown_content = f"""
|
||||
# AI股票分析报告
|
||||
|
||||
**生成时间**: {current_time}
|
||||
|
||||
---
|
||||
|
||||
## 📊 股票基本信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| **股票代码** | {stock_info.get('symbol', 'N/A')} |
|
||||
| **股票名称** | {stock_info.get('name', 'N/A')} |
|
||||
| **当前价格** | {stock_info.get('current_price', 'N/A')} |
|
||||
| **涨跌幅** | {stock_info.get('change_percent', 'N/A')}% |
|
||||
| **市盈率(PE)** | {stock_info.get('pe_ratio', 'N/A')} |
|
||||
| **市净率(PB)** | {stock_info.get('pb_ratio', 'N/A')} |
|
||||
| **市值** | {stock_info.get('market_cap', 'N/A')} |
|
||||
| **市场** | {stock_info.get('market', 'N/A')} |
|
||||
| **交易所** | {stock_info.get('exchange', 'N/A')} |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 各分析师详细分析
|
||||
|
||||
"""
|
||||
|
||||
# 添加各分析师的分析结果
|
||||
agent_names = {
|
||||
'technical': '📈 技术分析师',
|
||||
'fundamental': '📊 基本面分析师',
|
||||
'fund_flow': '💰 资金面分析师',
|
||||
'risk_management': '⚠️ 风险管理师',
|
||||
'market_sentiment': '📈 市场情绪分析师'
|
||||
}
|
||||
|
||||
for agent_key, agent_name in agent_names.items():
|
||||
if agent_key in agents_results:
|
||||
agent_result = agents_results[agent_key]
|
||||
if isinstance(agent_result, dict):
|
||||
analysis_text = agent_result.get('analysis', '暂无分析')
|
||||
else:
|
||||
analysis_text = str(agent_result)
|
||||
|
||||
markdown_content += f"""
|
||||
### {agent_name}
|
||||
|
||||
{analysis_text}
|
||||
|
||||
---
|
||||
|
||||
"""
|
||||
|
||||
# 添加团队讨论结果
|
||||
markdown_content += f"""
|
||||
## 🤝 团队综合讨论
|
||||
|
||||
{discussion_result}
|
||||
|
||||
---
|
||||
|
||||
## 📋 最终投资决策
|
||||
|
||||
"""
|
||||
|
||||
# 处理最终决策的显示
|
||||
if isinstance(final_decision, dict) and "decision_text" not in final_decision:
|
||||
# JSON格式的决策
|
||||
markdown_content += f"""
|
||||
**投资评级**: {final_decision.get('rating', '未知')}
|
||||
|
||||
**目标价位**: {final_decision.get('target_price', 'N/A')}
|
||||
|
||||
**操作建议**: {final_decision.get('operation_advice', '暂无建议')}
|
||||
|
||||
**进场区间**: {final_decision.get('entry_range', 'N/A')}
|
||||
|
||||
**止盈位**: {final_decision.get('take_profit', 'N/A')}
|
||||
|
||||
**止损位**: {final_decision.get('stop_loss', 'N/A')}
|
||||
|
||||
**持有周期**: {final_decision.get('holding_period', 'N/A')}
|
||||
|
||||
**仓位建议**: {final_decision.get('position_size', 'N/A')}
|
||||
|
||||
**信心度**: {final_decision.get('confidence_level', 'N/A')}/10
|
||||
|
||||
**风险提示**: {final_decision.get('risk_warning', '无')}
|
||||
"""
|
||||
else:
|
||||
# 文本格式的决策
|
||||
decision_text = final_decision.get('decision_text', str(final_decision))
|
||||
markdown_content += decision_text
|
||||
|
||||
markdown_content += """
|
||||
|
||||
---
|
||||
|
||||
## 📝 免责声明
|
||||
|
||||
本报告由AI系统生成,仅供参考,不构成投资建议。投资有风险,入市需谨慎。请在做出投资决策前咨询专业的投资顾问。
|
||||
|
||||
---
|
||||
|
||||
*报告生成时间: {current_time}*
|
||||
*AI股票分析系统 v1.0*
|
||||
"""
|
||||
|
||||
return markdown_content
|
||||
|
||||
def create_markdown_download_link(markdown_content, filename):
|
||||
"""创建Markdown下载链接"""
|
||||
b64 = base64.b64encode(markdown_content.encode()).decode()
|
||||
href = f'<a href="data:text/markdown;base64,{b64}" download="{filename}" style="display: inline-block; padding: 15px 30px; background-color: #9b59b6; color: white; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 16px; margin: 10px;">📝 下载Markdown报告</a>'
|
||||
return href
|
||||
|
||||
def display_pdf_export_section(stock_info, agents_results, discussion_result, final_decision):
|
||||
"""显示PDF导出区域"""
|
||||
|
||||
@@ -269,8 +391,11 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi
|
||||
|
||||
with col2:
|
||||
# 生成PDF报告按钮(使用股票代码作为key的一部分,确保唯一性)
|
||||
button_key = f"pdf_btn_{stock_info.get('symbol', 'unknown')}"
|
||||
if st.button("📄 生成并下载PDF报告", type="primary", width='content', key=button_key):
|
||||
pdf_button_key = f"pdf_btn_{stock_info.get('symbol', 'unknown')}"
|
||||
markdown_button_key = f"markdown_btn_{stock_info.get('symbol', 'unknown')}"
|
||||
|
||||
# 生成PDF报告按钮
|
||||
if st.button("📄 生成并下载PDF报告", type="primary", width='content', key=pdf_button_key):
|
||||
with st.spinner("正在生成PDF报告..."):
|
||||
try:
|
||||
# 生成PDF内容
|
||||
@@ -300,3 +425,36 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi
|
||||
st.error(f"❌ 生成PDF报告时出错: {str(e)}")
|
||||
import traceback
|
||||
st.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
# 生成Markdown报告按钮
|
||||
if st.button("📝 生成并下载Markdown报告", type="secondary", width='content', key=markdown_button_key):
|
||||
with st.spinner("正在生成Markdown报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
markdown_content = generate_markdown_report(stock_info, agents_results, discussion_result, final_decision)
|
||||
|
||||
# 生成文件名
|
||||
stock_symbol = stock_info.get('symbol', 'unknown')
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"股票分析报告_{stock_symbol}_{timestamp}.md"
|
||||
|
||||
st.success("✅ Markdown报告生成成功!")
|
||||
st.balloons()
|
||||
|
||||
# 显示下载链接
|
||||
st.markdown("### 📄 报告下载")
|
||||
|
||||
download_link = create_markdown_download_link(markdown_content, filename)
|
||||
st.markdown(f"""
|
||||
<div style="text-align: center; margin: 20px 0;">
|
||||
{download_link}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.info("💡 提示:点击上方按钮即可下载Markdown格式的完整分析报告")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ 生成Markdown报告时出错: {str(e)}")
|
||||
import traceback
|
||||
st.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
|
||||
+45
-147
@@ -133,151 +133,6 @@ def create_html_download_link(content, filename, link_text):
|
||||
href = f'<a href="data:text/html;base64,{b64}" download="{filename}" style="display: inline-block; padding: 10px 20px; background-color: #2196F3; color: white; text-decoration: none; border-radius: 5px; margin: 5px;">{link_text}</a>'
|
||||
return href
|
||||
|
||||
def generate_html_content(markdown_content):
|
||||
"""将Markdown转换为HTML"""
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>AI股票分析报告</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}}
|
||||
.container {{
|
||||
background-color: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}}
|
||||
h1 {{
|
||||
color: #2c3e50;
|
||||
border-bottom: 3px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}}
|
||||
h2 {{
|
||||
color: #34495e;
|
||||
border-left: 4px solid #3498db;
|
||||
padding-left: 15px;
|
||||
margin-top: 30px;
|
||||
}}
|
||||
h3 {{
|
||||
color: #2980b9;
|
||||
margin-top: 25px;
|
||||
}}
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}}
|
||||
th {{
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}}
|
||||
tr:nth-child(even) {{
|
||||
background-color: #f9f9f9;
|
||||
}}
|
||||
.disclaimer {{
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 30px;
|
||||
}}
|
||||
.footer {{
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
color: #7f8c8d;
|
||||
font-style: italic;
|
||||
}}
|
||||
hr {{
|
||||
border: none;
|
||||
height: 2px;
|
||||
background-color: #ecf0f1;
|
||||
margin: 20px 0;
|
||||
}}
|
||||
strong {{
|
||||
color: #2c3e50;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
"""
|
||||
|
||||
# 简单的Markdown到HTML转换
|
||||
html_body = markdown_content
|
||||
html_body = html_body.replace('\n# ', '\n<h1>').replace('\n## ', '\n<h2>').replace('\n### ', '\n<h3>')
|
||||
html_body = html_body.replace('# ', '<h1>').replace('## ', '<h2>').replace('### ', '<h3>')
|
||||
html_body = html_body.replace('\n---\n', '\n<hr>\n')
|
||||
|
||||
# 处理粗体文本
|
||||
html_body = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', html_body)
|
||||
|
||||
# 处理表格
|
||||
lines = html_body.split('\n')
|
||||
in_table = False
|
||||
processed_lines = []
|
||||
|
||||
for line in lines:
|
||||
if '|' in line and not in_table and line.strip().startswith('|'):
|
||||
processed_lines.append('<table>')
|
||||
in_table = True
|
||||
cells = [cell.strip() for cell in line.split('|')[1:-1]]
|
||||
processed_lines.append('<tr>')
|
||||
for cell in cells:
|
||||
processed_lines.append(f'<th>{cell}</th>')
|
||||
processed_lines.append('</tr>')
|
||||
elif '|' in line and in_table:
|
||||
if '---' not in line:
|
||||
cells = [cell.strip() for cell in line.split('|')[1:-1]]
|
||||
processed_lines.append('<tr>')
|
||||
for cell in cells:
|
||||
processed_lines.append(f'<td>{cell}</td>')
|
||||
processed_lines.append('</tr>')
|
||||
elif in_table and '|' not in line:
|
||||
processed_lines.append('</table>')
|
||||
processed_lines.append(line)
|
||||
in_table = False
|
||||
else:
|
||||
processed_lines.append(line)
|
||||
|
||||
if in_table:
|
||||
processed_lines.append('</table>')
|
||||
|
||||
html_body = '\n'.join(processed_lines)
|
||||
|
||||
# 处理段落
|
||||
paragraphs = html_body.split('\n\n')
|
||||
processed_paragraphs = []
|
||||
for para in paragraphs:
|
||||
para = para.strip()
|
||||
if para and not para.startswith('<') and not para.startswith('---'):
|
||||
processed_paragraphs.append(f'<p>{para}</p>')
|
||||
else:
|
||||
processed_paragraphs.append(para)
|
||||
|
||||
html_body = '\n'.join(processed_paragraphs)
|
||||
|
||||
html_content += html_body + """
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return html_content
|
||||
|
||||
def display_pdf_export_section(stock_info, agents_results, discussion_result, final_decision):
|
||||
"""显示PDF导出区域 - 修复报告生成问题"""
|
||||
|
||||
@@ -290,8 +145,11 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi
|
||||
# 生成报告按钮
|
||||
import uuid
|
||||
import time
|
||||
button_key = f"generate_report_btn_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
if st.button("📊 生成并下载报告", type="primary", width='content', key=button_key):
|
||||
pdf_button_key = f"generate_report_btn_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
markdown_button_key = f"generate_markdown_btn_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
# 生成PDF和HTML报告按钮
|
||||
if st.button("📊 生成并下载报告(PDF/HTML)", type="primary", width='content', key=pdf_button_key):
|
||||
with st.spinner("正在生成报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
@@ -338,3 +196,43 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi
|
||||
st.error(f"❌ 生成报告时出错: {str(e)}")
|
||||
import traceback
|
||||
st.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
# 单独生成Markdown报告按钮
|
||||
if st.button("📝 生成并下载Markdown报告", type="secondary", width='content', key=markdown_button_key):
|
||||
with st.spinner("正在生成Markdown报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
markdown_content = generate_markdown_report(stock_info, agents_results, discussion_result, final_decision)
|
||||
|
||||
# 生成文件名
|
||||
stock_symbol = stock_info.get('symbol', 'unknown')
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"股票分析报告_{stock_symbol}_{timestamp}.md"
|
||||
|
||||
st.success("✅ Markdown报告生成成功!")
|
||||
st.balloons()
|
||||
|
||||
# 显示下载链接
|
||||
st.markdown("### 📄 报告下载")
|
||||
|
||||
# 创建下载链接
|
||||
md_link = create_download_link(
|
||||
markdown_content,
|
||||
filename,
|
||||
"📝 下载Markdown报告"
|
||||
)
|
||||
|
||||
# 显示下载链接
|
||||
st.markdown(f"""
|
||||
<div style="text-align: center; margin: 20px 0;">
|
||||
{md_link}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.info("💡 提示:点击上方按钮即可下载Markdown格式的报告文件")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ 生成Markdown报告时出错: {str(e)}")
|
||||
import traceback
|
||||
st.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
|
||||
+36
-1
@@ -267,13 +267,48 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi
|
||||
col1, col2, col3 = st.columns([1, 2, 1])
|
||||
|
||||
with col2:
|
||||
if st.button("📊 生成并下载报告", type="primary", width='content', key="generate_report_btn"):
|
||||
pdf_button_key = "generate_report_btn"
|
||||
markdown_button_key = "generate_markdown_btn"
|
||||
|
||||
# 生成PDF报告按钮
|
||||
if st.button("📊 生成并下载报告(PDF/HTML)", type="primary", width='content', key=pdf_button_key):
|
||||
st.session_state.show_download_links = True
|
||||
with st.spinner("正在生成报告..."):
|
||||
success = generate_pdf_report(stock_info, agents_results, discussion_result, final_decision)
|
||||
if success:
|
||||
st.balloons()
|
||||
|
||||
# 生成Markdown报告按钮
|
||||
if st.button("📝 生成并下载Markdown报告", type="secondary", width='content', key=markdown_button_key):
|
||||
with st.spinner("正在生成Markdown报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
markdown_content = generate_markdown_report(stock_info, agents_results, discussion_result, final_decision)
|
||||
|
||||
# 生成文件名
|
||||
stock_symbol = stock_info.get('symbol', 'unknown')
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"股票分析报告_{stock_symbol}_{timestamp}.md"
|
||||
|
||||
st.success("✅ Markdown报告生成成功!")
|
||||
st.balloons()
|
||||
|
||||
# 显示下载链接
|
||||
st.markdown("### 📄 报告下载")
|
||||
|
||||
# Markdown下载链接
|
||||
md_download_link = create_download_link(
|
||||
markdown_content,
|
||||
filename,
|
||||
"📝 下载Markdown报告"
|
||||
)
|
||||
st.markdown(md_download_link, unsafe_allow_html=True)
|
||||
|
||||
st.info("💡 提示:点击上方按钮即可下载Markdown格式的报告文件")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ 生成Markdown报告时出错: {str(e)}")
|
||||
|
||||
# 如果已经生成了报告,显示下载链接
|
||||
if st.session_state.show_download_links:
|
||||
generate_pdf_report(stock_info, agents_results, discussion_result, final_decision)
|
||||
+215
-4
@@ -116,9 +116,12 @@ def display_analysis_tab():
|
||||
col1, col2, col3 = st.columns([2, 2, 2])
|
||||
|
||||
with col1:
|
||||
# 导入model_config.py中定义的model_options
|
||||
from model_config import model_options as app_model_options
|
||||
selected_model = st.selectbox(
|
||||
"选择AI模型",
|
||||
["deepseek-chat", "deepseek-reasoner"],
|
||||
"AI模型",
|
||||
list(app_model_options.keys()),
|
||||
format_func=lambda x: app_model_options[x],
|
||||
help="Reasoner模型提供更强的推理能力"
|
||||
)
|
||||
|
||||
@@ -774,10 +777,10 @@ def display_pdf_export_section(result):
|
||||
"""显示PDF导出部分"""
|
||||
st.subheader("📄 导出报告")
|
||||
|
||||
col1, col2, col3 = st.columns([2, 1, 1])
|
||||
col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
|
||||
|
||||
with col1:
|
||||
st.write("将分析报告导出为PDF文件,方便保存和分享")
|
||||
st.write("将分析报告导出为PDF或Markdown文件,方便保存和分享")
|
||||
|
||||
with col2:
|
||||
if st.button("📥 生成PDF报告", type="primary", width='content'):
|
||||
@@ -802,6 +805,23 @@ def display_pdf_export_section(result):
|
||||
st.error(f"❌ PDF生成失败: {str(e)}")
|
||||
|
||||
with col3:
|
||||
if st.button("📝 生成Markdown", type="secondary", width='content'):
|
||||
with st.spinner("正在生成Markdown报告..."):
|
||||
try:
|
||||
# 生成Markdown内容
|
||||
markdown_content = generate_sector_markdown_report(result)
|
||||
|
||||
# 保存到session_state
|
||||
st.session_state.sector_markdown_data = markdown_content
|
||||
st.session_state.sector_markdown_filename = f"智策报告_{result.get('timestamp', datetime.now().strftime('%Y%m%d_%H%M%S')).replace(':', '').replace(' ', '_')}.md"
|
||||
|
||||
st.success("✅ Markdown报告生成成功!")
|
||||
st.rerun()
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ Markdown生成失败: {str(e)}")
|
||||
|
||||
with col4:
|
||||
# 如果已经生成了PDF,显示下载按钮
|
||||
if 'sector_pdf_data' in st.session_state:
|
||||
st.download_button(
|
||||
@@ -812,6 +832,197 @@ def display_pdf_export_section(result):
|
||||
width='content'
|
||||
)
|
||||
|
||||
# 如果已经生成了Markdown,显示下载按钮
|
||||
if 'sector_markdown_data' in st.session_state:
|
||||
st.download_button(
|
||||
label="💾 下载Markdown",
|
||||
data=st.session_state.sector_markdown_data,
|
||||
file_name=st.session_state.sector_markdown_filename,
|
||||
mime="text/markdown",
|
||||
width='content'
|
||||
)
|
||||
|
||||
|
||||
def generate_sector_markdown_report(result_data: dict) -> str:
|
||||
"""生成智策分析Markdown报告"""
|
||||
|
||||
# 获取当前时间
|
||||
current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")
|
||||
|
||||
# 标题页
|
||||
markdown_content = f"""# 智策板块策略分析报告
|
||||
|
||||
**AI驱动的多维度板块投资决策支持系统**
|
||||
|
||||
---
|
||||
|
||||
## 📊 报告信息
|
||||
|
||||
- **生成时间**: {current_time}
|
||||
- **分析周期**: 当日市场数据
|
||||
- **AI模型**: DeepSeek Multi-Agent System
|
||||
- **分析维度**: 宏观·板块·资金·情绪
|
||||
|
||||
> ⚠️ 本报告由AI系统自动生成,仅供参考,不构成投资建议。投资有风险,决策需谨慎。
|
||||
|
||||
---
|
||||
|
||||
## 📈 市场概况
|
||||
|
||||
本报告基于{result_data.get('timestamp', 'N/A')}的实时市场数据,
|
||||
通过四位AI智能体的多维度分析,为您提供板块投资策略建议。
|
||||
|
||||
### 分析师团队:
|
||||
|
||||
- **宏观策略师** - 分析宏观经济、政策导向、新闻事件
|
||||
- **板块诊断师** - 分析板块走势、估值水平、轮动特征
|
||||
- **资金流向分析师** - 分析主力资金、北向资金流向
|
||||
- **市场情绪解码员** - 分析市场情绪、热度、赚钱效应
|
||||
|
||||
"""
|
||||
|
||||
# 核心预测
|
||||
predictions = result_data.get('final_predictions', {})
|
||||
|
||||
if predictions.get('prediction_text'):
|
||||
# 文本格式预测
|
||||
markdown_content += f"""
|
||||
## 🎯 核心预测
|
||||
|
||||
{predictions.get('prediction_text', '')}
|
||||
|
||||
"""
|
||||
else:
|
||||
# JSON格式预测
|
||||
markdown_content += "## 🎯 核心预测\n\n"
|
||||
|
||||
# 1. 板块多空预测
|
||||
long_short = predictions.get('long_short', {})
|
||||
bullish = long_short.get('bullish', [])
|
||||
bearish = long_short.get('bearish', [])
|
||||
|
||||
markdown_content += "### 📊 板块多空预测\n\n"
|
||||
|
||||
if bullish:
|
||||
markdown_content += "#### 🟢 看多板块\n\n"
|
||||
for idx, item in enumerate(bullish, 1):
|
||||
markdown_content += f"{idx}. **{item.get('sector', 'N/A')}** (信心度: {item.get('confidence', 0)}/10)\n"
|
||||
markdown_content += f" - 理由: {item.get('reason', 'N/A')}\n"
|
||||
markdown_content += f" - 风险: {item.get('risk', 'N/A')}\n\n"
|
||||
|
||||
if bearish:
|
||||
markdown_content += "#### 🔴 看空板块\n\n"
|
||||
for idx, item in enumerate(bearish, 1):
|
||||
markdown_content += f"{idx}. **{item.get('sector', 'N/A')}** (信心度: {item.get('confidence', 0)}/10)\n"
|
||||
markdown_content += f" - 理由: {item.get('reason', 'N/A')}\n"
|
||||
markdown_content += f" - 风险: {item.get('risk', 'N/A')}\n\n"
|
||||
|
||||
# 2. 板块轮动预测
|
||||
rotation = predictions.get('rotation', {})
|
||||
current_strong = rotation.get('current_strong', [])
|
||||
potential = rotation.get('potential', [])
|
||||
declining = rotation.get('declining', [])
|
||||
|
||||
markdown_content += "### 🔄 板块轮动预测\n\n"
|
||||
|
||||
if current_strong:
|
||||
markdown_content += "#### 💪 当前强势板块\n\n"
|
||||
for item in current_strong:
|
||||
markdown_content += f"- **{item.get('sector', 'N/A')}**\n"
|
||||
markdown_content += f" - 轮动逻辑: {item.get('logic', 'N/A')}\n"
|
||||
markdown_content += f" - 时间窗口: {item.get('time_window', 'N/A')}\n"
|
||||
markdown_content += f" - 操作建议: {item.get('advice', 'N/A')}\n\n"
|
||||
|
||||
if potential:
|
||||
markdown_content += "#### 🌱 潜力接力板块\n\n"
|
||||
for item in potential:
|
||||
markdown_content += f"- **{item.get('sector', 'N/A')}**\n"
|
||||
markdown_content += f" - 轮动逻辑: {item.get('logic', 'N/A')}\n"
|
||||
markdown_content += f" - 时间窗口: {item.get('time_window', 'N/A')}\n"
|
||||
markdown_content += f" - 操作建议: {item.get('advice', 'N/A')}\n\n"
|
||||
|
||||
if declining:
|
||||
markdown_content += "#### 📉 衰退板块\n\n"
|
||||
for item in declining:
|
||||
markdown_content += f"- **{item.get('sector', 'N/A')}**\n"
|
||||
markdown_content += f" - 轮动逻辑: {item.get('logic', 'N/A')}\n"
|
||||
markdown_content += f" - 时间窗口: {item.get('time_window', 'N/A')}\n"
|
||||
markdown_content += f" - 操作建议: {item.get('advice', 'N/A')}\n\n"
|
||||
|
||||
# 3. 板块热度排行
|
||||
heat = predictions.get('heat', {})
|
||||
hottest = heat.get('hottest', [])
|
||||
heating = heat.get('heating', [])
|
||||
cooling = heat.get('cooling', [])
|
||||
|
||||
markdown_content += "### 🔥 板块热度排行\n\n"
|
||||
|
||||
if hottest:
|
||||
markdown_content += "#### 最热板块\n\n| 排名 | 板块 | 热度评分 | 趋势 | 持续性 |\n|------|------|----------|------|--------|\n"
|
||||
for idx, item in enumerate(hottest[:10], 1):
|
||||
markdown_content += f"| {idx} | {item.get('sector', 'N/A')} | {item.get('score', 0)} | {item.get('trend', 'N/A')} | {item.get('sustainability', 'N/A')} |\n"
|
||||
markdown_content += "\n"
|
||||
|
||||
if heating:
|
||||
markdown_content += "#### 升温板块\n\n"
|
||||
for idx, item in enumerate(heating[:5], 1):
|
||||
markdown_content += f"{idx}. {item.get('sector', 'N/A')} (评分: {item.get('score', 0)})\n"
|
||||
markdown_content += "\n"
|
||||
|
||||
if cooling:
|
||||
markdown_content += "#### 降温板块\n\n"
|
||||
for idx, item in enumerate(cooling[:5], 1):
|
||||
markdown_content += f"{idx}. {item.get('sector', 'N/A')} (评分: {item.get('score', 0)})\n"
|
||||
markdown_content += "\n"
|
||||
|
||||
# 4. 策略总结
|
||||
summary = predictions.get('summary', {})
|
||||
if summary:
|
||||
markdown_content += "### 📝 策略总结\n\n"
|
||||
|
||||
if summary.get('market_view'):
|
||||
markdown_content += f"**市场观点:** {summary.get('market_view', '')}\n\n"
|
||||
|
||||
if summary.get('key_opportunity'):
|
||||
markdown_content += f"**核心机会:** {summary.get('key_opportunity', '')}\n\n"
|
||||
|
||||
if summary.get('major_risk'):
|
||||
markdown_content += f"**主要风险:** {summary.get('major_risk', '')}\n\n"
|
||||
|
||||
if summary.get('strategy'):
|
||||
markdown_content += f"**整体策略:** {summary.get('strategy', '')}\n\n"
|
||||
|
||||
# AI智能体分析
|
||||
agents_analysis = result_data.get('agents_analysis', {})
|
||||
if agents_analysis:
|
||||
markdown_content += "## 🤖 AI智能体分析\n\n"
|
||||
|
||||
for key, agent_data in agents_analysis.items():
|
||||
agent_name = agent_data.get('agent_name', '未知分析师')
|
||||
agent_role = agent_data.get('agent_role', '')
|
||||
focus_areas = ', '.join(agent_data.get('focus_areas', []))
|
||||
analysis = agent_data.get('analysis', '')
|
||||
|
||||
markdown_content += f"### {agent_name}\n\n"
|
||||
markdown_content += f"- **职责**: {agent_role}\n"
|
||||
markdown_content += f"- **关注领域**: {focus_areas}\n\n"
|
||||
markdown_content += f"{analysis}\n\n"
|
||||
markdown_content += "---\n\n"
|
||||
|
||||
# 综合研判
|
||||
comprehensive_report = result_data.get('comprehensive_report', '')
|
||||
if comprehensive_report:
|
||||
markdown_content += "## 📊 综合研判\n\n"
|
||||
markdown_content += f"{comprehensive_report}\n\n"
|
||||
|
||||
markdown_content += """
|
||||
---
|
||||
|
||||
*报告由智策AI系统自动生成*
|
||||
"""
|
||||
|
||||
return markdown_content
|
||||
|
||||
|
||||
def display_scheduler_settings():
|
||||
"""显示定时任务设置"""
|
||||
|
||||
Reference in New Issue
Block a user