diff --git a/README.md b/README.md
index 65c3c09..3eb5b11 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,16 @@
## ✨ 更新说明
+### 最新更新:增加批量分析功能 ⭐️
+- **批量输入**:支持同时输入多个股票代码(每行一个或逗号分隔)
+- **双模式分析**:
+ - 顺序分析:按次序逐个分析,稳定可靠
+ - 多线程并行:同时分析多只股票,速度更快
+- **对比视图**:横向对比多只股票的关键指标和投资评级
+- **详细卡片**:逐个查看每只股票的完整分析报告
+- **智能筛选**:按评级、涨跌幅、信心度等条件快速筛选
+- **自动保存**:所有分析结果自动保存到历史记录
+
### 1006增加跟踪主力资金mcp,增加环境配置功能
### 1005增加股票监测功能,增加历史记录中个股导入到监测板块
@@ -38,6 +48,19 @@
5. 📋 最终投资决策
6. 🎯 操作建议和风险提示
7. 📄 PDF报告导出
+
+### 🔥 批量分析功能(新增)⭐️
+- **批量输入支持**:一次性输入多只股票代码
+- **两种分析模式**:
+ - 顺序分析:稳定可靠,适合少量股票
+ - 多线程并行:快速高效,适合大量股票(最多3个并发)
+- **实时进度显示**:清晰展示每只股票的分析进度和状态
+- **对比分析表格**:
+ - 横向对比多只股票的关键指标
+ - 投资评级可视化(买入/持有/卖出)
+ - 智能筛选和排序功能
+- **详细卡片视图**:逐个查看完整的分析报告
+- **批量保存**:所有分析结果自动保存到数据库
@@ -111,7 +134,7 @@ Copy-Item .env.example .env
cp .env.example .env
```
-2. 编辑 `.env` 文件,设置您的配置:
+2. 编辑 `.env` 文件,设置您的配置(也可在前端web界面-环境配置中设置):
```env
# DeepSeek API配置(必需)
DEEPSEEK_API_KEY=your_actual_deepseek_api_key_here
@@ -157,13 +180,71 @@ streamlit run app.py
- **美股**:AAPL, MSFT, GOOGL, TSLA, NVDA
- **A股**:000001, 600036, 000002, 600519
-### 分析流程
-1. 在输入框中输入股票代码
-2. 点击"开始分析"按钮
-3. 等待AI分析师团队完成分析
-4. 查看各维度分析报告
-5. 阅读团队讨论结果
-6. 获取最终投资决策
+### 单个股票分析流程
+1. 选择"单个分析"模式
+2. 在输入框中输入股票代码
+3. 选择要参与分析的分析师团队
+4. 点击"开始分析"按钮
+5. 等待AI分析师团队完成分析
+6. 查看各维度分析报告
+7. 阅读团队讨论结果
+8. 获取最终投资决策
+
+### 批量股票分析流程 ⭐️ 新增
+1. **选择批量分析模式**
+ - 点击"批量分析"单选按钮
+ - 选择分析模式:顺序分析 或 多线程并行
+
+2. **输入股票代码**
+ - 支持多种格式:
+ ```
+ # 每行一个代码
+ 000001
+ 600036
+ 600519
+
+ # 或逗号分隔
+ 000001, 600036, 600519
+
+ # 或空格分隔
+ 000001 600036 600519
+ ```
+
+3. **配置分析参数**
+ - 选择数据周期(1y, 6mo, 3mo, 1mo)
+ - 选择要参与的分析师团队
+
+4. **开始批量分析**
+ - 点击"开始批量分析"按钮
+ - 系统会实时显示分析进度
+ - 每只股票的分析状态都会更新
+
+5. **查看分析结果**
+ - **对比表格模式**:
+ - 横向对比所有股票的关键指标
+ - 投资评级用颜色标识(绿色=买入,黄色=持有,红色=卖出)
+ - 可按评级筛选
+ - 可按涨跌幅、信心度、RSI等排序
+
+ - **详细卡片模式**:
+ - 从下拉列表选择要查看的股票
+ - 查看完整的分析报告
+ - 包含图表、各分析师意见、团队讨论、最终决策
+
+6. **结果管理**
+ - 所有分析结果自动保存到历史记录
+ - 点击"清除结果"可清空当前批量分析结果
+ - 失败的股票会单独列出,显示失败原因
+
+### 批量分析使用技巧
+- **建议数量**:一次分析不超过20只股票
+- **模式选择**:
+ - 3-5只股票:建议使用顺序分析,更稳定
+ - 6只以上:建议使用多线程并行,更快速
+- **注意事项**:
+ - 多线程模式最多3个并发,避免API限流
+ - 批量分析时间较长,请耐心等待
+ - 可在历史记录中查看所有分析结果
### 结果解读
- **投资评级**:买入/持有/卖出
diff --git a/app.py b/app.py
index d420de5..497fbd6 100644
--- a/app.py
+++ b/app.py
@@ -399,22 +399,68 @@ def main():
return
# 主界面
- col1, col2, col3 = st.columns([2, 1, 1])
-
- with col1:
- stock_input = st.text_input(
- "🔍 请输入股票代码或名称",
- placeholder="例如: AAPL, 000001, 600036",
- help="支持美股代码(如AAPL)和A股代码(如000001)"
+ # 添加单个/批量分析切换
+ col_mode1, col_mode2 = st.columns([1, 3])
+ with col_mode1:
+ analysis_mode = st.radio(
+ "分析模式",
+ ["单个分析", "批量分析"],
+ horizontal=True,
+ help="单个分析:分析单只股票;批量分析:同时分析多只股票"
)
- with col2:
- analyze_button = st.button("🚀 开始分析", type="primary", use_container_width=True)
+ with col_mode2:
+ if analysis_mode == "批量分析":
+ batch_mode = st.radio(
+ "批量模式",
+ ["顺序分析", "多线程并行"],
+ horizontal=True,
+ help="顺序分析:按次序分析,稳定但较慢;多线程并行:同时分析多只,快速但消耗资源"
+ )
+ st.session_state.batch_mode = batch_mode
- with col3:
- if st.button("🔄 清除缓存", use_container_width=True):
- st.cache_data.clear()
- st.success("缓存已清除")
+ st.markdown("---")
+
+ if analysis_mode == "单个分析":
+ # 单个股票分析界面
+ col1, col2, col3 = st.columns([2, 1, 1])
+
+ with col1:
+ stock_input = st.text_input(
+ "🔍 请输入股票代码或名称",
+ placeholder="例如: AAPL, 000001, 600036",
+ help="支持美股代码(如AAPL)和A股代码(如000001)"
+ )
+
+ with col2:
+ analyze_button = st.button("🚀 开始分析", type="primary", use_container_width=True)
+
+ with col3:
+ if st.button("🔄 清除缓存", use_container_width=True):
+ st.cache_data.clear()
+ st.success("缓存已清除")
+
+ else:
+ # 批量股票分析界面
+ stock_input = st.text_area(
+ "🔍 请输入多个股票代码(每行一个或用逗号分隔)",
+ placeholder="例如:\n000001\n600036\n600519\n\n或者: 000001, 600036, 600519",
+ height=120,
+ help="支持多种格式:每行一个代码或用逗号分隔"
+ )
+
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ analyze_button = st.button("🚀 开始批量分析", type="primary", use_container_width=True)
+ with col2:
+ if st.button("🔄 清除缓存", use_container_width=True):
+ st.cache_data.clear()
+ st.success("缓存已清除")
+ with col3:
+ if st.button("🗑️ 清除结果", use_container_width=True):
+ if 'batch_analysis_results' in st.session_state:
+ del st.session_state.batch_analysis_results
+ st.success("已清除批量分析结果")
# 分析师团队选择
st.markdown("---")
@@ -480,21 +526,47 @@ def main():
st.error("❌ 请至少选择一位分析师参与分析")
return
- # 清除之前的分析结果
- if 'analysis_completed' in st.session_state:
- del st.session_state.analysis_completed
- if 'stock_info' in st.session_state:
- del st.session_state.stock_info
- if 'agents_results' in st.session_state:
- del st.session_state.agents_results
- if 'discussion_result' in st.session_state:
- del st.session_state.discussion_result
- if 'final_decision' in st.session_state:
- del st.session_state.final_decision
+ if analysis_mode == "单个分析":
+ # 单个股票分析
+ # 清除之前的分析结果
+ if 'analysis_completed' in st.session_state:
+ del st.session_state.analysis_completed
+ if 'stock_info' in st.session_state:
+ del st.session_state.stock_info
+ if 'agents_results' in st.session_state:
+ del st.session_state.agents_results
+ if 'discussion_result' in st.session_state:
+ del st.session_state.discussion_result
+ if 'final_decision' in st.session_state:
+ del st.session_state.final_decision
+
+ run_stock_analysis(stock_input, period)
+
+ else:
+ # 批量股票分析
+ # 解析股票代码列表
+ stock_list = parse_stock_list(stock_input)
- run_stock_analysis(stock_input, period)
+ if not stock_list:
+ st.error("❌ 请输入有效的股票代码")
+ return
+
+ if len(stock_list) > 20:
+ st.warning(f"⚠️ 检测到 {len(stock_list)} 只股票,建议一次分析不超过20只")
+
+ st.info(f"📊 准备分析 {len(stock_list)} 只股票: {', '.join(stock_list)}")
+
+ # 清除之前的批量分析结果
+ if 'batch_analysis_results' in st.session_state:
+ del st.session_state.batch_analysis_results
+
+ # 获取批量模式
+ batch_mode = st.session_state.get('batch_mode', '顺序分析')
+
+ # 运行批量分析
+ run_batch_analysis(stock_list, period, batch_mode)
- # 检查是否有已完成的分析结果
+ # 检查是否有已完成的单个分析结果
if 'analysis_completed' in st.session_state and st.session_state.analysis_completed:
# 重新显示分析结果
stock_info = st.session_state.stock_info
@@ -521,6 +593,10 @@ def main():
# 显示最终决策
display_final_decision(final_decision, stock_info, agents_results, discussion_result)
+ # 检查是否有已完成的批量分析结果
+ elif 'batch_analysis_results' in st.session_state and st.session_state.batch_analysis_results:
+ display_batch_analysis_results(st.session_state.batch_analysis_results, period)
+
# 示例和说明
elif not stock_input:
show_example_interface()
@@ -548,6 +624,287 @@ def get_stock_data(symbol, period):
return stock_info, stock_data_with_indicators, indicators
+def parse_stock_list(stock_input):
+ """解析股票代码列表
+
+ 支持的格式:
+ - 每行一个代码
+ - 逗号分隔
+ - 空格分隔
+ """
+ if not stock_input or not stock_input.strip():
+ return []
+
+ # 先按换行符分割
+ lines = stock_input.strip().split('\n')
+
+ # 处理每一行
+ stock_list = []
+ for line in lines:
+ line = line.strip()
+ if not line:
+ continue
+
+ # 检查是否包含逗号
+ if ',' in line:
+ codes = [code.strip() for code in line.split(',')]
+ stock_list.extend([code for code in codes if code])
+ # 检查是否包含空格
+ elif ' ' in line:
+ codes = [code.strip() for code in line.split()]
+ stock_list.extend([code for code in codes if code])
+ else:
+ stock_list.append(line)
+
+ # 去重并保持顺序
+ seen = set()
+ unique_list = []
+ for code in stock_list:
+ if code not in seen:
+ seen.add(code)
+ unique_list.append(code)
+
+ return unique_list
+
+def analyze_single_stock_for_batch(symbol, period, enabled_analysts_config=None, selected_model='deepseek-chat'):
+ """单个股票分析(用于批量分析)
+
+ Args:
+ symbol: 股票代码
+ period: 数据周期
+ enabled_analysts_config: 分析师配置字典
+ selected_model: 选择的AI模型
+
+ 返回分析结果或错误信息
+ """
+ try:
+ # 使用默认配置
+ if enabled_analysts_config is None:
+ enabled_analysts_config = {
+ 'technical': True,
+ 'fundamental': True,
+ 'fund_flow': True,
+ 'risk': True,
+ 'sentiment': False,
+ 'news': False
+ }
+
+ # 1. 获取股票数据
+ stock_info, stock_data, indicators = get_stock_data(symbol, period)
+
+ if "error" in stock_info:
+ return {"symbol": symbol, "error": stock_info['error'], "success": False}
+
+ if stock_data is None:
+ return {"symbol": symbol, "error": "无法获取股票历史数据", "success": False}
+
+ # 2. 获取财务数据
+ fetcher = StockDataFetcher()
+ financial_data = fetcher.get_financial_data(symbol)
+
+ # 获取分析师选择状态(从参数而不是session_state)
+ enable_fund_flow = enabled_analysts_config.get('fund_flow', True)
+ enable_sentiment = enabled_analysts_config.get('sentiment', False)
+ enable_news = enabled_analysts_config.get('news', False)
+
+ # 3. 获取资金流向数据(可选)
+ fund_flow_data = None
+ if enable_fund_flow and fetcher._is_chinese_stock(symbol):
+ try:
+ fund_flow_data = fetcher.get_fund_flow_data(symbol)
+ except:
+ pass
+
+ # 4. 获取市场情绪数据(可选)
+ sentiment_data = None
+ if enable_sentiment and fetcher._is_chinese_stock(symbol):
+ try:
+ from market_sentiment_data import MarketSentimentDataFetcher
+ sentiment_fetcher = MarketSentimentDataFetcher()
+ sentiment_data = sentiment_fetcher.get_market_sentiment_data(symbol, stock_data)
+ except:
+ pass
+
+ # 5. 获取新闻公告数据(可选)
+ news_announcement_data = None
+ if enable_news and fetcher._is_chinese_stock(symbol):
+ try:
+ from news_announcement_data import NewsAnnouncementDataFetcher
+ news_fetcher = NewsAnnouncementDataFetcher()
+ news_announcement_data = news_fetcher.get_news_and_announcements(symbol)
+ except:
+ pass
+
+ # 6. 初始化AI分析系统
+ agents = StockAnalysisAgents(model=selected_model)
+
+ # 使用传入的分析师配置
+ enabled_analysts = enabled_analysts_config
+
+ # 7. 运行多智能体分析
+ agents_results = agents.run_multi_agent_analysis(
+ stock_info, stock_data, indicators, financial_data,
+ fund_flow_data, sentiment_data, news_announcement_data,
+ enabled_analysts=enabled_analysts
+ )
+
+ # 8. 团队讨论
+ discussion_result = agents.conduct_team_discussion(agents_results, stock_info)
+
+ # 9. 最终决策
+ final_decision = agents.make_final_decision(discussion_result, stock_info, indicators)
+
+ # 保存到数据库
+ try:
+ db.save_analysis(
+ symbol=stock_info.get('symbol', ''),
+ stock_name=stock_info.get('name', ''),
+ period=period,
+ stock_info=stock_info,
+ agents_results=agents_results,
+ discussion_result=discussion_result,
+ final_decision=final_decision
+ )
+ except Exception as e:
+ print(f"保存到数据库时出现错误: {str(e)}")
+
+ return {
+ "symbol": symbol,
+ "success": True,
+ "stock_info": stock_info,
+ "indicators": indicators,
+ "agents_results": agents_results,
+ "discussion_result": discussion_result,
+ "final_decision": final_decision
+ }
+
+ except Exception as e:
+ return {"symbol": symbol, "error": str(e), "success": False}
+
+def run_batch_analysis(stock_list, period, batch_mode="顺序分析"):
+ """运行批量股票分析"""
+ import concurrent.futures
+ import threading
+
+ # 在开始分析前获取配置(从session_state)
+ enabled_analysts_config = {
+ 'technical': st.session_state.get('enable_technical', True),
+ 'fundamental': st.session_state.get('enable_fundamental', True),
+ 'fund_flow': st.session_state.get('enable_fund_flow', True),
+ 'risk': st.session_state.get('enable_risk', True),
+ 'sentiment': st.session_state.get('enable_sentiment', False),
+ 'news': st.session_state.get('enable_news', False)
+ }
+ selected_model = st.session_state.get('selected_model', 'deepseek-chat')
+
+ # 创建进度显示
+ st.subheader(f"📊 批量分析进行中 ({batch_mode})")
+
+ progress_bar = st.progress(0)
+ status_text = st.empty()
+
+ # 存储结果
+ results = []
+ total = len(stock_list)
+
+ if batch_mode == "多线程并行":
+ # 多线程并行分析
+ status_text.text(f"🚀 使用多线程并行分析 {total} 只股票...")
+
+ # 创建线程锁用于更新进度
+ lock = threading.Lock()
+ completed = [0] # 使用列表以便在闭包中修改
+ progress_status = [{}] # 存储进度状态
+
+ def analyze_with_progress(symbol):
+ """包装分析函数,不在线程中访问Streamlit上下文"""
+ try:
+ result = analyze_single_stock_for_batch(symbol, period, enabled_analysts_config, selected_model)
+ with lock:
+ completed[0] += 1
+ progress_status[0][symbol] = result
+ return result
+ except Exception as e:
+ with lock:
+ completed[0] += 1
+ error_result = {"symbol": symbol, "error": str(e), "success": False}
+ progress_status[0][symbol] = error_result
+ return error_result
+
+ # 使用线程池执行,限制最大并发数为3以避免API限流
+ with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
+ future_to_symbol = {executor.submit(analyze_with_progress, symbol): symbol
+ for symbol in stock_list}
+
+ for future in concurrent.futures.as_completed(future_to_symbol):
+ symbol = future_to_symbol[future]
+ try:
+ result = future.result(timeout=300) # 5分钟超时
+ results.append(result)
+
+ # 在主线程中更新UI
+ progress = len(results) / total
+ progress_bar.progress(progress)
+
+ if result['success']:
+ status_text.text(f"✅ [{len(results)}/{total}] {symbol} 分析完成")
+ else:
+ status_text.text(f"❌ [{len(results)}/{total}] {symbol} 分析失败: {result.get('error', '未知错误')}")
+
+ except concurrent.futures.TimeoutError:
+ results.append({"symbol": symbol, "error": "分析超时(5分钟)", "success": False})
+ progress_bar.progress(len(results) / total)
+ status_text.text(f"⏱️ [{len(results)}/{total}] {symbol} 分析超时")
+ except Exception as e:
+ results.append({"symbol": symbol, "error": str(e), "success": False})
+ progress_bar.progress(len(results) / total)
+ status_text.text(f"❌ [{len(results)}/{total}] {symbol} 出现错误")
+
+ else:
+ # 顺序分析
+ status_text.text(f"📝 按顺序分析 {total} 只股票...")
+
+ for i, symbol in enumerate(stock_list, 1):
+ status_text.text(f"🔍 [{i}/{total}] 正在分析 {symbol}...")
+
+ try:
+ result = analyze_single_stock_for_batch(symbol, period, enabled_analysts_config, selected_model)
+ except Exception as e:
+ result = {"symbol": symbol, "error": str(e), "success": False}
+
+ results.append(result)
+
+ # 更新进度
+ progress = i / total
+ progress_bar.progress(progress)
+
+ if result['success']:
+ status_text.text(f"✅ [{i}/{total}] {symbol} 分析完成")
+ else:
+ status_text.text(f"❌ [{i}/{total}] {symbol} 分析失败: {result.get('error', '未知错误')}")
+
+ # 完成
+ progress_bar.progress(1.0)
+
+ # 统计结果
+ success_count = sum(1 for r in results if r['success'])
+ failed_count = total - success_count
+
+ if success_count > 0:
+ status_text.success(f"✅ 批量分析完成!成功 {success_count} 只,失败 {failed_count} 只")
+ else:
+ status_text.error(f"❌ 批量分析完成,但所有股票都分析失败")
+
+ # 保存结果到session_state
+ st.session_state.batch_analysis_results = results
+ st.session_state.batch_analysis_mode = batch_mode
+
+ time.sleep(2)
+ progress_bar.empty()
+
+ # 自动显示结果
+ st.rerun()
+
def run_stock_analysis(symbol, period):
"""运行股票分析"""
@@ -1682,5 +2039,190 @@ MINIQMT_HOST="{current_config.get('MINIQMT_HOST', '127.0.0.1')}"
MINIQMT_PORT="{current_config.get('MINIQMT_PORT', '58610')}"
""", language="bash")
+def display_batch_analysis_results(results, period):
+ """显示批量分析结果(对比视图)"""
+
+ st.subheader("📊 批量分析结果对比")
+
+ # 统计信息
+ total = len(results)
+ success_results = [r for r in results if r['success']]
+ failed_results = [r for r in results if not r['success']]
+
+ # 显示统计
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("总数", total)
+ with col2:
+ st.metric("成功", len(success_results), delta=None, delta_color="normal")
+ with col3:
+ st.metric("失败", len(failed_results), delta=None, delta_color="inverse")
+
+ st.markdown("---")
+
+ # 失败的股票列表
+ if failed_results:
+ with st.expander(f"❌ 查看失败的 {len(failed_results)} 只股票", expanded=False):
+ for result in failed_results:
+ st.error(f"**{result['symbol']}**: {result.get('error', '未知错误')}")
+
+ # 成功的股票分析结果
+ if not success_results:
+ st.warning("⚠️ 没有成功分析的股票")
+ return
+
+ # 创建对比视图选项
+ view_mode = st.radio(
+ "显示模式",
+ ["对比表格", "详细卡片"],
+ horizontal=True,
+ help="对比表格:横向对比多只股票;详细卡片:逐个查看详细分析"
+ )
+
+ if view_mode == "对比表格":
+ # 表格对比视图
+ display_comparison_table(success_results)
+ else:
+ # 详细卡片视图
+ display_detailed_cards(success_results, period)
+
+def display_comparison_table(results):
+ """显示对比表格"""
+ import pandas as pd
+
+ st.subheader("📋 股票对比表格")
+
+ # 构建对比数据
+ comparison_data = []
+ for result in results:
+ stock_info = result['stock_info']
+ indicators = result.get('indicators', {})
+ final_decision = result['final_decision']
+
+ # 解析评级
+ if isinstance(final_decision, dict):
+ rating = final_decision.get('rating', 'N/A')
+ confidence = final_decision.get('confidence_level', 'N/A')
+ target_price = final_decision.get('target_price', 'N/A')
+ else:
+ rating = 'N/A'
+ confidence = 'N/A'
+ target_price = 'N/A'
+
+ row = {
+ '股票代码': 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'),
+ '市盈率': stock_info.get('pe_ratio', 'N/A'),
+ '市净率': stock_info.get('pb_ratio', 'N/A'),
+ 'RSI': indicators.get('rsi', 'N/A'),
+ 'MACD': indicators.get('macd', 'N/A'),
+ '投资评级': rating,
+ '信心度': confidence,
+ '目标价格': target_price
+ }
+ comparison_data.append(row)
+
+ # 创建DataFrame
+ df = pd.DataFrame(comparison_data)
+
+ # 应用样式
+ def highlight_rating(val):
+ if val == '买入' or val == '强烈买入':
+ return 'background-color: #c8e6c9; color: #2e7d32;'
+ elif val == '持有':
+ return 'background-color: #fff9c4; color: #f57f17;'
+ elif val == '卖出' or val == '强烈卖出':
+ return 'background-color: #ffcdd2; color: #c62828;'
+ return ''
+
+ # 显示表格
+ st.dataframe(
+ df.style.applymap(highlight_rating, subset=['投资评级']),
+ use_container_width=True,
+ height=400
+ )
+
+ # 添加筛选功能
+ st.markdown("---")
+ st.subheader("🔍 快速筛选")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ rating_filter = st.multiselect(
+ "按评级筛选",
+ options=df['投资评级'].unique().tolist(),
+ default=df['投资评级'].unique().tolist()
+ )
+
+ with col2:
+ # 按涨跌幅排序
+ sort_by = st.selectbox(
+ "排序方式",
+ ["默认", "涨跌幅降序", "涨跌幅升序", "信心度降序", "RSI降序"]
+ )
+
+ # 应用筛选
+ filtered_df = df[df['投资评级'].isin(rating_filter)]
+
+ # 应用排序
+ if sort_by == "涨跌幅降序":
+ filtered_df = filtered_df.sort_values('涨跌幅(%)', ascending=False)
+ elif sort_by == "涨跌幅升序":
+ filtered_df = filtered_df.sort_values('涨跌幅(%)', ascending=True)
+ elif sort_by == "信心度降序":
+ filtered_df = filtered_df.sort_values('信心度', ascending=False)
+ elif sort_by == "RSI降序":
+ filtered_df = filtered_df.sort_values('RSI', ascending=False)
+
+ if not filtered_df.empty:
+ st.dataframe(filtered_df, use_container_width=True)
+ else:
+ st.info("没有符合条件的股票")
+
+def display_detailed_cards(results, period):
+ """显示详细卡片视图"""
+
+ st.subheader("📇 详细分析卡片")
+
+ # 选择要查看的股票
+ stock_options = [f"{r['stock_info']['symbol']} - {r['stock_info']['name']}" for r in results]
+ selected_stock = st.selectbox("选择股票", options=stock_options)
+
+ # 找到对应的结果
+ selected_index = stock_options.index(selected_stock)
+ result = results[selected_index]
+
+ # 显示详细分析
+ stock_info = result['stock_info']
+ indicators = result['indicators']
+ agents_results = result['agents_results']
+ discussion_result = result['discussion_result']
+ final_decision = result['final_decision']
+
+ # 获取股票数据用于显示图表
+ try:
+ stock_info_current, stock_data, _ = get_stock_data(stock_info['symbol'], period)
+
+ # 显示股票基本信息
+ display_stock_info(stock_info, indicators)
+
+ # 显示股票图表
+ if stock_data is not None:
+ display_stock_chart(stock_data, stock_info)
+
+ # 显示各分析师报告
+ display_agents_analysis(agents_results)
+
+ # 显示团队讨论
+ display_team_discussion(discussion_result)
+
+ # 显示最终决策
+ display_final_decision(final_decision, stock_info, agents_results, discussion_result)
+
+ except Exception as e:
+ st.error(f"显示详细信息时出错: {str(e)}")
+
if __name__ == "__main__":
main()
\ No newline at end of file
diff --git a/批量分析功能说明.md b/批量分析功能说明.md
new file mode 100644
index 0000000..e4c2f71
--- /dev/null
+++ b/批量分析功能说明.md
@@ -0,0 +1,290 @@
+# 📊 批量分析功能使用指南
+
+## 功能概述
+
+批量分析功能允许您一次性分析多只股票,并提供横向对比视图,快速筛选出最有投资价值的股票。
+
+## ✨ 核心特性
+
+### 1. 灵活的输入方式
+支持多种股票代码输入格式:
+- **每行一个代码**
+- **逗号分隔**
+- **空格分隔**
+- **混合格式**
+
+### 2. 双模式分析
+- **顺序分析**:按次序逐个分析,稳定可靠
+ - 适用场景:3-5只股票,网络不稳定时
+ - 优点:可靠性高,不会因并发导致问题
+ - 缺点:速度较慢
+
+- **多线程并行**:同时分析多只股票
+ - 适用场景:6只以上股票,网络稳定时
+ - 优点:速度快,效率高
+ - 缺点:资源消耗较大
+ - 限制:最多3个并发(避免API限流)
+
+### 3. 实时进度追踪
+- 显示当前分析进度(X/总数)
+- 实时更新每只股票的状态
+- ✅ 成功标记
+- ❌ 失败标记及原因
+
+### 4. 智能结果展示
+
+#### 对比表格模式
+横向对比所有股票的关键指标:
+- **基本信息**:代码、名称、价格、涨跌幅
+- **估值指标**:市盈率、市净率
+- **技术指标**:RSI、MACD
+- **投资建议**:评级、信心度、目标价格
+
+**颜色编码**:
+- 🟢 绿色:买入/强烈买入
+- 🟡 黄色:持有
+- 🔴 红色:卖出/强烈卖出
+
+**筛选和排序**:
+- 按投资评级筛选
+- 按涨跌幅排序
+- 按信心度排序
+- 按RSI排序
+
+#### 详细卡片模式
+逐个查看每只股票的完整分析:
+- 基本信息和技术指标
+- K线图和成交量图
+- 各分析师详细报告
+- 团队综合讨论
+- 最终投资决策
+- 操作建议和风险提示
+
+## 📖 使用步骤
+
+### Step 1: 选择批量分析模式
+1. 在主界面顶部选择"批量分析"
+2. 选择分析模式:
+ - "顺序分析":稳定但较慢
+ - "多线程并行":快速但需要稳定网络
+
+### Step 2: 输入股票代码
+在文本框中输入股票代码,支持以下格式:
+
+```
+# 格式1:每行一个(推荐)
+000001
+600036
+600519
+000858
+
+# 格式2:逗号分隔
+000001, 600036, 600519, 000858
+
+# 格式3:空格分隔
+000001 600036 600519 000858
+
+# 格式4:混合使用
+000001, 600036
+600519 000858
+```
+
+### Step 3: 配置分析参数
+1. **选择数据周期**:
+ - 1y(一年)- 推荐,数据更完整
+ - 6mo(半年)
+ - 3mo(三个月)
+ - 1mo(一个月)
+
+2. **选择分析师团队**:
+ - ✅ 技术分析师(推荐)
+ - ✅ 基本面分析师(推荐)
+ - ✅ 资金面分析师(推荐)
+ - ✅ 风险管理师(推荐)
+ - ⬜ 市场情绪分析师(可选,仅A股)
+ - ⬜ 新闻公告分析师(可选,仅A股)
+
+### Step 4: 开始分析
+1. 点击"🚀 开始批量分析"按钮
+2. 系统会显示实时进度:
+ ```
+ 📊 批量分析进行中 (多线程并行)
+ ✅ [1/5] 000001 分析完成
+ 🔍 [2/5] 正在分析 600036...
+ ```
+3. 等待所有股票分析完成
+
+### Step 5: 查看结果
+
+#### 对比表格视图
+1. 自动显示对比表格
+2. 查看所有股票的关键指标
+3. 使用筛选功能:
+ - 按评级筛选(只看"买入"的股票)
+ - 按涨跌幅排序(找涨幅最大的)
+ - 按信心度排序(找最有把握的)
+
+#### 详细卡片视图
+1. 切换到"详细卡片"模式
+2. 从下拉列表选择要查看的股票
+3. 查看完整的分析报告
+4. 查看K线图和技术指标
+5. 阅读各分析师的详细意见
+
+## 💡 使用技巧
+
+### 最佳实践
+
+1. **选股策略**
+ ```
+ 第一步:输入感兴趣的板块所有股票(10-20只)
+ 第二步:使用批量分析快速筛选
+ 第三步:在对比表格中按评级筛选
+ 第四步:在详细卡片中深入研究候选股票
+ ```
+
+2. **分析数量建议**
+ - **3-5只**:使用顺序分析
+ - **6-10只**:使用多线程并行
+ - **11-20只**:使用多线程并行,但需要耐心等待
+ - **20只以上**:分批次分析
+
+3. **时间估算**
+ - 单只股票分析:约1-2分钟
+ - 顺序分析5只:约5-10分钟
+ - 多线程分析10只:约4-7分钟(3个并发)
+
+4. **结果筛选技巧**
+ - **保守型投资者**:筛选"买入"评级且信心度≥7
+ - **激进型投资者**:按涨跌幅降序,关注强势股
+ - **价值投资者**:关注低市盈率+高ROE的股票
+ - **技术派**:按RSI筛选超卖或超买状态
+
+### 注意事项
+
+⚠️ **重要提示**:
+1. 批量分析时间较长,请耐心等待
+2. 多线程模式会消耗更多API调用额度
+3. 建议在非交易时段进行批量分析
+4. 分析过程中不要关闭浏览器
+5. 所有结果会自动保存到历史记录
+
+⚠️ **故障处理**:
+- 如果某只股票分析失败,不会影响其他股票
+- 失败的股票会单独列出,显示失败原因
+- 可以在历史记录中单独重新分析失败的股票
+
+## 📈 应用场景
+
+### 场景1:板块轮动分析
+```
+1. 识别热门板块(如新能源、半导体)
+2. 批量输入该板块10-15只股票
+3. 使用对比表格筛选出评级最高的3-5只
+4. 深入研究这些候选股票
+```
+
+### 场景2:持仓股票全面体检
+```
+1. 输入所有持仓股票代码
+2. 批量分析获取最新评级
+3. 识别需要止损的股票(评级"卖出")
+4. 识别可以加仓的股票(评级"买入")
+```
+
+### 场景3:每日选股
+```
+1. 选择10-20只活跃度高的股票
+2. 批量分析获取当日评级
+3. 筛选出"买入"评级的股票
+4. 结合个人风险偏好做出决策
+```
+
+### 场景4:龙头股对比
+```
+1. 输入同行业的龙头公司(如茅台、五粮液、泸州老窖)
+2. 批量分析进行横向对比
+3. 对比关键指标:市盈率、ROE、涨跌幅
+4. 选择综合表现最佳的股票
+```
+
+## 🎯 结果解读
+
+### 投资评级
+- **强烈买入**:多位分析师一致看好,建议重点关注
+- **买入**:综合评价积极,可以考虑买入
+- **持有**:观望为主,已持有可继续持有
+- **卖出**:风险较大,建议卖出或不买入
+- **强烈卖出**:多位分析师一致看空,建议立即卖出
+
+### 信心度(1-10分)
+- **8-10分**:高度确定,分析师共识强
+- **6-7分**:较为确定,可以参考
+- **4-5分**:不确定,需谨慎对待
+- **1-3分**:非常不确定,建议忽略
+
+### 技术指标参考
+- **RSI > 70**:超买,可能回调
+- **RSI < 30**:超卖,可能反弹
+- **MACD > 0**:上涨趋势
+- **MACD < 0**:下跌趋势
+
+## 📊 示例演示
+
+### 示例:分析白酒板块
+```python
+输入股票代码:
+600519 # 贵州茅台
+000858 # 五粮液
+000568 # 泸州老窖
+002304 # 洋河股份
+603369 # 今世缘
+
+选择:多线程并行
+
+结果对比表格:
+| 股票代码 | 股票名称 | 当前价格 | 涨跌幅 | 市盈率 | 投资评级 | 信心度 |
+|---------|---------|---------|-------|-------|---------|-------|
+| 600519 | 贵州茅台 | 1688.00 | 2.5% | 38.5 | 买入 | 8 |
+| 000858 | 五粮液 | 168.50 | 1.8% | 28.3 | 买入 | 7 |
+| 000568 | 泸州老窖 | 188.20 | -0.5% | 32.1 | 持有 | 6 |
+| 002304 | 洋河股份 | 128.60 | 0.3% | 24.7 | 买入 | 7 |
+| 603369 | 今世缘 | 38.50 | -1.2% | 21.3 | 持有 | 5 |
+
+筛选结果:
+按"买入"评级筛选 → 3只股票(茅台、五粮液、洋河)
+按信心度降序 → 茅台(8) > 五粮液(7) = 洋河(7)
+```
+
+## 🔧 技术细节
+
+### 多线程实现
+- 使用Python `concurrent.futures.ThreadPoolExecutor`
+- 最大并发数:3(避免API限流)
+- 线程安全:使用锁保护进度更新
+- 异常处理:单个股票失败不影响其他
+
+### 数据保存
+- 每只股票分析完成后立即保存到数据库
+- 保存内容包括:
+ - 股票基本信息
+ - 技术指标
+ - 各分析师报告
+ - 团队讨论
+ - 最终决策
+
+### 性能优化
+- 使用Streamlit缓存机制
+- 股票数据缓存5分钟
+- 避免重复获取相同数据
+
+## 📞 问题反馈
+
+如遇到问题或有改进建议,请联系:
+- 邮箱:ws3101001@126.com
+- 或在项目中提交Issue
+
+---
+
+**免责声明**:本系统提供的分析结果仅供参考,不构成投资建议。股市有风险,投资需谨慎。
+