diff --git a/app.py b/app.py index 9d3b34f..9f07daf 100644 --- a/app.py +++ b/app.py @@ -16,6 +16,7 @@ from monitor_manager import display_monitor_manager, get_monitor_summary from monitor_service import monitor_service from notification_service import notification_service from config_manager import config_manager +from main_force_ui import display_main_force_selector # 页面配置 st.set_page_config( @@ -296,6 +297,17 @@ def main(): st.session_state.show_monitor = True if 'show_history' in st.session_state: del st.session_state.show_history + if 'show_main_force' in st.session_state: + del st.session_state.show_main_force + + if st.button("🎯 主力选股", use_container_width=True, key="nav_main_force"): + st.session_state.show_main_force = True + if 'show_history' in st.session_state: + del st.session_state.show_history + if 'show_monitor' in st.session_state: + del st.session_state.show_monitor + if 'show_config' in st.session_state: + del st.session_state.show_config if st.button("🏠 返回首页", use_container_width=True, key="nav_home"): if 'show_history' in st.session_state: @@ -304,6 +316,8 @@ def main(): del st.session_state.show_monitor if 'show_config' in st.session_state: del st.session_state.show_config + if 'show_main_force' in st.session_state: + del st.session_state.show_main_force if st.button("⚙️ 环境配置", use_container_width=True, key="nav_config"): st.session_state.show_config = True @@ -373,6 +387,7 @@ def main(): **功能说明** - **智能分析**:AI团队深度分析 + - **主力选股**:主力资金精选标的 - **实时监测**:价格监控与提醒 - **历史记录**:查看分析历史 @@ -393,6 +408,11 @@ def main(): display_monitor_manager() return + # 检查是否显示主力选股 + if 'show_main_force' in st.session_state and st.session_state.show_main_force: + display_main_force_selector() + return + # 检查是否显示环境配置 if 'show_config' in st.session_state and st.session_state.show_config: display_config_manager() @@ -539,6 +559,8 @@ def main(): del st.session_state.discussion_result if 'final_decision' in st.session_state: del st.session_state.final_decision + if 'just_completed' in st.session_state: + del st.session_state.just_completed run_stock_analysis(stock_input, period) @@ -566,32 +588,36 @@ def main(): # 运行批量分析 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 - agents_results = st.session_state.agents_results - discussion_result = st.session_state.discussion_result - final_decision = st.session_state.final_decision - - # 重新获取股票数据用于显示图表 - stock_info_current, stock_data, indicators = 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) + # 如果是刚刚完成的分析,清除标志,避免重复显示 + if st.session_state.get('just_completed', False): + st.session_state.just_completed = False + else: + # 重新显示之前的分析结果(页面刷新后) + stock_info = st.session_state.stock_info + agents_results = st.session_state.agents_results + discussion_result = st.session_state.discussion_result + final_decision = st.session_state.final_decision + + # 重新获取股票数据用于显示图表 + stock_info_current, stock_data, indicators = 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) # 检查是否有已完成的批量分析结果 elif 'batch_analysis_results' in st.session_state and st.session_state.batch_analysis_results: @@ -1064,12 +1090,16 @@ def run_stock_analysis(symbol, period): final_decision = agents.make_final_decision(discussion_result, stock_info, indicators) progress_bar.progress(100) - # 保存分析结果到session_state + # 显示最终决策 + display_final_decision(final_decision, stock_info, agents_results, discussion_result) + + # 保存分析结果到session_state(用于页面刷新后显示) st.session_state.analysis_completed = True st.session_state.stock_info = stock_info st.session_state.agents_results = agents_results st.session_state.discussion_result = discussion_result st.session_state.final_decision = final_decision + st.session_state.just_completed = True # 标记刚刚完成分析 # 保存到数据库 try: @@ -1086,9 +1116,6 @@ def run_stock_analysis(symbol, period): except Exception as e: st.warning(f"⚠️ 保存到数据库时出现错误: {str(e)}") - # 显示最终决策 - display_final_decision(final_decision, stock_info, agents_results, discussion_result) - status_text.text("✅ 分析完成!") time.sleep(1) status_text.empty() diff --git a/main_force_analysis.py b/main_force_analysis.py new file mode 100644 index 0000000..158f7a1 --- /dev/null +++ b/main_force_analysis.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +主力选股AI分析整合模块 +整体批量分析,从板块热点和资金流向角度筛选优质标的 +""" + +from typing import Dict, List, Tuple +import pandas as pd +from main_force_selector import main_force_selector +from stock_data import StockDataFetcher +from ai_agents import StockAnalysisAgents +from deepseek_client import DeepSeekClient +import time +import json + +class MainForceAnalyzer: + """主力选股分析器 - 批量整体分析""" + + def __init__(self, model='deepseek-chat'): + self.selector = main_force_selector + self.fetcher = StockDataFetcher() + self.model = model + self.agents = StockAnalysisAgents(model=model) + self.deepseek_client = self.agents.deepseek_client + self.raw_stocks = None + self.final_recommendations = [] + + def run_full_analysis(self, start_date: str = None, days_ago: int = 90, + final_n: int = 5) -> Dict: + """ + 运行完整的主力选股分析流程 - 整体批量分析 + + Args: + start_date: 开始日期,格式如"2025年10月1日" + days_ago: 距今多少天,默认90天 + final_n: 最终精选N只,默认5只 + + Returns: + 分析结果字典 + """ + result = { + 'success': False, + 'total_stocks': 0, + 'filtered_stocks': 0, + 'final_recommendations': [], + 'error': None + } + + try: + print(f"\n{'='*80}") + print(f"🚀 主力选股智能分析系统 - 批量整体分析") + print(f"{'='*80}\n") + + # 步骤1: 获取主力资金净流入前100名股票 + success, raw_data, message = self.selector.get_main_force_stocks( + start_date=start_date, + days_ago=days_ago + ) + + if not success: + result['error'] = message + return result + + result['total_stocks'] = len(raw_data) + + # 步骤2: 智能筛选(涨幅、市值等) + filtered_data = self.selector.filter_stocks( + raw_data, + max_range_change=30.0, + min_market_cap=50.0, + max_market_cap=1300.0 + ) + + result['filtered_stocks'] = len(filtered_data) + + if filtered_data.empty: + result['error'] = "筛选后没有符合条件的股票" + return result + + # 保存原始数据 + self.raw_stocks = filtered_data + + # 步骤3: 整体数据分析(不是逐个分析) + print(f"\n{'='*80}") + print(f"🤖 AI分析师团队开始整体分析...") + print(f"{'='*80}\n") + + # 准备整体数据摘要 + overall_summary = self._prepare_overall_summary(filtered_data) + + # 三大分析师整体分析 + fund_flow_analysis = self._fund_flow_overall_analysis(filtered_data, overall_summary) + industry_analysis = self._industry_overall_analysis(filtered_data, overall_summary) + fundamental_analysis = self._fundamental_overall_analysis(filtered_data, overall_summary) + + # 保存分析报告到对象属性,供UI展示 + self.fund_flow_analysis = fund_flow_analysis + self.industry_analysis = industry_analysis + self.fundamental_analysis = fundamental_analysis + + # 步骤4: 综合决策,精选优质标的 + print(f"\n{'='*80}") + print(f"👔 资深研究员综合评估并精选标的...") + print(f"{'='*80}\n") + + final_recommendations = self._select_best_stocks( + filtered_data, + fund_flow_analysis, + industry_analysis, + fundamental_analysis, + final_n=final_n + ) + + result['final_recommendations'] = final_recommendations + result['success'] = True + + # 显示最终结果 + self._print_final_recommendations(final_recommendations) + + return result + + except Exception as e: + result['error'] = f"分析过程出错: {str(e)}" + import traceback + traceback.print_exc() + return result + + def _prepare_overall_summary(self, df: pd.DataFrame) -> str: + """准备整体数据摘要""" + + summary_lines = [] + summary_lines.append(f"候选股票总数: {len(df)}只") + + # 主力资金统计 + main_fund_cols = [col for col in df.columns if '主力' in col and '净流入' in col] + if main_fund_cols: + col_name = main_fund_cols[0] + df[col_name] = pd.to_numeric(df[col_name], errors='coerce') + total_inflow = df[col_name].sum() + avg_inflow = df[col_name].mean() + summary_lines.append(f"主力资金总净流入: {total_inflow/100000000:.2f}亿") + summary_lines.append(f"平均主力资金净流入: {avg_inflow/100000000:.2f}亿") + + # 涨跌幅统计 + range_cols = [col for col in df.columns if '涨跌幅' in col] + if range_cols: + col_name = range_cols[0] + df[col_name] = pd.to_numeric(df[col_name], errors='coerce') + avg_change = df[col_name].mean() + max_change = df[col_name].max() + min_change = df[col_name].min() + summary_lines.append(f"平均涨跌幅: {avg_change:.2f}%") + summary_lines.append(f"涨跌幅范围: {min_change:.2f}% ~ {max_change:.2f}%") + + # 行业分布 + industry_cols = [col for col in df.columns if '行业' in col] + if industry_cols: + col_name = industry_cols[0] + top_industries = df[col_name].value_counts().head(10) + summary_lines.append("\n主要行业分布:") + for industry, count in top_industries.items(): + summary_lines.append(f" - {industry}: {count}只") + + return "\n".join(summary_lines) + + def _fund_flow_overall_analysis(self, df: pd.DataFrame, summary: str) -> str: + """资金流向整体分析""" + + print("💰 资金流向分析师整体分析中...") + + # 准备数据表格 + data_table = self._prepare_data_table(df, focus='fund_flow') + + prompt = f""" +你是一名资深的资金面分析师,现在需要你从整体角度分析这批主力资金净流入的股票。 + +【整体数据摘要】 +{summary} + +【候选股票详细数据】(共{len(df)}只) +{data_table} + +【分析任务】 +请从资金流向的整体角度进行分析,重点关注: + +1. **资金流向特征** + - 哪些板块/行业资金流入最集中? + - 主力资金的整体行为特征(大规模建仓/试探性进场/板块轮动) + - 资金流向与涨跌幅的配合情况 + +2. **优质标的识别** + - 从资金面角度,哪些股票最值得关注? + - 主力资金流入大但涨幅不高的潜力股 + - 资金持续流入且趋势明确的股票 + +3. **板块热点判断** + - 当前资金最看好哪些板块? + - 是否有板块轮动迹象? + - 新兴热点 vs 传统强势板块 + +4. **投资建议** + - 从资金面角度,建议重点关注哪3-5只股票? + - 理由和风险提示 + +请给出专业、系统的资金面整体分析报告。 +""" + + messages = [ + {"role": "system", "content": "你是资金面分析专家,擅长从整体资金流向中发现投资机会。"}, + {"role": "user", "content": prompt} + ] + + analysis = self.deepseek_client.call_api(messages, max_tokens=4000) + + print(" ✅ 资金流向整体分析完成") + time.sleep(1) + + return analysis + + def _industry_overall_analysis(self, df: pd.DataFrame, summary: str) -> str: + """行业板块整体分析""" + + print("📊 行业板块分析师整体分析中...") + + # 准备数据表格 + data_table = self._prepare_data_table(df, focus='industry') + + prompt = f""" +你是一名资深的行业板块分析师,现在需要你从行业热点和板块轮动角度分析这批股票。 + +【整体数据摘要】 +{summary} + +【候选股票详细数据】(共{len(df)}只) +{data_table} + +【分析任务】 +请从行业板块的整体角度进行分析,重点关注: + +1. **热点板块识别** + - 哪些行业/板块最受资金青睐? + - 热点板块的持续性如何? + - 是否有新兴热点正在形成? + +2. **板块特征分析** + - 各板块的涨幅与资金流入匹配度 + - 哪些板块处于启动阶段(资金流入但涨幅不大) + - 哪些板块可能过热(涨幅高但资金流入减弱) + +3. **行业前景评估** + - 主力资金集中的行业,基本面支撑如何? + - 政策面、产业面是否有催化因素? + - 行业竞争格局和龙头地位 + +4. **优质标的推荐** + - 从行业板块角度,推荐3-5只最具潜力的股票 + - 推荐理由(行业地位、成长空间、催化因素) + +请给出专业、深入的行业板块分析报告。 +""" + + messages = [ + {"role": "system", "content": "你是行业板块分析专家,擅长发现市场热点和板块机会。"}, + {"role": "user", "content": prompt} + ] + + analysis = self.deepseek_client.call_api(messages, max_tokens=4000) + + print(" ✅ 行业板块整体分析完成") + time.sleep(1) + + return analysis + + def _fundamental_overall_analysis(self, df: pd.DataFrame, summary: str) -> str: + """财务基本面整体分析""" + + print("📈 财务基本面分析师整体分析中...") + + # 准备数据表格 + data_table = self._prepare_data_table(df, focus='fundamental') + + prompt = f""" +你是一名资深的基本面分析师,现在需要你从财务质量和基本面角度分析这批股票。 + +【整体数据摘要】 +{summary} + +【候选股票详细数据】(共{len(df)}只) +{data_table} + +【分析任务】 +请从财务基本面的整体角度进行分析,重点关注: + +1. **财务质量评估** + - 整体财务指标健康度如何? + - 哪些股票盈利能力、成长性突出? + - 是否存在财务风险较大的股票? + +2. **估值水平分析** + - 市盈率、市净率的整体分布 + - 哪些股票估值合理且有成长空间? + - 高估值是否有业绩支撑? + +3. **成长性评估** + - 营收、净利润增长情况 + - 哪些股票成长性最好? + - 成长能力评分较高的股票 + +4. **优质标的筛选** + - 从基本面角度,推荐3-5只最优质的股票 + - 推荐理由(财务健康、估值合理、成长性好) + +请给出专业、详实的基本面分析报告。 +""" + + messages = [ + {"role": "system", "content": "你是基本面分析专家,擅长从财务角度评估投资价值。"}, + {"role": "user", "content": prompt} + ] + + analysis = self.deepseek_client.call_api(messages, max_tokens=4000) + + print(" ✅ 财务基本面整体分析完成") + time.sleep(1) + + return analysis + + def _prepare_data_table(self, df: pd.DataFrame, focus: str = 'all') -> str: + """准备数据表格用于AI分析""" + + # 选择关键列 + key_columns = ['股票代码', '股票简称'] + + # 根据分析重点添加相关列 + if focus == 'fund_flow' or focus == 'all': + fund_cols = [col for col in df.columns if '主力' in col or '资金' in col] + key_columns.extend(fund_cols[:3]) # 最多3列资金数据 + + if focus == 'industry' or focus == 'all': + industry_cols = [col for col in df.columns if '行业' in col] + key_columns.extend(industry_cols[:1]) + + # 智能匹配区间涨跌幅列 + interval_pct_col = None + possible_names = [ + '区间涨跌幅:前复权', '区间涨跌幅:前复权(%)', '区间涨跌幅(%)', + '区间涨跌幅', '涨跌幅:前复权', '涨跌幅:前复权(%)', '涨跌幅(%)', '涨跌幅' + ] + for name in possible_names: + for col in df.columns: + if name in col: + interval_pct_col = col + break + if interval_pct_col: + break + if interval_pct_col: + key_columns.append(interval_pct_col) + + if focus == 'fundamental' or focus == 'all': + fundamental_cols = [col for col in df.columns if any( + keyword in col for keyword in ['市盈率', '市净率', '营收', '净利润', '评分'] + )] + key_columns.extend(fundamental_cols[:5]) + + # 去重并保持顺序 + seen = set() + unique_columns = [] + for col in key_columns: + if col in df.columns and col not in seen: + seen.add(col) + unique_columns.append(col) + + # 限制显示前50只股票的详细数据,避免超出token限制 + display_df = df[unique_columns].head(50) + + # 转换为表格字符串 + table_str = display_df.to_string(index=False, max_rows=50) + + if len(df) > 50: + table_str += f"\n... 还有 {len(df) - 50} 只股票未显示" + + return table_str + + def _select_best_stocks(self, df: pd.DataFrame, + fund_analysis: str, + industry_analysis: str, + fundamental_analysis: str, + final_n: int = 5) -> List[Dict]: + """综合三位分析师的意见,精选最优标的""" + + # 准备完整数据表格 + data_table = self._prepare_data_table(df, focus='all') + + prompt = f""" +你是一名资深股票研究员,具有20年以上的投资研究经验。现在需要你综合三位分析师的意见, +从{len(df)}只候选股票中精选出{final_n}只最具投资价值的优质标的。 + +【候选股票数据】 +{data_table} + +【资金流向分析师观点】 +{fund_analysis} + +【行业板块分析师观点】 +{industry_analysis} + +【财务基本面分析师观点】 +{fundamental_analysis} + +【筛选标准】 +1. **主力资金**: 主力资金净流入较多,显示机构看好 +2. **涨幅适中**: 区间涨跌幅不是很高(避免追高),还有上涨空间 +3. **行业热点**: 所属行业有发展前景,是市场热点 +4. **基本面良好**: 财务指标健康,盈利能力强 +5. **综合平衡**: 资金、行业、基本面三方面都不错 + +【任务要求】 +综合三位分析师的观点,精选出{final_n}只最优标的。 + +对于每只精选股票,请提供: +1. **股票代码和名称** +2. **核心推荐理由**(3-5条,综合资金、行业、基本面) +3. **投资亮点**(最突出的优势) +4. **风险提示**(需要注意的风险) +5. **建议仓位**(如20-30%) +6. **投资周期**(短期/中期/长期) + +请按以下JSON格式输出(只输出JSON,不要其他内容): +```json +{{ + "recommendations": [ + {{ + "rank": 1, + "symbol": "股票代码", + "name": "股票名称", + "reasons": [ + "理由1:资金面角度", + "理由2:行业板块角度", + "理由3:基本面角度" + ], + "highlights": "投资亮点描述", + "risks": "风险提示", + "position": "建议仓位", + "investment_period": "投资周期" + }} + ] +}} +``` + +注意: +- 必须严格按照JSON格式输出 +- 推荐数量为{final_n}只 +- 按投资价值从高到低排序 +- 理由要具体、有说服力,体现三位分析师的综合观点 +""" + + try: + print(" 🔍 正在综合评估并精选标的...") + + messages = [ + {"role": "system", "content": "你是资深股票研究员,擅长综合多维度分析做出投资决策。"}, + {"role": "user", "content": prompt} + ] + + response = self.deepseek_client.call_api(messages, max_tokens=4000) + + # 解析JSON响应 + import re + + # 提取JSON部分 + json_match = re.search(r'```json\s*(\{.*?\})\s*```', response, re.DOTALL) + if json_match: + json_str = json_match.group(1) + else: + # 尝试直接解析 + json_str = response + + result = json.loads(json_str) + recommendations = result.get('recommendations', []) + + # 补充详细数据 + for rec in recommendations: + symbol = rec['symbol'] + # 从原始数据中找到对应股票 + stock_data = df[df['股票代码'] == symbol] + if not stock_data.empty: + rec['stock_data'] = stock_data.iloc[0].to_dict() + + return recommendations + + except Exception as e: + print(f" ❌ JSON解析失败,使用备选方案: {e}") + + # 降级方案:按主力资金排序返回前N个 + main_fund_cols = [col for col in df.columns if '主力' in col and '净流入' in col] + if main_fund_cols: + col_name = main_fund_cols[0] + df[col_name] = pd.to_numeric(df[col_name], errors='coerce') + sorted_df = df.nlargest(final_n, col_name) + else: + sorted_df = df.head(final_n) + + recommendations = [] + for i, (idx, row) in enumerate(sorted_df.iterrows(), 1): + recommendations.append({ + 'rank': i, + 'symbol': row.get('股票代码', 'N/A'), + 'name': row.get('股票简称', 'N/A'), + 'reasons': [ + f"主力资金净流入较多", + f"所属行业: {row.get('所属同花顺行业', 'N/A')}", + f"涨跌幅适中" + ], + 'highlights': '主力资金持续关注', + 'risks': '需关注后续走势', + 'position': '15-25%', + 'investment_period': '中短期', + 'stock_data': row.to_dict() + }) + + return recommendations + + def _print_final_recommendations(self, recommendations: List[Dict]): + """打印最终推荐结果""" + if not recommendations: + print("❌ 未能生成推荐结果") + return + + print(f"\n{'='*80}") + print(f"⭐ 最终精选推荐 ({len(recommendations)}只)") + print(f"{'='*80}\n") + + for rec in recommendations: + print(f"【第{rec['rank']}名】{rec['symbol']} - {rec['name']}") + print(f"{'-'*60}") + + print(f"📌 推荐理由:") + for reason in rec.get('reasons', []): + print(f" • {reason}") + + print(f"\n💡 投资亮点: {rec.get('highlights', 'N/A')}") + print(f"⚠️ 风险提示: {rec.get('risks', 'N/A')}") + print(f"📊 建议仓位: {rec.get('position', 'N/A')}") + print(f"⏰ 投资周期: {rec.get('investment_period', 'N/A')}") + print(f"{'='*80}\n") + +# 全局实例 +main_force_analyzer = MainForceAnalyzer() diff --git a/main_force_pdf_generator.py b/main_force_pdf_generator.py new file mode 100644 index 0000000..f9814cd --- /dev/null +++ b/main_force_pdf_generator.py @@ -0,0 +1,456 @@ +import os +import base64 +import re +from datetime import datetime +import streamlit as st +import pandas as pd + +def generate_main_force_markdown_report(analyzer, result): + """生成主力选股Markdown格式的分析报告""" + + # 获取当前时间 + current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S") + + # 获取分析参数 + params = result.get('params', {}) + start_date = params.get('start_date', 'N/A') + min_cap = params.get('min_market_cap', 50) + max_cap = params.get('max_market_cap', 5000) + max_change = params.get('max_range_change', 50) + + markdown_content = f""" +# 主力选股AI分析报告 + +**生成时间**: {current_time} + +--- + +## 📊 选股参数 + +| 项目 | 值 | +|------|-----| +| **起始日期** | {start_date} | +| **市值范围** | {min_cap}亿 - {max_cap}亿 | +| **最大涨跌幅** | {max_change}% | +| **初始数据量** | {result.get('total_fetched', 0)}只 | +| **筛选后数量** | {result.get('filtered_count', 0)}只 | +| **最终推荐** | {len(result.get('final_recommendations', []))}只 | + +--- + +## 🤖 AI分析师团队报告 + +""" + + # 添加资金流向分析 + if hasattr(analyzer, 'fund_flow_analysis') and analyzer.fund_flow_analysis: + markdown_content += f""" +### 💰 资金流向分析师 + +{analyzer.fund_flow_analysis} + +--- + +""" + + # 添加行业板块分析 + if hasattr(analyzer, 'industry_analysis') and analyzer.industry_analysis: + markdown_content += f""" +### 📊 行业板块及市场热点分析师 + +{analyzer.industry_analysis} + +--- + +""" + + # 添加财务基本面分析 + if hasattr(analyzer, 'fundamental_analysis') and analyzer.fundamental_analysis: + markdown_content += f""" +### 📈 财务基本面分析师 + +{analyzer.fundamental_analysis} + +--- + +""" + + # 添加精选推荐 + markdown_content += """ +## ⭐ 精选推荐股票 + +""" + + final_recommendations = result.get('final_recommendations', []) + if final_recommendations: + for rec in final_recommendations: + markdown_content += f""" +### 【第{rec['rank']}名】{rec['symbol']} - {rec['name']} + +**推荐理由**: +{rec.get('reason', '暂无')} + +**关键指标**: +""" + if 'stock_data' in rec: + stock_data = rec['stock_data'] + markdown_content += f""" +- **所属行业**: {stock_data.get('industry', 'N/A')} +- **市值**: {stock_data.get('market_cap', 'N/A')} +- **主力资金流向**: {stock_data.get('main_fund_inflow', 'N/A')} +- **区间涨跌幅**: {stock_data.get('range_change', 'N/A')}% +- **市盈率**: {stock_data.get('pe_ratio', 'N/A')} +- **市净率**: {stock_data.get('pb_ratio', 'N/A')} + +""" + + if 'scores' in rec.get('stock_data', {}): + scores = rec['stock_data']['scores'] + if scores: + markdown_content += "**能力评分**:\n" + for score_name, score_value in scores.items(): + markdown_content += f"- {score_name}: {score_value}\n" + markdown_content += "\n" + + markdown_content += "---\n\n" + else: + markdown_content += "暂无推荐股票\n\n---\n\n" + + # 添加候选股票列表(前100名,按主力资金排序) + if analyzer and analyzer.raw_stocks is not None and not analyzer.raw_stocks.empty: + markdown_content += """ +## 📋 候选股票完整列表(按主力资金净流入排序) + +""" + + # 获取主力资金列名 + df = analyzer.raw_stocks + main_fund_col = None + main_fund_patterns = [ + '区间主力资金流向', '区间主力资金净流入', + '主力资金流向', '主力资金净流入', '主力净流入' + ] + for pattern in main_fund_patterns: + matching = [col for col in df.columns if pattern in col] + if matching: + main_fund_col = matching[0] + break + + # 按主力资金排序 + if main_fund_col: + df_sorted = df.copy() + df_sorted[main_fund_col] = pd.to_numeric(df_sorted[main_fund_col], errors='coerce') + df_sorted = df_sorted.sort_values(by=main_fund_col, ascending=False).head(100) + else: + df_sorted = df.head(100) + + # 选择要显示的列 + display_cols = [] + if '股票代码' in df_sorted.columns: + display_cols.append('股票代码') + if '股票简称' in df_sorted.columns: + display_cols.append('股票简称') + + # 行业 + industry_cols = [col for col in df_sorted.columns if '行业' in col] + if industry_cols: + display_cols.append(industry_cols[0]) + + # 主力资金 + if main_fund_col: + display_cols.append(main_fund_col) + + # 涨跌幅 + change_cols = [col for col in df_sorted.columns if '涨跌幅' in col] + if change_cols: + display_cols.append(change_cols[0]) + + # 市值、市盈率、市净率 + for col_name in ['总市值', '市盈率', '市净率']: + matching_cols = [col for col in df_sorted.columns if col_name in col] + if matching_cols: + display_cols.append(matching_cols[0]) + + # 生成表格 + if display_cols: + final_display_cols = [col for col in display_cols if col in df_sorted.columns] + markdown_content += "| 序号 | " + " | ".join(final_display_cols) + " |\n" + markdown_content += "|------|" + "|".join(['-----' for _ in final_display_cols]) + "|\n" + + for idx, (_, row) in enumerate(df_sorted[final_display_cols].iterrows(), 1): + row_data = [str(idx)] + for col in final_display_cols: + value = row[col] + if pd.isna(value): + row_data.append('N/A') + else: + row_data.append(str(value)) + markdown_content += "| " + " | ".join(row_data) + " |\n" + + markdown_content += "\n" + + # 添加免责声明 + markdown_content += f""" +--- + +## 📝 免责声明 + +本报告由AI系统生成,仅供参考,不构成投资建议。投资有风险,入市需谨慎。请在做出投资决策前咨询专业的投资顾问。 + +--- + +*报告生成时间: {current_time}* +*主力选股AI分析系统 v1.0* +""" + + return markdown_content + + +def generate_html_content(markdown_content): + """将Markdown转换为HTML""" + html_content = f""" + + + + + 主力选股AI分析报告 + + + +
+""" + + # 简单的Markdown到HTML转换 + html_body = markdown_content + html_body = html_body.replace('\n# ', '\n

').replace('\n## ', '\n

').replace('\n### ', '\n

') + html_body = html_body.replace('# ', '

').replace('## ', '

').replace('### ', '

') + html_body = html_body.replace('\n---\n', '\n
\n') + + # 处理粗体文本 + html_body = re.sub(r'\*\*(.*?)\*\*', r'\1', 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('') + in_table = True + cells = [cell.strip() for cell in line.split('|')[1:-1]] + processed_lines.append('') + for cell in cells: + processed_lines.append(f'') + processed_lines.append('') + elif '|' in line and in_table: + if '---' not in line: + cells = [cell.strip() for cell in line.split('|')[1:-1]] + processed_lines.append('') + for cell in cells: + processed_lines.append(f'') + processed_lines.append('') + elif in_table and '|' not in line: + processed_lines.append('
{cell}
{cell}
') + in_table = False + processed_lines.append(line) + else: + processed_lines.append(line) + + if in_table: + processed_lines.append('') + + html_body = '\n'.join(processed_lines) + + # 处理列表 + html_body = re.sub(r'\n- (.*)', r'\n
  • \1
  • ', html_body) + html_body = re.sub(r'(
  • .*
  • )\n(?!
  • )', r'
      \1
    \n', html_body) + html_body = re.sub(r'(
  • .*
  • \n)+', lambda m: '\n', html_body) + + # 处理换行 + html_body = html_body.replace('\n\n', '

    ') + html_body = '

    ' + html_body + '

    ' + + html_content += html_body + html_content += """ +

    + + +""" + + return html_content + + +def create_download_link(content, filename, link_text): + """创建下载链接""" + b64 = base64.b64encode(content.encode()).decode() + href = f'{link_text}' + return href + + +def create_html_download_link(content, filename, link_text): + """创建HTML下载链接""" + b64 = base64.b64encode(content.encode('utf-8')).decode() + href = f'{link_text}' + return href + + +def display_report_download_section(analyzer, result): + """显示报告下载区域""" + + st.markdown("---") + st.markdown("### 📥 下载分析报告") + + col1, col2 = st.columns(2) + + with col1: + st.markdown("#### 📄 Markdown格式") + st.caption("适合编辑和进一步处理") + + # 生成Markdown报告 + markdown_content = generate_main_force_markdown_report(analyzer, result) + + # 生成文件名 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + md_filename = f"主力选股分析报告_{timestamp}.md" + + # 创建下载链接 + md_link = create_download_link(markdown_content, md_filename, "📥 下载Markdown报告") + st.markdown(md_link, unsafe_allow_html=True) + + # 显示预览 + with st.expander("👀 预览Markdown内容"): + st.code(markdown_content[:2000] + "..." if len(markdown_content) > 2000 else markdown_content) + + with col2: + st.markdown("#### 🌐 HTML格式") + st.caption("可在浏览器中打开查看") + + # 生成HTML报告 + html_content = generate_html_content(markdown_content) + + # 生成文件名 + html_filename = f"主力选股分析报告_{timestamp}.html" + + # 创建下载链接 + html_link = create_html_download_link(html_content, html_filename, "📥 下载HTML报告") + st.markdown(html_link, unsafe_allow_html=True) + + # 显示说明 + st.info("💡 HTML报告可以直接在浏览器中打开,格式美观易读") + + # 添加CSV下载(候选股票列表) + if analyzer and analyzer.raw_stocks is not None and not analyzer.raw_stocks.empty: + st.markdown("---") + st.markdown("#### 📊 候选股票数据") + + # 按主力资金排序 + df = analyzer.raw_stocks.copy() + main_fund_col = None + main_fund_patterns = [ + '区间主力资金流向', '区间主力资金净流入', + '主力资金流向', '主力资金净流入', '主力净流入' + ] + for pattern in main_fund_patterns: + matching = [col for col in df.columns if pattern in col] + if matching: + main_fund_col = matching[0] + break + + if main_fund_col: + df[main_fund_col] = pd.to_numeric(df[main_fund_col], errors='coerce') + df = df.sort_values(by=main_fund_col, ascending=False) + + # 导出为CSV + csv = df.to_csv(index=False, encoding='utf-8-sig') + csv_filename = f"主力选股候选列表_{timestamp}.csv" + + st.download_button( + label="📥 下载候选股票CSV", + data=csv, + file_name=csv_filename, + mime="text/csv", + use_container_width=True + ) + diff --git a/main_force_selector.py b/main_force_selector.py new file mode 100644 index 0000000..3ba5306 --- /dev/null +++ b/main_force_selector.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +主力选股模块 +使用pywencai获取主力资金净流入前100名股票,并进行智能筛选 +""" + +import pandas as pd +import pywencai +from datetime import datetime, timedelta +from typing import Dict, List, Tuple +import time + +class MainForceStockSelector: + """主力选股类""" + + def __init__(self): + self.raw_data = None + self.filtered_stocks = None + + def get_main_force_stocks(self, start_date: str = None, days_ago: int = 90) -> Tuple[bool, pd.DataFrame, str]: + """ + 获取主力资金净流入前100名股票 + + Args: + start_date: 开始日期,格式如"2025年10月1日",如果不提供则使用days_ago + days_ago: 距今多少天,默认90天(约3个月) + + Returns: + (success, dataframe, message) + """ + try: + # 如果没有提供开始日期,根据days_ago计算 + if not start_date: + date_obj = datetime.now() - timedelta(days=days_ago) + start_date = f"{date_obj.year}年{date_obj.month}月{date_obj.day}日" + + print(f"\n{'='*60}") + print(f"🔍 主力选股 - 数据获取中") + print(f"{'='*60}") + print(f"开始日期: {start_date}") + print(f"目标: 获取主力资金净流入排名前100名股票") + + # 构建查询语句 - 使用多个备选方案,所有方案都要求计算区间涨跌幅 + queries = [ + # 方案1: 完整查询(最优) + f"{start_date}以来主力资金净流入排名,并计算区间涨跌幅,市值50-5000亿之间,非科创非st," + f"所属同花顺行业,总市值,净利润,营收,市盈率,市净率," + f"盈利能力评分,成长能力评分,营运能力评分,偿债能力评分," + f"现金流评分,资产质量评分,流动性评分,资本充足性评分", + + # 方案2: 简化查询 + f"{start_date}以来主力资金净流入,并计算区间涨跌幅,市值50-5000亿,非科创非st," + f"所属同花顺行业,总市值,净利润,营收,市盈率,市净率", + + # 方案3: 基础查询 + f"{start_date}以来主力资金净流入排名,并计算区间涨跌幅,市值50-5000亿,非科创非st," + f"所属行业,总市值", + + # 方案4: 最简查询 + f"{start_date}以来主力资金净流入前100名,并计算区间涨跌幅,市值50-5000亿,非st非科创板,所属行业,总市值", + ] + + # 尝试不同的查询方案 + for i, query in enumerate(queries, 1): + print(f"\n尝试方案 {i}/{len(queries)}...") + print(f"查询语句: {query[:100]}...") + + try: + result = pywencai.get(query=query, loop=True) + + if result is None: + print(f" ⚠️ 方案{i}返回None,尝试下一个方案") + continue + + # 转换为DataFrame + df_result = self._convert_to_dataframe(result) + + if df_result is None or df_result.empty: + print(f" ⚠️ 方案{i}数据为空,尝试下一个方案") + continue + + # 成功获取数据 + print(f" ✅ 方案{i}成功!获取到 {len(df_result)} 只股票") + self.raw_data = df_result + + # 显示获取到的列名 + print(f"\n获取到的数据字段:") + for col in df_result.columns[:15]: # 只显示前15个字段 + print(f" - {col}") + if len(df_result.columns) > 15: + print(f" ... 还有 {len(df_result.columns) - 15} 个字段") + + return True, df_result, f"成功获取{len(df_result)}只股票数据" + + except Exception as e: + print(f" ❌ 方案{i}失败: {str(e)}") + time.sleep(2) # 失败后等待2秒再试 + continue + + # 所有方案都失败 + error_msg = "所有查询方案都失败了,请检查网络或稍后重试" + print(f"\n❌ {error_msg}") + return False, None, error_msg + + except Exception as e: + error_msg = f"获取主力选股数据失败: {str(e)}" + print(f"\n❌ {error_msg}") + return False, None, error_msg + + def _convert_to_dataframe(self, result) -> pd.DataFrame: + """转换问财返回结果为DataFrame""" + try: + if isinstance(result, pd.DataFrame): + return result + elif isinstance(result, dict): + # 检查是否有嵌套的tableV1结构 + if 'tableV1' in result: + table_data = result['tableV1'] + if isinstance(table_data, pd.DataFrame): + return table_data + elif isinstance(table_data, list): + return pd.DataFrame(table_data) + # 直接转换字典 + return pd.DataFrame([result]) + elif isinstance(result, list): + return pd.DataFrame(result) + else: + return None + except Exception as e: + print(f" 转换DataFrame失败: {e}") + return None + + def filter_stocks(self, df: pd.DataFrame, + max_range_change: float = 30.0, + min_market_cap: float = 50.0, + max_market_cap: float = 1300.0) -> pd.DataFrame: + """ + 智能筛选股票 + + Args: + df: 原始数据 + max_range_change: 区间涨跌幅上限(%),默认30% + min_market_cap: 最小市值(亿),默认50亿 + max_market_cap: 最大市值(亿),默认1300亿 + + Returns: + 筛选后的DataFrame + """ + if df is None or df.empty: + return df + + print(f"\n{'='*60}") + print(f"🔍 智能筛选中...") + print(f"{'='*60}") + print(f"筛选条件:") + print(f" - 区间涨跌幅 < {max_range_change}%") + print(f" - 市值范围: {min_market_cap}-{max_market_cap}亿") + + original_count = len(df) + filtered_df = df.copy() + + # 1. 筛选区间涨跌幅(智能匹配列名) + # 优先精确匹配,按优先级查找 + interval_pct_col = None + possible_interval_pct_names = [ + '区间涨跌幅:前复权', + '区间涨跌幅:前复权(%)', + '区间涨跌幅(%)', + '区间涨跌幅', + '涨跌幅:前复权', + '涨跌幅:前复权(%)', + '涨跌幅(%)', + '涨跌幅' + ] + + # 优先精确匹配 + for name in possible_interval_pct_names: + for col in df.columns: + if name in col: + interval_pct_col = col + break + if interval_pct_col: + break + + if interval_pct_col: + print(f"\n使用字段: {interval_pct_col}") + + # 转换为数值并筛选 + filtered_df[interval_pct_col] = pd.to_numeric(filtered_df[interval_pct_col], errors='coerce') + before = len(filtered_df) + filtered_df = filtered_df[ + (filtered_df[interval_pct_col].notna()) & + (filtered_df[interval_pct_col] < max_range_change) + ] + print(f" 区间涨跌幅筛选: {before} -> {len(filtered_df)} 只") + else: + print(f" ⚠️ 未找到区间涨跌幅字段,跳过涨跌幅筛选") + print(f" 可用字段: {list(df.columns[:10])}") + + # 2. 筛选市值 + market_cap_cols = [col for col in df.columns if '总市值' in col or '市值' in col] + if market_cap_cols: + col_name = market_cap_cols[0] + print(f"\n使用字段: {col_name}") + + # 转换为数值(单位可能是亿或元) + filtered_df[col_name] = pd.to_numeric(filtered_df[col_name], errors='coerce') + + # 判断单位(如果值很大,可能是元) + max_val = filtered_df[col_name].max() + if max_val > 100000: # 大于10万,认为是元 + print(f" 检测到单位为元,转换为亿") + filtered_df[col_name] = filtered_df[col_name] / 100000000 + + before = len(filtered_df) + filtered_df = filtered_df[ + (filtered_df[col_name].notna()) & + (filtered_df[col_name] >= min_market_cap) & + (filtered_df[col_name] <= max_market_cap) + ] + print(f" 市值筛选: {before} -> {len(filtered_df)} 只") + + # 3. 去除ST股票(额外保险) + if '股票简称' in filtered_df.columns: + before = len(filtered_df) + filtered_df = filtered_df[~filtered_df['股票简称'].str.contains('ST', na=False)] + if before != len(filtered_df): + print(f" ST股票过滤: {before} -> {len(filtered_df)} 只") + + print(f"\n筛选完成: {original_count} -> {len(filtered_df)} 只股票") + + self.filtered_stocks = filtered_df + return filtered_df + + def get_top_stocks(self, df: pd.DataFrame, top_n: int = 20) -> pd.DataFrame: + """ + 获取主力资金净流入最多的前N只股票 + + Args: + df: 筛选后的数据 + top_n: 取前N名,默认20 + + Returns: + 前N名股票DataFrame + """ + if df is None or df.empty: + return df + + # 查找主力资金相关列(智能匹配) + main_fund_col = None + main_fund_patterns = [ + '区间主力资金流向', # 实际列名 + '区间主力资金净流入', + '主力资金流向', + '主力资金净流入', + '主力净流入' + ] + for pattern in main_fund_patterns: + matching = [col for col in df.columns if pattern in col] + if matching: + main_fund_col = matching[0] + break + + if main_fund_col: + print(f"\n使用字段排序: {main_fund_col}") + + # 转换为数值并排序 + df[main_fund_col] = pd.to_numeric(df[main_fund_col], errors='coerce') + top_df = df.nlargest(top_n, main_fund_col) + + print(f"获取主力资金净流入前 {len(top_df)} 名") + return top_df + else: + # 如果没有主力资金列,直接返回前N条 + print(f"未找到主力资金列,返回前{top_n}条数据") + return df.head(top_n) + + def format_stock_list_for_analysis(self, df: pd.DataFrame) -> List[Dict]: + """ + 格式化股票列表,准备提交给AI分析师 + + Args: + df: 股票数据DataFrame + + Returns: + 格式化后的股票列表 + """ + if df is None or df.empty: + return [] + + stock_list = [] + + for idx, row in df.iterrows(): + stock_data = { + 'symbol': row.get('股票代码', 'N/A'), + 'name': row.get('股票简称', 'N/A'), + 'industry': row.get('所属同花顺行业', row.get('所属行业', 'N/A')), + 'market_cap': row.get('总市值[20241209]', row.get('总市值', 'N/A')), + 'range_change': None, + 'main_fund_inflow': None, + 'pe_ratio': row.get('市盈率', 'N/A'), + 'pb_ratio': row.get('市净率', 'N/A'), + 'revenue': row.get('营业收入', row.get('营收', 'N/A')), + 'net_profit': row.get('净利润', 'N/A'), + 'scores': {}, + 'raw_data': row.to_dict() + } + + # 提取区间涨跌幅(使用智能匹配) + interval_pct_col = None + possible_names = [ + '区间涨跌幅:前复权', '区间涨跌幅:前复权(%)', '区间涨跌幅(%)', + '区间涨跌幅', '涨跌幅:前复权', '涨跌幅:前复权(%)', '涨跌幅(%)', '涨跌幅' + ] + for name in possible_names: + for col in df.columns: + if name in col: + interval_pct_col = col + break + if interval_pct_col: + break + if interval_pct_col: + stock_data['range_change'] = row.get(interval_pct_col, 'N/A') + + # 提取主力资金(智能匹配) + main_fund_col = None + main_fund_patterns = [ + '区间主力资金流向', '区间主力资金净流入', + '主力资金流向', '主力资金净流入', '主力净流入' + ] + for pattern in main_fund_patterns: + matching = [col for col in df.columns if pattern in col] + if matching: + main_fund_col = matching[0] + break + if main_fund_col: + stock_data['main_fund_inflow'] = row.get(main_fund_col, 'N/A') + + # 提取评分 + score_keywords = ['评分', '能力'] + for col in df.columns: + if any(keyword in col for keyword in score_keywords): + stock_data['scores'][col] = row.get(col, 'N/A') + + stock_list.append(stock_data) + + return stock_list + + def print_stock_summary(self, stock_list: List[Dict]): + """打印股票摘要信息""" + print(f"\n{'='*80}") + print(f"📊 候选股票列表 ({len(stock_list)}只)") + print(f"{'='*80}") + print(f"{'序号':<4} {'代码':<8} {'名称':<12} {'行业':<15} {'主力资金':<12} {'涨跌幅':<8}") + print(f"{'-'*80}") + + for i, stock in enumerate(stock_list, 1): + symbol = stock['symbol'] + name = stock['name'][:10] if isinstance(stock['name'], str) else 'N/A' + industry = stock['industry'][:13] if isinstance(stock['industry'], str) else 'N/A' + + # 格式化主力资金 + main_fund = stock['main_fund_inflow'] + if isinstance(main_fund, (int, float)): + if abs(main_fund) >= 100000000: # 大于1亿 + main_fund_str = f"{main_fund/100000000:.2f}亿" + else: + main_fund_str = f"{main_fund/10000:.2f}万" + else: + main_fund_str = 'N/A' + + # 格式化涨跌幅 + change = stock['range_change'] + if isinstance(change, (int, float)): + change_str = f"{change:.2f}%" + else: + change_str = 'N/A' + + print(f"{i:<4} {symbol:<8} {name:<12} {industry:<15} {main_fund_str:<12} {change_str:<8}") + + print(f"{'='*80}\n") + +# 全局实例 +main_force_selector = MainForceStockSelector() + diff --git a/main_force_ui.py b/main_force_ui.py new file mode 100644 index 0000000..8f37ca3 --- /dev/null +++ b/main_force_ui.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +主力选股UI模块 +""" + +import streamlit as st +from datetime import datetime, timedelta +from main_force_analysis import MainForceAnalyzer +from main_force_pdf_generator import display_report_download_section +import pandas as pd + +def display_main_force_selector(): + """显示主力选股界面""" + + st.markdown("## 🎯 主力选股 - 智能筛选优质标的") + st.markdown("---") + + st.markdown(""" + ### 功能说明 + + 本功能通过以下步骤筛选优质股票: + + 1. **数据获取**: 使用问财获取指定日期以来主力资金净流入前100名股票 + 2. **智能筛选**: 过滤掉涨幅过高、市值不符的股票 + 3. **AI分析**: 调用资金流向、行业板块、财务基本面三大分析师团队 + 4. **综合决策**: 资深研究员综合评估,精选3-5只优质标的 + + **筛选标准**: + - ✅ 主力资金净流入较多 + - ✅ 区间涨跌幅适中(避免追高) + - ✅ 财务基本面良好 + - ✅ 行业前景明朗 + - ✅ 综合素质优秀 + """) + + st.markdown("---") + + # 参数设置 + col1, col2, col3 = st.columns(3) + + with col1: + date_option = st.selectbox( + "选择时间区间", + ["最近3个月", "最近6个月", "最近1年", "自定义日期"] + ) + + if date_option == "最近3个月": + days_ago = 90 + start_date = None + elif date_option == "最近6个月": + days_ago = 180 + start_date = None + elif date_option == "最近1年": + days_ago = 365 + start_date = None + else: + custom_date = st.date_input( + "选择开始日期", + value=datetime.now() - timedelta(days=90) + ) + start_date = f"{custom_date.year}年{custom_date.month}月{custom_date.day}日" + days_ago = None + + with col2: + final_n = st.slider( + "最终精选数量", + min_value=3, + max_value=10, + value=5, + step=1, + help="最终推荐的股票数量" + ) + + with col3: + st.info("💡 系统将获取前100名股票,进行整体分析后精选优质标的") + + # 高级选项 + with st.expander("⚙️ 高级筛选参数"): + col1, col2, col3 = st.columns(3) + + with col1: + max_change = st.number_input( + "最大涨跌幅(%)", + min_value=10.0, + max_value=100.0, + value=30.0, + step=5.0, + help="过滤掉涨幅过高的股票,避免追高" + ) + + with col2: + min_cap = st.number_input( + "最小市值(亿)", + min_value=10.0, + max_value=500.0, + value=50.0, + step=10.0 + ) + + with col3: + max_cap = st.number_input( + "最大市值(亿)", + min_value=100.0, + max_value=50000.0, + value=5000.0, + step=100.0 + ) + + # 模型选择 + model = st.selectbox( + "选择AI模型", + ["deepseek-chat", "deepseek-reasoner"], + help="deepseek-chat速度快,deepseek-reasoner推理能力强" + ) + + st.markdown("---") + + # 开始分析按钮 + if st.button("🚀 开始主力选股", type="primary", use_container_width=True): + + with st.spinner("正在获取数据并分析,这可能需要几分钟..."): + + # 创建分析器 + analyzer = MainForceAnalyzer(model=model) + + # 运行分析 + result = analyzer.run_full_analysis( + start_date=start_date, + days_ago=days_ago, + final_n=final_n + ) + + # 保存结果到session_state + st.session_state.main_force_result = result + st.session_state.main_force_analyzer = analyzer + + # 显示结果 + if result['success']: + st.success(f"✅ 分析完成!共筛选出 {len(result['final_recommendations'])} 只优质标的") + st.rerun() + else: + st.error(f"❌ 分析失败: {result.get('error', '未知错误')}") + + # 显示分析结果 + if 'main_force_result' in st.session_state: + result = st.session_state.main_force_result + + if result['success']: + display_analysis_results(result, st.session_state.get('main_force_analyzer')) + +def display_analysis_results(result: dict, analyzer): + """显示分析结果""" + + st.markdown("---") + st.markdown("## 📊 分析结果") + + # 统计信息 + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("获取股票数", result['total_stocks']) + + with col2: + st.metric("筛选后", result['filtered_stocks']) + + with col3: + st.metric("最终推荐", len(result['final_recommendations'])) + + st.markdown("---") + + # 显示AI分析师完整报告 + if analyzer and hasattr(analyzer, 'fund_flow_analysis'): + display_analyst_reports(analyzer) + + st.markdown("---") + + # 显示推荐股票 + if result['final_recommendations']: + st.markdown("### ⭐ 精选推荐") + + for rec in result['final_recommendations']: + with st.expander( + f"【第{rec['rank']}名】{rec['symbol']} - {rec['name']}", + expanded=(rec['rank'] <= 3) + ): + display_recommendation_detail(rec) + + # 显示候选股票列表 + if analyzer and analyzer.raw_stocks is not None and not analyzer.raw_stocks.empty: + st.markdown("---") + st.markdown("### 📋 候选股票列表(筛选后)") + + # 选择关键列显示 + display_cols = ['股票代码', '股票简称'] + + # 添加行业列 + industry_cols = [col for col in analyzer.raw_stocks.columns if '行业' in col] + if industry_cols: + display_cols.append(industry_cols[0]) + + # 添加区间主力资金净流入(智能匹配) + main_fund_col = None + main_fund_patterns = [ + '区间主力资金流向', # 实际列名 + '区间主力资金净流入', + '主力资金流向', + '主力资金净流入', + '主力净流入', + '主力资金' + ] + for pattern in main_fund_patterns: + matching = [col for col in analyzer.raw_stocks.columns if pattern in col] + if matching: + main_fund_col = matching[0] + break + if main_fund_col: + display_cols.append(main_fund_col) + + # 添加区间涨跌幅(前复权)(智能匹配) + interval_pct_col = None + interval_pct_patterns = [ + '区间涨跌幅:前复权', '区间涨跌幅:前复权(%)', '区间涨跌幅(%)', + '区间涨跌幅', '涨跌幅:前复权', '涨跌幅:前复权(%)', '涨跌幅(%)', '涨跌幅' + ] + for pattern in interval_pct_patterns: + matching = [col for col in analyzer.raw_stocks.columns if pattern in col] + if matching: + interval_pct_col = matching[0] + break + if interval_pct_col: + display_cols.append(interval_pct_col) + + # 添加市值、市盈率、市净率 + for col_name in ['总市值', '市盈率', '市净率']: + matching_cols = [col for col in analyzer.raw_stocks.columns if col_name in col] + if matching_cols: + display_cols.append(matching_cols[0]) + + # 选择存在的列 + final_cols = [col for col in display_cols if col in analyzer.raw_stocks.columns] + + # 调试信息:显示找到的列名 + with st.expander("🔍 调试信息 - 查看数据列", expanded=False): + st.caption("所有可用列:") + cols_list = list(analyzer.raw_stocks.columns) + st.write(cols_list) + st.caption(f"\n已选择显示的列: {final_cols}") + if main_fund_col: + st.success(f"✅ 找到主力资金列: {main_fund_col}") + else: + st.warning("⚠️ 未找到主力资金列") + if interval_pct_col: + st.success(f"✅ 找到涨跌幅列: {interval_pct_col}") + else: + st.warning("⚠️ 未找到涨跌幅列") + + # 显示DataFrame + display_df = analyzer.raw_stocks[final_cols].copy() + st.dataframe(display_df, use_container_width=True, height=400) + + # 显示统计 + st.caption(f"共 {len(display_df)} 只候选股票,显示 {len(final_cols)} 个字段") + + # 下载按钮 + csv = display_df.to_csv(index=False, encoding='utf-8-sig') + st.download_button( + label="📥 下载候选列表CSV", + data=csv, + file_name=f"main_force_stocks_{datetime.now().strftime('%Y%m%d')}.csv", + mime="text/csv" + ) + + # 显示PDF报告下载区域 + if analyzer and result: + display_report_download_section(analyzer, result) + +def display_recommendation_detail(rec: dict): + """显示单个推荐股票的详细信息""" + + col1, col2 = st.columns([1, 1]) + + with col1: + st.markdown("#### 📌 推荐理由") + for reason in rec.get('reasons', []): + st.markdown(f"- {reason}") + + st.markdown("#### 💡 投资亮点") + st.info(rec.get('highlights', 'N/A')) + + with col2: + st.markdown("#### 📊 投资建议") + st.markdown(f"**建议仓位**: {rec.get('position', 'N/A')}") + st.markdown(f"**投资周期**: {rec.get('investment_period', 'N/A')}") + + st.markdown("#### ⚠️ 风险提示") + st.warning(rec.get('risks', 'N/A')) + + # 显示股票详细数据 + if 'stock_data' in rec: + st.markdown("---") + st.markdown("#### 📊 股票详细数据") + + stock_data = rec['stock_data'] + + # 创建数据展示 + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("股票代码", stock_data.get('股票代码', 'N/A')) + + # 显示行业 + industry_keys = [k for k in stock_data.keys() if '行业' in k] + if industry_keys: + st.metric("所属行业", stock_data.get(industry_keys[0], 'N/A')) + + with col2: + # 显示主力资金 + fund_keys = [k for k in stock_data.keys() if '主力' in k and '净流入' in k] + if fund_keys: + fund_value = stock_data.get(fund_keys[0], 'N/A') + if isinstance(fund_value, (int, float)): + st.metric("主力资金净流入", f"{fund_value/100000000:.2f}亿") + else: + st.metric("主力资金净流入", str(fund_value)) + + with col3: + # 显示涨跌幅 + change_keys = [k for k in stock_data.keys() if '涨跌幅' in k] + if change_keys: + change_value = stock_data.get(change_keys[0], 'N/A') + if isinstance(change_value, (int, float)): + st.metric("区间涨跌幅", f"{change_value:.2f}%") + else: + st.metric("区间涨跌幅", str(change_value)) + + # 显示其他关键指标 + st.markdown("**其他关键指标:**") + metrics_col1, metrics_col2, metrics_col3 = st.columns(3) + + with metrics_col1: + if '市盈率' in stock_data or any('市盈率' in k for k in stock_data.keys()): + pe_keys = [k for k in stock_data.keys() if '市盈率' in k] + if pe_keys: + st.caption(f"市盈率: {stock_data.get(pe_keys[0], 'N/A')}") + + with metrics_col2: + if '市净率' in stock_data or any('市净率' in k for k in stock_data.keys()): + pb_keys = [k for k in stock_data.keys() if '市净率' in k] + if pb_keys: + st.caption(f"市净率: {stock_data.get(pb_keys[0], 'N/A')}") + + with metrics_col3: + if '总市值' in stock_data or any('总市值' in k for k in stock_data.keys()): + cap_keys = [k for k in stock_data.keys() if '总市值' in k] + if cap_keys: + st.caption(f"总市值: {stock_data.get(cap_keys[0], 'N/A')}") + +def display_analyst_reports(analyzer): + """显示AI分析师完整报告""" + + st.markdown("### 🤖 AI分析师团队完整报告") + + # 创建三个标签页 + tab1, tab2, tab3 = st.tabs(["💰 资金流向分析", "📊 行业板块分析", "📈 财务基本面分析"]) + + with tab1: + st.markdown("#### 💰 资金流向分析师报告") + st.markdown("---") + if hasattr(analyzer, 'fund_flow_analysis') and analyzer.fund_flow_analysis: + st.markdown(analyzer.fund_flow_analysis) + else: + st.info("暂无资金流向分析报告") + + with tab2: + st.markdown("#### 📊 行业板块及市场热点分析师报告") + st.markdown("---") + if hasattr(analyzer, 'industry_analysis') and analyzer.industry_analysis: + st.markdown(analyzer.industry_analysis) + else: + st.info("暂无行业板块分析报告") + + with tab3: + st.markdown("#### 📈 财务基本面分析师报告") + st.markdown("---") + if hasattr(analyzer, 'fundamental_analysis') and analyzer.fundamental_analysis: + st.markdown(analyzer.fundamental_analysis) + else: + st.info("暂无财务基本面分析报告") + +def format_number(value, unit='', suffix=''): + """格式化数字显示""" + if value is None or value == 'N/A': + return 'N/A' + + try: + num = float(value) + + # 如果单位是亿,需要转换 + if unit == '亿': + if abs(num) >= 100000000: # 大于1亿(以元为单位) + num = num / 100000000 + elif abs(num) < 100: # 小于100,可能已经是亿 + pass + else: # 100-100000000之间,可能是万 + num = num / 10000 + + # 格式化显示 + if abs(num) >= 1000: + formatted = f"{num:,.2f}" + elif abs(num) >= 1: + formatted = f"{num:.2f}" + else: + formatted = f"{num:.4f}" + + return f"{formatted}{suffix}" + except (ValueError, TypeError): + return str(value) + diff --git a/pdf_generator.py b/pdf_generator.py index 0bb0ada..346d525 100644 --- a/pdf_generator.py +++ b/pdf_generator.py @@ -249,8 +249,9 @@ def display_pdf_export_section(stock_info, agents_results, discussion_result, fi col1, col2, col3 = st.columns([1, 2, 1]) with col2: - # 生成PDF报告按钮 - if st.button("📄 生成并下载PDF报告", type="primary", use_container_width=True): + # 生成PDF报告按钮(使用股票代码作为key的一部分,确保唯一性) + button_key = f"pdf_btn_{stock_info.get('symbol', 'unknown')}" + if st.button("📄 生成并下载PDF报告", type="primary", use_container_width=True, key=button_key): with st.spinner("正在生成PDF报告..."): try: # 生成PDF内容 diff --git a/stock_data.py b/stock_data.py index 3dd9700..c80e529 100644 --- a/stock_data.py +++ b/stock_data.py @@ -463,20 +463,14 @@ class StockDataFetcher: except Exception as e: print(f"获取财务指标失败: {e}") - # 5. 获取季度业绩(尝试不同API) + # 5. 获取季度业绩(使用问财) try: - # 尝试获取业绩预告 - quarter_data = ak.stock_profit_forecast_em(symbol=symbol) - if quarter_data is not None and not quarter_data.empty: - financial_data["quarter_data"] = quarter_data.head(4).to_dict('records') - except: - try: - # 备用方案:获取季度财报 - quarter_data = ak.stock_financial_report_sina(stock=symbol, symbol="季报") - if quarter_data is not None and not quarter_data.empty: - financial_data["quarter_data"] = quarter_data.head(4).to_dict('records') - except Exception as e: - print(f"获取季度数据失败: {e}") + # 使用pywencai获取季报数据 + quarter_data = self._get_quarter_data_from_wencai(symbol) + if quarter_data: + financial_data["quarter_data"] = quarter_data + except Exception as e: + print(f"获取季度数据失败: {e}") return financial_data @@ -484,6 +478,75 @@ class StockDataFetcher: print(f"获取中国股票财务数据失败: {e}") return financial_data + def _get_quarter_data_from_wencai(self, symbol): + """使用问财获取季报数据 - 直接返回原始数据给AI分析""" + try: + # 构建查询语句 - 获取最近4个季度的数据 + query = f"{symbol}最近4个季度的营业收入、净利润、每股收益、净资产收益率、营业收入同比增长率、净利润同比增长率" + + # 使用pywencai查询 + print(f"正在使用问财获取 {symbol} 的季报数据...") + result = pywencai.get(question=query, perpage=10) + + # 检查返回类型 + if result is None: + print(f"问财未返回 {symbol} 的季报数据") + return None + + # 处理不同返回类型 + quarter_data = None + + # 如果返回的是DataFrame + if isinstance(result, pd.DataFrame): + if result.empty: + print(f"问财返回的DataFrame为空") + return None + + print(f" 问财返回DataFrame,共 {len(result)} 行数据") + print(f" 列名: {list(result.columns)}") + + # 将DataFrame转为字典列表,保留所有原始数据 + quarter_data = { + 'data_type': 'dataframe', + 'columns': list(result.columns), + 'records': result.head(10).to_dict('records'), # 最多取10条 + 'row_count': len(result), + 'summary': f"获取到{len(result)}条季报相关数据" + } + + # 如果返回的是字典 + elif isinstance(result, dict): + print(f" 问财返回字典数据") + print(f" 字典键: {list(result.keys())}") + + quarter_data = { + 'data_type': 'dict', + 'raw_data': result, + 'summary': f"获取到季报字典数据,包含 {len(result)} 个字段" + } + + # 其他类型 + else: + print(f" 问财返回未知类型: {type(result)}") + quarter_data = { + 'data_type': 'unknown', + 'raw_data': str(result), + 'summary': f"获取到季报数据,类型: {type(result).__name__}" + } + + if quarter_data: + print(f"✅ 成功获取季报数据,将原始数据交由AI分析师处理") + return quarter_data + else: + print(f"⚠️ 未能获取有效的季报数据") + return None + + except Exception as e: + print(f"使用问财获取季报数据失败: {e}") + import traceback + traceback.print_exc() + return None + def _get_us_financial_data(self, symbol): """获取美股财务数据""" financial_data = { diff --git a/test_main_force.py b/test_main_force.py new file mode 100644 index 0000000..6168c59 --- /dev/null +++ b/test_main_force.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +主力选股功能测试脚本 +快速验证功能是否正常工作 +""" + +import sys +from datetime import datetime, timedelta + +def test_imports(): + """测试模块导入""" + print("="*60) + print("测试1: 检查模块导入") + print("="*60) + + try: + print("导入 pywencai...", end=" ") + import pywencai + print("✅") + except Exception as e: + print(f"❌ {e}") + return False + + try: + print("导入 main_force_selector...", end=" ") + from main_force_selector import main_force_selector + print("✅") + except Exception as e: + print(f"❌ {e}") + return False + + try: + print("导入 main_force_analysis...", end=" ") + from main_force_analysis import MainForceAnalyzer + print("✅") + except Exception as e: + print(f"❌ {e}") + return False + + try: + print("导入 main_force_ui...", end=" ") + from main_force_ui import display_main_force_selector + print("✅") + except Exception as e: + print(f"❌ {e}") + return False + + print("\n✅ 所有模块导入成功!\n") + return True + +def test_data_fetch(): + """测试数据获取""" + print("="*60) + print("测试2: 测试数据获取功能") + print("="*60) + + try: + from main_force_selector import main_force_selector + + # 使用较短的时间范围进行测试 + print("\n尝试获取最近30天的主力资金数据...") + success, data, message = main_force_selector.get_main_force_stocks(days_ago=30) + + if success: + print(f"\n✅ 数据获取成功!") + print(f" 获取到 {len(data)} 只股票") + print(f"\n前5只股票:") + print(data.head(5) if len(data) > 0 else "无数据") + return True + else: + print(f"\n❌ 数据获取失败: {message}") + print("\n可能原因:") + print(" 1. 网络连接问题") + print(" 2. pywencai服务暂时不可用") + print(" 3. 需要安装Node.js >= 16.0") + print("\n请检查:") + print(" - 网络连接是否正常") + print(" - Node.js版本: node --version") + print(" - pywencai是否正确安装: pip list | findstr pywencai") + return False + + except Exception as e: + print(f"\n❌ 测试过程出错: {e}") + import traceback + traceback.print_exc() + return False + +def test_filter(): + """测试筛选功能""" + print("\n" + "="*60) + print("测试3: 测试筛选功能") + print("="*60) + + try: + from main_force_selector import main_force_selector + import pandas as pd + + # 创建测试数据 + test_data = pd.DataFrame({ + '股票代码': ['000001', '000002', '600519', '300750'], + '股票简称': ['平安银行', '万科A', '贵州茅台', '宁德时代'], + '区间涨跌幅': [15.5, 35.8, 12.3, 28.9], + '总市值': [3000, 2500, 25000, 9000], + '主力资金净流入': [50000000, 80000000, 120000000, 95000000] + }) + + print("\n原始测试数据:") + print(test_data) + + print("\n应用筛选条件:") + print(" - 区间涨跌幅 < 30%") + print(" - 市值 50-1300亿") + + filtered_data = main_force_selector.filter_stocks( + test_data, + max_range_change=30.0, + min_market_cap=50.0, + max_market_cap=1300.0 + ) + + print("\n筛选后数据:") + print(filtered_data) + + print("\n✅ 筛选功能正常!") + return True + + except Exception as e: + print(f"\n❌ 筛选测试失败: {e}") + import traceback + traceback.print_exc() + return False + +def test_ai_analysis(): + """测试AI分析(需要API配置)""" + print("\n" + "="*60) + print("测试4: 测试AI分析功能") + print("="*60) + + try: + import os + from dotenv import load_dotenv + + load_dotenv() + + api_key = os.getenv('DEEPSEEK_API_KEY') + if not api_key: + print("\n⚠️ 未配置DEEPSEEK_API_KEY,跳过AI分析测试") + print(" 请在.env文件中配置API密钥后再测试AI功能") + return None + + print("\n✅ API密钥已配置") + print(" 如需测试完整AI分析,请运行主程序") + return True + + except Exception as e: + print(f"\n⚠️ {e}") + return None + +def main(): + """主测试函数""" + print("\n" + "="*80) + print(" "*20 + "主力选股功能测试") + print("="*80 + "\n") + + results = [] + + # 测试1: 模块导入 + result1 = test_imports() + results.append(("模块导入", result1)) + + if not result1: + print("\n❌ 模块导入失败,请先安装依赖:") + print(" pip install pywencai pandas streamlit") + return + + # 测试2: 数据获取 + result2 = test_data_fetch() + results.append(("数据获取", result2)) + + # 测试3: 筛选功能 + result3 = test_filter() + results.append(("数据筛选", result3)) + + # 测试4: AI分析 + result4 = test_ai_analysis() + if result4 is not None: + results.append(("AI分析", result4)) + + # 总结 + print("\n" + "="*80) + print(" "*30 + "测试总结") + print("="*80 + "\n") + + for test_name, result in results: + status = "✅ 通过" if result else "❌ 失败" + print(f"{test_name:<15} {status}") + + passed = sum(1 for _, r in results if r) + total = len(results) + + print(f"\n总计: {passed}/{total} 项测试通过") + + if passed == total: + print("\n🎉 恭喜!所有测试通过,主力选股功能可以正常使用!") + print("\n下一步:") + print(" 1. 运行主程序: streamlit run app.py") + print(" 2. 点击侧边栏的 '🎯 主力选股' 按钮") + print(" 3. 设置参数并开始分析") + else: + print("\n⚠️ 部分测试未通过,请根据上述错误信息进行排查") + print("\n常见问题:") + print(" 1. 数据获取失败 → 检查网络和Node.js版本") + print(" 2. 模块导入失败 → 检查依赖安装") + print(" 3. API测试失败 → 检查.env配置") + + print("\n" + "="*80 + "\n") + +if __name__ == "__main__": + main() + diff --git a/主力选股使用指南.md b/主力选股使用指南.md new file mode 100644 index 0000000..fd3c30b --- /dev/null +++ b/主力选股使用指南.md @@ -0,0 +1,313 @@ +# 主力选股功能 - 快速使用指南 + +## ✨ 核心特点 + +**整体批量分析,不是逐个分析!** + +传统方式:获取100只股票 → 逐个分析(慢,费时) +本系统:获取100只股票 → **整体分析** → 精选优质标的(快速高效) + +## 🔄 工作流程 + +``` +1️⃣ 数据获取 + └─ 使用pywencai获取主力资金净流入前100名 + └─ 包含:资金、涨跌幅、行业、市值、财务等完整数据 + +2️⃣ 智能筛选 + └─ 涨幅过滤(<30%,避免追高) + └─ 市值筛选(50-1300亿) + └─ ST股过滤 + +3️⃣ 整体分析(核心创新!) + ├─ 💰 资金流向分析师 + │ └─ 分析整体资金流向特征 + │ └─ 识别板块资金集中度 + │ └─ 发现资金流入但涨幅不高的机会 + │ + ├─ 📊 行业板块分析师 + │ └─ 识别当前热点板块 + │ └─ 分析板块轮动态势 + │ └─ 评估各行业发展前景 + │ + └─ 📈 财务基本面分析师 + └─ 评估整体财务质量 + └─ 筛选估值合理的标的 + └─ 关注成长性突出的股票 + +4️⃣ 综合决策 + └─ 资深研究员AI综合三位分析师意见 + └─ 从候选股票中精选3-5只最优标的 + └─ 给出详细推荐理由和风险提示 +``` + +## 🚀 快速开始 + +### 步骤1:进入功能 +侧边栏点击 **🎯 主力选股** + +### 步骤2:设置参数 +- **时间区间**:最近3个月(默认) +- **精选数量**:5只(默认) +- **AI模型**:deepseek-chat(快速) + +### 步骤3:开始分析 +点击 **🚀 开始主力选股** + +### 预期时间 +- 数据获取:30-60秒 +- 整体分析:1-2分钟 +- 总耗时:约2-3分钟(比逐个分析快10倍!) + +## 📊 分析逻辑 + +### 为什么是"整体分析"? + +**传统逐个分析的问题**: +- ❌ 耗时长(100只股票 × 15秒 = 25分钟) +- ❌ 成本高(大量API调用) +- ❌ 缺乏全局视角 + +**整体分析的优势**: +- ✅ 快速高效(2-3分钟完成) +- ✅ 成本低(3次AI调用) +- ✅ 宏观视角(板块热点、资金流向) +- ✅ 更符合实际选股逻辑 + +### AI分析师如何工作? + +#### 💰 资金流向分析师 +``` +看什么: +- 哪些板块资金流入最多? +- 主力资金的整体行为特征 +- 资金流入但涨幅不高的潜力股 + +输出: +- 资金集中的热点板块 +- 值得关注的3-5只股票 +``` + +#### 📊 行业板块分析师 +``` +看什么: +- 当前市场热点板块 +- 板块轮动和趋势 +- 行业发展前景 + +输出: +- 热点行业识别 +- 板块机会评估 +- 推荐的优质板块标的 +``` + +#### 📈 财务基本面分析师 +``` +看什么: +- 整体财务质量分布 +- 估值水平合理性 +- 成长性突出的股票 + +输出: +- 基本面优秀的股票 +- 估值合理的标的 +- 财务风险提示 +``` + +## 🎯 筛选标准 + +### 数据层面 +- ✅ 主力资金净流入前100名 +- ✅ 区间涨跌幅 < 30%(避免追高) +- ✅ 市值 50-1300亿(优质中盘) +- ✅ 排除ST股 + +### 分析层面 +- ✅ 资金面:主力看好,持续流入 +- ✅ 行业面:热点板块,前景明朗 +- ✅ 基本面:财务健康,成长性好 +- ✅ 综合性:三方面都不错的均衡标的 + +## 📋 结果展示 + +### 精选推荐(3-5只) +每只股票包含: +- **推荐理由**(3-5条,综合三位分析师观点) +- **投资亮点**(突出优势) +- **风险提示**(需要注意的风险) +- **建议仓位**(如20-30%) +- **投资周期**(短期/中期/长期) +- **详细数据**(主力资金、涨跌幅、行业等) + +### 候选列表(筛选后所有股票) +- 完整的数据表格 +- 可下载CSV格式 +- 方便进一步研究 + +## 💡 使用建议 + +### 最佳使用时机 +``` +周末(推荐) + └─ 为下周交易做准备 + └─ 时间充裕,深入研究 + +月初 + └─ 调整持仓结构 + └─ 发现新的机会 + +热点出现时 + └─ 快速捕捉市场机会 + └─ 参数设置:最近1个月 +``` + +### 参数配置建议 +``` +保守型: + 时间:6个月 + 涨幅:<20% + 精选:3只 + +平衡型(推荐): + 时间:3个月 + 涨幅:<30% + 精选:5只 + +激进型: + 时间:1个月 + 涨幅:<40% + 精选:8只 +``` + +### 后续操作 +``` +1. 查看精选推荐 + ↓ +2. 阅读AI分析报告 + ↓ +3. 查看候选列表完整数据 + ↓ +4. 选择2-3只深入研究 + ↓ +5. 查看K线和技术指标 + ↓ +6. 周一择机建仓 + ↓ +7. 加入实时监测 +``` + +## ⚠️ 注意事项 + +### 环境要求 +```bash +Node.js >= 16.0 # 重要!pywencai依赖 +pywencai >= 0.7.0 +DEEPSEEK_API_KEY # 在.env中配置 +``` + +### 数据时效 +- 数据有1-2天延迟 +- 适合中短期投资 +- 不适合日内交易 + +### 市场环境 +- **牛市**:效果最佳,资金推动明显 +- **震荡**:关注短期机会 +- **熊市**:谨慎使用,关注防御性板块 + +## 🆚 对比说明 + +### 主力选股 vs 单个分析 + +| 特性 | 主力选股 | 单个分析 | +|-----|---------|---------| +| 分析对象 | 100只股票整体 | 1只股票 | +| 分析视角 | 宏观(板块、热点) | 微观(个股) | +| 耗时 | 2-3分钟 | 10-15秒/只 | +| 适用场景 | 选股、发现机会 | 研究特定股票 | +| 输出 | 3-5只精选标的 | 1只详细分析 | + +### 何时使用? + +**使用主力选股**: +- ✅ 不知道买什么股票 +- ✅ 想发现市场热点 +- ✅ 需要批量筛选 +- ✅ 寻找新的投资机会 + +**使用单个分析**: +- ✅ 已有明确目标股票 +- ✅ 需要深入研究 +- ✅ 验证投资想法 +- ✅ 查看详细技术面 + +## ❓ 常见问题 + +### Q: 为什么只精选3-5只? +A: 精选的目的是找出最优标的,太多会降低质量。3-5只足够分散风险,又便于深入研究。 + +### Q: 推荐结果每次一样吗? +A: 不一样。数据每天更新,AI分析也会考虑最新市场环境。 + +### Q: 如何判断推荐质量? +A: 看推荐理由是否具体、有数据支撑、逻辑连贯。建议结合自己判断,不要盲目跟从。 + +### Q: 可以每天用吗? +A: 可以,但建议每周1-2次。数据有延迟,频繁使用意义不大。 + +## 📈 实战示例 + +### 场景:周末选股 +``` +1. 周日晚上运行主力选股 + 参数:最近3个月,精选5只 + +2. 查看结果 + - 精选推荐:5只 + - AI分析:资金、行业、基本面 + +3. 深入研究 + - 选择前3只 + - 查看K线图 + - 了解公司业务 + +4. 制定策略 + - 周一开盘价 + - 买入价位 + - 止损止盈 + +5. 周一执行 + - 分批建仓 + - 加入监测 +``` + +## 🎓 学习建议 + +1. **先了解逻辑** + - 理解整体分析的优势 + - 明白三位分析师的作用 + +2. **实践使用** + - 先用默认参数试试 + - 查看完整结果 + +3. **调整优化** + - 根据市场环境调整参数 + - 找到适合自己的配置 + +4. **结合其他功能** + - 单个分析:深入研究精选标的 + - 实时监测:跟踪股价变化 + - 批量分析:对比分析 + +--- + +**开始使用**: +```bash +streamlit run app.py +→ 点击 🎯 主力选股 +→ 开始分析 +``` + +**祝您投资顺利!** 🚀📈💰 + diff --git a/主力选股功能说明.md b/主力选股功能说明.md new file mode 100644 index 0000000..221f409 --- /dev/null +++ b/主力选股功能说明.md @@ -0,0 +1,435 @@ +# 主力选股功能说明 + +## 功能概述 + +**主力选股**是一个智能选股系统,通过分析主力资金流向,结合AI多智能体分析,自动筛选出近期拉升概率高、主力资金净流入较多、综合素质优秀的优质投资标的。 + +## 核心特点 + +### 1. 数据来源权威 +- 使用 `pywencai` 获取同花顺数据 +- 主力资金流向真实可靠 +- 包含完整的财务评分和行业信息 + +### 2. 智能筛选机制 +- **主力资金排名**:优先选择主力资金净流入前100名 +- **涨幅过滤**:排除涨幅过高的股票,避免追高 +- **市值筛选**:聚焦50-1300亿市值区间的优质标的 +- **基本面保护**:自动过滤ST股、科创板(可配置) + +### 3. AI多维度分析 +整合三大分析师团队: +- **💰 资金流向分析师**:分析主力资金行为和市场情绪 +- **📊 行业板块分析师**:评估行业前景和市场热点 +- **📈 财务基本面分析师**:考察盈利能力和财务健康度 + +### 4. 资深研究员决策 +- AI研究员综合所有分析结果 +- 从候选股票中精选最优标的 +- 提供详细的推荐理由和风险提示 + +## 工作流程 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 1. 数据获取 │ +│ 使用pywencai获取主力资金净流入前100名股票 │ +│ 包含:资金流向、涨跌幅、市值、财务指标、行业等 │ +└──────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────▼──────────────────────────────────────┐ +│ 2. 智能筛选 │ +│ ✓ 区间涨跌幅 < 30%(避免追高) │ +│ ✓ 市值 50-1300亿(优质中盘股) │ +│ ✓ 排除ST股和科创板 │ +│ ✓ 按主力资金排序,取前20名 │ +└──────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────▼──────────────────────────────────────┐ +│ 3. AI深度分析 │ +│ 对每只候选股票进行三维度分析: │ +│ • 资金流向(主力行为、资金结构) │ +│ • 行业板块(行业地位、市场热度) │ +│ • 财务基本面(盈利能力、成长性) │ +└──────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────▼──────────────────────────────────────┐ +│ 4. 综合决策 │ +│ 资深研究员AI综合评估: │ +│ • 对比所有候选股票 │ +│ • 精选3-5只最优标的 │ +│ • 提供推荐理由、投资亮点、风险提示 │ +└─────────────────────────────────────────────────────────┘ +``` + +## 使用方法 + +### 步骤1:进入主力选股界面 + +在系统侧边栏点击 **🎯 主力选股** 按钮 + +### 步骤2:设置参数 + +#### 基础参数 +1. **时间区间**:选择数据统计的时间范围 + - 最近3个月(默认) + - 最近6个月 + - 最近1年 + - 自定义日期 + +2. **初步筛选数量**:从符合条件的股票中选取前N名(默认20只) + - 范围:10-50只 + - 建议:20-30只 + +3. **最终精选数量**:最终推荐的股票数量(默认5只) + - 范围:3-10只 + - 建议:3-5只 + +#### 高级参数(可选) +4. **最大涨跌幅**:过滤涨幅过高的股票(默认30%) + - 作用:避免追高 + - 建议:20%-40% + +5. **市值范围**: + - 最小市值:默认50亿 + - 最大市值:默认1300亿 + - 作用:聚焦优质中盘股 + +6. **AI模型**:选择分析使用的AI模型 + - `deepseek-chat`:速度快,适合快速筛选 + - `deepseek-reasoner`:推理能力强,分析更深入 + +### 步骤3:开始分析 + +点击 **🚀 开始主力选股** 按钮,系统将自动: +1. 获取数据(约30-60秒) +2. 智能筛选(约10秒) +3. AI深度分析(约2-5分钟,取决于股票数量) +4. 综合决策(约30秒) + +### 步骤4:查看结果 + +#### 统计概览 +显示四个关键指标: +- **获取股票数**:原始数据数量(通常100只) +- **筛选后**:符合筛选条件的数量 +- **深度分析**:成功完成AI分析的数量 +- **最终推荐**:精选的优质标的数量 + +#### 精选推荐 +对每只推荐股票,系统提供: + +1. **📌 推荐理由**(3-5条核心理由) + - 为什么推荐这只股票 + - 关键优势是什么 + +2. **💡 投资亮点** + - 突出的投资价值 + - 主要看点 + +3. **⚠️ 风险提示** + - 需要关注的风险 + - 注意事项 + +4. **📊 投资建议** + - 建议仓位:如20-30% + - 目标价位:预期目标价 + - 投资周期:短期/中期/长期 + +5. **🤖 AI分析师详细报告** + - 资金流向分析 + - 行业板块分析 + - 财务基本面分析 + +#### 候选股票列表 +显示所有候选股票的详细数据: +- 股票代码、名称 +- 所属行业 +- 市值 +- 主力资金净流入 +- 区间涨跌幅 +- 市盈率、市净率 + +支持下载CSV格式数据 + +## 筛选逻辑详解 + +### 主力资金标准 +- **主力资金净流入排名**:选择资金流入最多的股票 +- **资金持续性**:优先考虑持续流入的股票 +- **资金占比**:主力资金占比高的更受关注 + +### 涨幅控制 +``` +✅ 涨幅适中(<30%): + - 还有上涨空间 + - 风险相对可控 + +❌ 涨幅过高(>30%): + - 可能面临回调压力 + - 追高风险较大 + - 自动过滤 +``` + +### 市值要求 +``` +市值范围:50-1300亿 + +原因: +✓ <50亿:流动性差,风险高 +✓ 50-1300亿:优质中盘股,兼顾成长性和稳定性 +✓ >1300亿:大盘股,成长空间有限 +``` + +### 基本面保护 +- 自动排除ST股票(特别处理) +- 可配置排除科创板(波动较大) +- 要求财务数据完整 + +## AI分析维度 + +### 1. 资金流向分析 +**分析内容**: +- 主力资金流向趋势 +- 大单、中单、小单分布 +- 机构与散户资金博弈 +- 资金推动与股价配合度 +- 换手率与成交活跃度 + +**评判标准**: +- ✅ 主力持续流入 + 股价上涨 = 强势信号 +- ⚠️ 主力流出 + 股价上涨 = 警惕接盘 +- 💡 主力流入 + 股价下跌 = 可能低吸 +- ❌ 主力流出 + 股价下跌 = 弱势信号 + +### 2. 行业板块分析 +**分析内容**: +- 所属行业发展前景 +- 行业在市场中的地位 +- 板块轮动和市场热度 +- 政策支持和行业催化 +- 行业竞争格局 + +**关注重点**: +- 行业是否处于上升周期 +- 是否有政策利好支持 +- 市场关注度和热度 +- 行业龙头还是跟随者 + +### 3. 财务基本面分析 +**分析内容**: +- 盈利能力(净利润、ROE等) +- 成长能力(营收增长、利润增长) +- 营运能力(周转率等) +- 偿债能力(资产负债率) +- 现金流状况 +- 资产质量 + +**评分体系**: +系统获取8大评分: +1. 盈利能力评分 +2. 成长能力评分 +3. 营运能力评分 +4. 偿债能力评分 +5. 现金流评分 +6. 资产质量评分 +7. 流动性评分 +8. 资本充足性评分 + +## 使用建议 + +### 适用场景 +1. **中短期波段交易** + - 捕捉主力资金推动的行情 + - 关注3个月内的主力行为 + +2. **行业轮动投资** + - 发现市场热点板块 + - 把握资金流向切换 + +3. **价值成长投资** + - 寻找被主力关注的潜力股 + - 结合基本面和资金面 + +### 使用频率 +- **建议**:每周执行1-2次 +- **最佳时机**:周末或交易日收盘后 +- **避免**:开盘前或盘中(数据可能不完整) + +### 注意事项 + +#### 1. 数据时效性 +- 数据有一定延迟(通常1-2天) +- 适合中短期布局,不适合日内交易 +- 建议结合实时行情确认 + +#### 2. 市场环境 +- **牛市**:主力选股效果更佳,资金推动明显 +- **震荡市**:关注短期波段机会 +- **熊市**:谨慎使用,关注防御性板块 + +#### 3. 仓位管理 +- 不要孤注一掷,建议分散投资 +- 单只股票仓位不超过30% +- 保留一定现金仓位 + +#### 4. 风险控制 +- 设置止损位(建议-8%到-10%) +- 设置止盈位(建议+15%到+20%) +- 不追高,不贪婪 + +## 常见问题 + +### Q1: 为什么获取数据失败? +**可能原因**: +1. 网络连接问题 +2. pywencai服务暂时不可用 +3. 查询语句触发限制 + +**解决方法**: +- 检查网络连接 +- 等待片刻后重试 +- 调整时间区间参数 + +### Q2: 为什么筛选后股票很少? +**可能原因**: +1. 筛选条件过于严格 +2. 当前市场符合条件的股票较少 + +**解决方法**: +- 适当放宽涨跌幅限制(如改为40%) +- 扩大市值范围 +- 调整时间区间 + +### Q3: AI分析需要多长时间? +**时间估算**: +- 单只股票:约10-15秒 +- 20只股票:约3-5分钟 +- 50只股票:约8-10分钟 + +**影响因素**: +- 股票数量 +- AI模型选择 +- 网络速度 + +### Q4: 推荐结果每次都一样吗? +**答案**:不完全一样 + +**原因**: +1. 数据每天更新(主力资金、价格等) +2. AI分析会考虑最新市场环境 +3. 不同时间段的数据可能不同 + +### Q5: 如何判断推荐质量? +**评判标准**: +1. **理由充分性**:推荐理由是否具体、有说服力 +2. **数据支撑**:是否有真实数据支撑 +3. **风险提示**:是否明确指出风险 +4. **逻辑连贯性**:资金、行业、基本面是否相互印证 + +**建议**: +- 认真阅读AI分析报告 +- 对比多个候选股票 +- 结合自己的投资逻辑 +- 不盲目跟从,独立思考 + +## 技术要求 + +### 环境依赖 +```bash +# 必须安装 +pywencai>=0.7.0 +pandas +streamlit + +# Node.js要求 +Node.js >= 16.0(pywencai依赖) +``` + +### 安装方法 +```bash +# 安装Python依赖 +pip install pywencai pandas streamlit + +# 检查Node.js版本 +node --version + +# 如果版本低于16.0,需要升级Node.js +``` + +## 最佳实践 + +### 1. 每周选股流程 +``` +周日晚上 → 运行主力选股 + ↓ + 查看推荐结果 + ↓ + 深入研究前3名 + ↓ + 周一开盘前制定策略 + ↓ + 择机建仓 +``` + +### 2. 结合技术分析 +推荐股票后,建议再进行: +- K线形态分析 +- 支撑压力位判断 +- 均线系统确认 +- 成交量变化观察 + +### 3. 分批建仓 +``` +发现优质标的后: +第1批:20% → 试探性建仓 +第2批:30% → 确认后加仓 +第3批:30% → 突破后追加 +保留:20% → 备用资金 +``` + +### 4. 持仓管理 +``` +持有期间关注: +✓ 主力资金是否继续流入 +✓ 股价是否沿着上升趋势 +✓ 行业是否仍有催化 +✓ 基本面是否发生变化 + +出现以下情况考虑减仓: +✗ 主力资金持续流出 +✗ 技术面破位 +✗ 基本面恶化 +✗ 达到目标价位 +``` + +## 更新日志 + +### v1.0.0 (2025-10-09) +- ✅ 初始版本发布 +- ✅ 支持主力资金数据获取 +- ✅ 实现智能筛选算法 +- ✅ 集成AI三大分析师 +- ✅ 资深研究员综合决策 +- ✅ Web界面集成 + +## 技术支持 + +如遇问题,请提供: +1. 错误截图或错误信息 +2. 使用的参数配置 +3. 操作步骤描述 + +## 免责声明 + +**重要提示**: +1. 本功能仅供学习和研究使用 +2. 不构成任何投资建议 +3. 股市有风险,投资需谨慎 +4. 请根据自身情况做出投资决策 +5. 盈亏自负,谨慎操作 + +--- + +**祝您投资顺利!** 🚀📈 + diff --git a/主力选股快速开始.md b/主力选股快速开始.md new file mode 100644 index 0000000..25a92ac --- /dev/null +++ b/主力选股快速开始.md @@ -0,0 +1,316 @@ +# 主力选股功能 - 快速开始指南 + +## ✅ 已完成的功能模块 + +### 核心模块 +1. **main_force_selector.py** - 数据获取和筛选 + - 使用pywencai获取主力资金数据 + - 智能筛选算法 + - 数据格式化 + +2. **main_force_analysis.py** - AI分析整合 + - 整合三大分析师团队 + - 资深研究员综合决策 + - 完整的分析流程 + +3. **main_force_ui.py** - Web界面 + - 参数配置界面 + - 结果展示 + - 数据导出 + +4. **app.py** - 主应用集成 + - 添加导航按钮 + - 路由配置 + +### 文档 +5. **主力选股功能说明.md** - 详细使用手册 +6. **test_main_force.py** - 功能测试脚本 + +## 🚀 快速开始 + +### 步骤1: 测试功能 +```bash +# 运行测试脚本 +python test_main_force.py +``` + +测试内容: +- ✅ 模块导入检查 +- ✅ 数据获取功能 +- ✅ 筛选功能 +- ✅ API配置检查 + +### 步骤2: 启动系统 +```bash +streamlit run app.py +``` + +### 步骤3: 使用主力选股 +1. 在侧边栏点击 **🎯 主力选股** +2. 设置参数(时间区间、筛选数量等) +3. 点击 **🚀 开始主力选股** +4. 等待3-5分钟完成分析 +5. 查看精选推荐结果 + +## 📊 功能特点 + +### 智能筛选 +``` +原始数据(100只) + ↓ 涨幅过滤(<30%) + ↓ 市值筛选(50-1300亿) + ↓ ST股过滤 +筛选结果(20-50只) + ↓ 主力资金排序 +候选股票(前20名) +``` + +### AI三维分析 +``` +💰 资金流向分析师 + ├─ 主力资金行为 + ├─ 资金流向趋势 + └─ 市场情绪判断 + +📊 行业板块分析师 + ├─ 行业发展前景 + ├─ 市场热点判断 + └─ 板块轮动分析 + +📈 财务基本面分析师 + ├─ 盈利能力评估 + ├─ 成长性分析 + └─ 财务健康度 +``` + +### 综合决策 +``` +资深研究员AI + ├─ 综合评估所有候选 + ├─ 精选3-5只最优标的 + ├─ 提供推荐理由 + ├─ 投资建议和风险提示 + └─ 目标价位和持仓建议 +``` + +## 📋 参数说明 + +### 基础参数 +| 参数 | 默认值 | 范围 | 说明 | +|-----|--------|------|------| +| 时间区间 | 最近3个月 | 自定义 | 主力资金统计周期 | +| 初步筛选 | 20只 | 10-50 | 候选股票数量 | +| 最终精选 | 5只 | 3-10 | 推荐标的数量 | + +### 高级参数 +| 参数 | 默认值 | 范围 | 说明 | +|-----|--------|------|------| +| 最大涨幅 | 30% | 10-100% | 过滤涨幅过高的股票 | +| 最小市值 | 50亿 | 10-500亿 | 市值下限 | +| 最大市值 | 1300亿 | 100-5000亿 | 市值上限 | +| AI模型 | deepseek-chat | - | 分析使用的模型 | + +## 🎯 使用场景 + +### 1. 周末选股 +**时机**: 每周日晚上 +**参数**: 最近3个月,精选5只 +**用途**: 为下周交易做准备 + +### 2. 月度调仓 +**时机**: 每月初 +**参数**: 最近6个月,精选8-10只 +**用途**: 调整持仓结构 + +### 3. 热点捕捉 +**时机**: 市场出现明显热点时 +**参数**: 最近1个月,精选3只 +**用途**: 抓住短期机会 + +## ⚠️ 注意事项 + +### 环境要求 +```bash +# 必须安装 +Python >= 3.8 +Node.js >= 16.0(重要!pywencai依赖) + +# Python包 +pywencai >= 0.7.0 +pandas +streamlit +deepseek +``` + +### 检查Node.js版本 +```bash +node --version + +# 如果低于16.0,需要升级 +# Windows: 从nodejs.org下载最新版本 +# Mac: brew install node +# Linux: nvm install 18 +``` + +### API配置 +确保.env文件中配置了: +```env +DEEPSEEK_API_KEY=your_api_key_here +EMAIL_ENABLED=true # 如需邮件通知 +``` + +## 🔍 常见问题 + +### Q: 数据获取失败? +**检查**: +- [ ] 网络连接正常 +- [ ] Node.js版本 >= 16.0 +- [ ] pywencai已正确安装 + +**解决**: +```bash +# 重新安装pywencai +pip uninstall pywencai +pip install pywencai --upgrade + +# 检查Node.js +node --version +``` + +### Q: 分析时间太长? +**原因**: 候选股票太多 + +**优化**: +- 减少初步筛选数量(改为15只) +- 使用deepseek-chat模型(更快) +- 调整筛选条件减少候选数量 + +### Q: 推荐结果不理想? +**建议**: +- 调整时间区间(试试最近6个月) +- 放宽涨幅限制(改为40%) +- 多次运行对比结果 +- 结合自己的投资逻辑 + +## 📈 最佳实践 + +### 1. 参数配置建议 +```yaml +保守型: + 时间区间: 6个月 + 最大涨幅: 20% + 市值范围: 100-1300亿 + 精选数量: 3只 + +平衡型: + 时间区间: 3个月 + 最大涨幅: 30% + 市值范围: 50-1300亿 + 精选数量: 5只 + +激进型: + 时间区间: 1个月 + 最大涨幅: 40% + 市值范围: 50-800亿 + 精选数量: 8只 +``` + +### 2. 使用流程 +``` +1. 周末运行主力选股 + ↓ +2. 查看推荐结果和AI分析 + ↓ +3. 深入研究前3名标的 + ↓ +4. 查看实时K线和技术指标 + ↓ +5. 周一开盘后择机建仓 + ↓ +6. 加入实时监测,设置止盈止损 +``` + +### 3. 风险控制 +``` +✅ 分散投资: 不超过3-5只股票 +✅ 仓位控制: 单只不超过30% +✅ 止损设置: -8%到-10% +✅ 止盈目标: +15%到+20% +✅ 动态调整: 根据走势调整策略 +``` + +## 📊 输出结果 + +### 统计数据 +- 获取股票总数 +- 筛选后数量 +- 深度分析数量 +- 最终推荐数量 + +### 推荐详情 +每只股票包含: +- ✅ 推荐理由(3-5条) +- ✅ 投资亮点 +- ✅ 风险提示 +- ✅ 建议仓位 +- ✅ 目标价位 +- ✅ 投资周期 +- ✅ AI详细分析报告 + +### 数据导出 +- CSV格式候选列表 +- 包含所有关键指标 +- 可用于进一步分析 + +## 🎓 学习资源 + +### 相关文档 +- 主力选股功能说明.md - 完整使用手册 +- 资金流向功能说明.md - 资金分析原理 +- 实时监测优化说明.md - 监测功能 +- README.md - 系统总体说明 + +### 代码学习 +- main_force_selector.py - 数据处理逻辑 +- main_force_analysis.py - AI分析流程 +- main_force_ui.py - 界面实现 + +## 🆘 获取帮助 + +### 运行测试 +```bash +python test_main_force.py +``` + +### 查看日志 +运行时终端会显示详细日志: +- 数据获取过程 +- 筛选统计 +- AI分析进度 +- 错误信息 + +### 问题反馈 +提供以下信息: +1. 错误截图 +2. 终端日志 +3. 使用的参数 +4. 系统环境(Python版本、Node.js版本) + +## ✨ 开始使用 + +```bash +# 1. 测试功能 +python test_main_force.py + +# 2. 启动系统 +streamlit run app.py + +# 3. 点击 🎯 主力选股 + +# 4. 开始分析 +``` + +--- + +**祝您选股顺利,投资成功!** 🚀📈💰 +