增强风险管理师分析功能

This commit is contained in:
oficcejo
2025-10-19 09:43:18 +08:00
parent 77f30827fb
commit aab874d326
9 changed files with 1425 additions and 47 deletions
+1 -9
View File
@@ -21,14 +21,6 @@ DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
# 获取地址:https://tushare.pro/register
TUSHARE_TOKEN=
# ========== AI服务商配置 ==========
AI_PROVIDER="siliconflow"
# ========== 硅基流动 API配置 ==========
SILICONFLOW_API_KEY="YOUR_API_KEY_FROM_CLOUD_SILICONFLOW_CN"
SILICONFLOW_BASE_URL="https://api.siliconflow.cn/v1"
SILICONFLOW_MODEL="Qwen/Qwen2.5-72B-Instruct"
# ========== MiniQMT量化交易配置(可选)==========
# 是否启用MiniQMT量化交易接口
@@ -58,7 +50,7 @@ SMTP_SERVER=smtp.163.com
# SMTP服务器端口
# 常用端口:
# - 465:SSL加密连接(推荐)
# - 465SSL加密连接163邮箱常用(推荐)
# - 587TLS加密连接
SMTP_PORT=465
+113 -19
View File
@@ -73,13 +73,34 @@ class StockAnalysisAgents:
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
}
def risk_management_agent(self, stock_info: Dict, indicators: Dict) -> Dict[str, Any]:
"""风险管理智能体"""
def risk_management_agent(self, stock_info: Dict, indicators: Dict, risk_data: Dict = None) -> Dict[str, Any]:
"""风险管理智能体(增强版)"""
print("⚠️ 风险管理师正在评估中...")
# 如果有风险数据,显示数据来源
if risk_data and risk_data.get('data_success'):
print(" ✓ 已获取问财风险数据(限售解禁、大股东减持、重要事件)")
else:
print(" ⚠ 未获取到风险数据,将基于基本信息分析")
time.sleep(1)
# 构建风险数据文本
risk_data_text = ""
if risk_data and risk_data.get('data_success'):
# 使用格式化的风险数据
from risk_data_fetcher import RiskDataFetcher
fetcher = RiskDataFetcher()
risk_data_text = f"""
【实际风险数据】(来自问财)
{fetcher.format_risk_data_for_ai(risk_data)}
以上是通过问财(pywencai)获取的实际风险数据,请重点关注这些数据进行深度风险分析。
"""
risk_prompt = f"""
作为风险管理专家,请基于以下信息进行风险评估:
作为资深风险管理专家,请基于以下信息进行全面深度的风险评估:
股票信息:
- 股票代码:{stock_info.get('symbol', 'N/A')}
@@ -93,32 +114,105 @@ class StockAnalysisAgents:
- RSI{indicators.get('rsi', 'N/A')}
- 布林带位置:当前价格相对于上下轨的位置
- 波动率指标等
{risk_data_text}
请从以下角度进行风险评估
1. 市场风险(系统性风险)
2. 个股风险(非系统性风险
3. 流动性风险
4. 波动性风险
5. 估值风险
6. 行业风险
7. 风险等级评定(低/中/高)
8. 风险控制建议
⚠️ 重要提示:以上风险数据是从问财(pywencai)实时查询的完整原始数据,请你
1. 仔细解析每一条记录的所有字段信息
2. 识别数据中的关键风险点(时间、规模、频率、股东身份等
3. 对数据进行深度分析,不要遗漏任何重要信息
4. 如果数据中有日期字段,要特别关注最近的记录和即将发生的事件
5. 如果数据中有金额/比例字段,要评估其规模和影响力
6. 基于实际数据给出量化的风险评估,而不是空泛的描述
给出专业的风险评估报告。
请从以下角度进行全面的风险评估
1. **限售解禁风险分析** ⭐ 重点
- 解禁时间和规模评估
- 解禁对股价的潜在冲击
- 解禁股东类型分析(创始人/投资机构/其他)
- 历史解禁后股价走势参考
- 风险等级评定和应对建议
2. **股东减持风险分析** ⭐ 重点
- 减持频率和力度评估
- 减持股东身份和意图分析
- 减持对市场信心的影响
- 是否存在连续减持或集中减持
- 风险警示和投资建议
3. **重要事件风险分析** ⭐ 重点
- 识别可能影响股价的重大事件
- 事件性质判断(利好/利空/中性)
- 事件影响的时间维度(短期/中期/长期)
- 事件的确定性和不确定性
- 风险提示和关注要点
4. **市场风险(系统性风险)**
- 宏观经济环境风险
- 市场整体走势风险
- Beta系数反映的市场敏感度
- 系统性风险应对策略
5. **个股风险(非系统性风险)**
- 公司基本面风险
- 经营管理风险
- 竞争力风险
- 行业地位风险
6. **流动性风险**
- 成交量和换手率分析
- 买卖盘深度评估
- 流动性枯竭风险
- 大额交易影响评估
7. **波动性风险**
- 价格波动幅度分析
- 52周最高最低位分析
- RSI等技术指标的风险提示
- 波动率对投资的影响
8. **估值风险**
- 当前估值水平评估
- 市场预期和估值偏差
- 估值过高风险警示
9. **行业风险**
- 行业周期阶段
- 行业竞争格局
- 行业政策风险
- 行业技术变革风险
10. **综合风险评定**
- 风险等级评定(低/中/高)
- 主要风险因素排序
- 风险暴露时间窗口
- 风险演变趋势判断
11. **风险控制建议** ⭐ 核心
- 仓位控制建议(具体比例)
- 止损位设置建议(具体价位)
- 风险规避策略(什么情况下不建议投资)
- 风险对冲方案(如果适用)
- 持仓时间建议
- 重点关注指标和信号
请基于实际数据进行客观、专业、严谨的风险评估,给出可操作的风险控制建议。
如果某些风险数据缺失,也要指出数据缺失本身可能带来的风险。
"""
messages = [
{"role": "system", "content": "你是一名专业的风险管理专家,具有丰富的风险识别和控制经验。"},
{"role": "system", "content": "你是一名资深的风险管理专家,具有20年以上的风险识别和控制经验,擅长全面评估各类投资风险,特别关注限售解禁、股东减持、重要事件等可能影响股价的风险因素。你擅长从海量原始数据中提取关键信息,进行深度解析和量化评估"},
{"role": "user", "content": risk_prompt}
]
analysis = self.deepseek_client.call_api(messages)
analysis = self.deepseek_client.call_api(messages, max_tokens=6000)
return {
"agent_name": "风险管理师",
"agent_role": "负责风险识别、风险评估、风险控制策略制定",
"analysis": analysis,
"focus_areas": ["风险识别", "风险量化", "风险控制", "资产配置"],
"focus_areas": ["限售解禁风险", "股东减持风险", "重要事件风险", "风险识别", "风险量化", "风险控制", "资产配置"],
"risk_data": risk_data, # 保存风险数据以供后续使用
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
}
@@ -313,7 +407,7 @@ class StockAnalysisAgents:
def run_multi_agent_analysis(self, stock_info: Dict, stock_data: Any, indicators: Dict,
financial_data: Dict = None, fund_flow_data: Dict = None,
sentiment_data: Dict = None, news_data: Dict = None,
quarterly_data: Dict = None,
quarterly_data: Dict = None, risk_data: Dict = None,
enabled_analysts: Dict = None) -> Dict[str, Any]:
"""运行多智能体分析
@@ -356,9 +450,9 @@ class StockAnalysisAgents:
if enabled_analysts.get('fund_flow', True):
agents_results["fund_flow"] = self.fund_flow_analyst_agent(stock_info, indicators, fund_flow_data)
# 风险管理分析
# 风险管理分析(传入风险数据)
if enabled_analysts.get('risk', True):
agents_results["risk_management"] = self.risk_management_agent(stock_info, indicators)
agents_results["risk_management"] = self.risk_management_agent(stock_info, indicators, risk_data)
# 市场情绪分析(传入市场情绪数据)
if enabled_analysts.get('sentiment', False):
+64 -19
View File
@@ -636,9 +636,21 @@ def main():
st.info(f"📊 准备分析 {len(stock_list)} 只股票: {', '.join(stock_list)}")
# 清除之前的批量分析结果
# 清除之前的分析结果(包括单个和批量)
if 'batch_analysis_results' in st.session_state:
del st.session_state.batch_analysis_results
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 'just_completed' in st.session_state:
del st.session_state.just_completed
# 获取批量模式
batch_mode = st.session_state.get('batch_mode', '顺序分析')
@@ -646,8 +658,12 @@ def main():
# 运行批量分析
run_batch_analysis(stock_list, period, batch_mode)
# 检查是否有已完成的批量分析结果(优先显示批量结果)
if '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)
# 检查是否有已完成的单个分析结果(但不是刚刚完成的,避免重复显示)
if 'analysis_completed' in st.session_state and st.session_state.analysis_completed:
elif 'analysis_completed' in st.session_state and st.session_state.analysis_completed:
# 如果是刚刚完成的分析,清除标志,避免重复显示
if st.session_state.get('just_completed', False):
st.session_state.just_completed = False
@@ -677,10 +693,6 @@ 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()
@@ -832,6 +844,15 @@ def analyze_single_stock_for_batch(symbol, period, enabled_analysts_config=None,
except:
pass
# 5.5 获取风险数据(限售解禁、大股东减持、重要事件,可选)
risk_data = None
enable_risk = enabled_analysts_config.get('risk', True)
if enable_risk and fetcher._is_chinese_stock(symbol):
try:
risk_data = fetcher.get_risk_data(symbol)
except:
pass
# 6. 初始化AI分析系统
agents = StockAnalysisAgents(model=selected_model)
@@ -841,7 +862,7 @@ def analyze_single_stock_for_batch(symbol, period, enabled_analysts_config=None,
# 7. 运行多智能体分析
agents_results = agents.run_multi_agent_analysis(
stock_info, stock_data, indicators, financial_data,
fund_flow_data, sentiment_data, news_data, quarterly_data,
fund_flow_data, sentiment_data, news_data, quarterly_data, risk_data,
enabled_analysts=enabled_analysts_config
)
@@ -1137,6 +1158,36 @@ def run_stock_analysis(symbol, period):
news_data = None
elif enable_news and not fetcher._is_chinese_stock(symbol):
st.info("️ 美股暂不支持新闻数据")
progress_bar.progress(45)
# 5.5 获取风险数据(仅在选择了风险管理师时,使用问财数据源)
enable_risk = st.session_state.get('enable_risk', True)
risk_data = None
if enable_risk and fetcher._is_chinese_stock(symbol):
status_text.text("⚠️ 正在获取风险数据(限售解禁、大股东减持、重要事件)...")
try:
risk_data = fetcher.get_risk_data(symbol)
if risk_data and risk_data.get('data_success'):
# 统计获取到的风险数据类型
risk_types = []
if risk_data.get('lifting_ban') and risk_data['lifting_ban'].get('has_data'):
risk_types.append("限售解禁")
if risk_data.get('shareholder_reduction') and risk_data['shareholder_reduction'].get('has_data'):
risk_types.append("大股东减持")
if risk_data.get('important_events') and risk_data['important_events'].get('has_data'):
risk_types.append("重要事件")
if risk_types:
st.info(f"✅ 成功获取风险数据:{', '.join(risk_types)}")
else:
st.info("️ 暂无风险相关数据")
else:
st.info("ℹ️ 暂无风险相关数据,将基于基本信息进行风险分析")
except Exception as e:
st.warning(f"⚠️ 获取风险数据时出错: {str(e)}")
risk_data = None
elif enable_risk and not fetcher._is_chinese_stock(symbol):
st.info("ℹ️ 美股暂不支持风险数据(限售解禁、大股东减持等)")
progress_bar.progress(50)
# 6. 初始化AI分析系统
@@ -1165,7 +1216,7 @@ def run_stock_analysis(symbol, period):
status_text.text("🔍 AI分析师团队正在分析,请耐心等待几分钟...")
agents_results = agents.run_multi_agent_analysis(
stock_info, stock_data, indicators, financial_data,
fund_flow_data, sentiment_data, news_data, quarterly_data,
fund_flow_data, sentiment_data, news_data, quarterly_data, risk_data,
enabled_analysts=enabled_analysts
)
progress_bar.progress(75)
@@ -2504,22 +2555,16 @@ def display_comparison_table(results):
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 ''
# 显示表格
# 显示表格(不使用样式,避免matplotlib导入问题)
st.dataframe(
df.style.applymap(highlight_rating, subset=['投资评级']),
df,
use_container_width=True,
height=400
)
# 添加评级说明
st.caption("💡 投资评级说明:强烈买入 > 买入 > 持有 > 卖出 > 强烈卖出")
# 添加筛选功能
st.markdown("---")
st.subheader("🔍 快速筛选")
File diff suppressed because one or more lines are too long
+469
View File
@@ -0,0 +1,469 @@
"""
风险数据获取模块
使用pywencai获取股票风险相关信息:
1. 限售解禁数据
2. 大股东减持公告
3. 近期重要事件
"""
import pywencai
import pandas as pd
from typing import Dict, Any
import time
import warnings
import os
# 屏蔽pywencai的Node.js警告信息(不影响功能)
warnings.filterwarnings('ignore', category=DeprecationWarning)
os.environ['PYTHONWARNINGS'] = 'ignore::DeprecationWarning'
os.environ['NODE_NO_WARNINGS'] = '1' # 屏蔽Node.js警告
class RiskDataFetcher:
"""风险数据获取类"""
def __init__(self):
"""初始化"""
pass
def get_risk_data(self, symbol: str) -> Dict[str, Any]:
"""
获取股票风险相关数据
Args:
symbol: 股票代码(如:600000
Returns:
包含风险数据的字典
"""
print(f"\n正在获取 {symbol} 的风险数据...")
risk_data = {
'symbol': symbol,
'data_success': False,
'lifting_ban': None, # 限售解禁数据
'shareholder_reduction': None, # 大股东减持数据
'important_events': None, # 重要事件数据
'error': None
}
try:
# 1. 获取限售解禁数据
print(" 查询限售解禁数据...")
lifting_ban = self._get_lifting_ban_data(symbol)
risk_data['lifting_ban'] = lifting_ban
if lifting_ban and lifting_ban.get('has_data'):
print(f" 获取到限售解禁数据")
else:
print(f" 暂无限售解禁数据")
time.sleep(1) # 避免请求过快
# 2. 获取大股东减持公告
print(" 查询大股东减持公告...")
reduction = self._get_shareholder_reduction_data(symbol)
risk_data['shareholder_reduction'] = reduction
if reduction and reduction.get('has_data'):
print(f" 获取到大股东减持数据")
else:
print(f" 暂无大股东减持数据")
time.sleep(1) # 避免请求过快
# 3. 获取近期重要事件
print(" 查询近期重要事件...")
events = self._get_important_events_data(symbol)
risk_data['important_events'] = events
if events and events.get('has_data'):
print(f" 获取到重要事件数据")
else:
print(f" 暂无重要事件数据")
# 如果至少有一个数据源成功,则认为获取成功
if (lifting_ban and lifting_ban.get('has_data')) or \
(reduction and reduction.get('has_data')) or \
(events and events.get('has_data')):
risk_data['data_success'] = True
print(f"风险数据获取完成")
else:
print(f"未获取到风险相关数据")
except Exception as e:
print(f"风险数据获取失败: {str(e)}")
risk_data['error'] = str(e)
return risk_data
def _get_lifting_ban_data(self, symbol: str) -> Dict[str, Any]:
"""获取限售解禁数据"""
result = {
'has_data': False,
'query': f"{symbol}限售解禁",
'data': None,
'summary': None
}
try:
# 构建问句
query = f"{symbol}限售解禁"
# 使用pywencai查询
response = pywencai.get(query=query, loop=True)
if response is None:
return result
# 处理返回结果
df_result = self._convert_to_dataframe(response)
if df_result is None or df_result.empty:
return result
# 提取有用的信息
result['has_data'] = True
result['data'] = df_result
# 生成摘要
summary = []
# 尝试提取关键字段
if '解禁时间' in df_result.columns or '限售解禁日' in df_result.columns:
time_col = '解禁时间' if '解禁时间' in df_result.columns else '限售解禁日'
summary.append(f"发现 {len(df_result)} 条解禁记录")
# 提取最近的解禁记录
recent_records = df_result.head(5)
for idx, row in recent_records.iterrows():
record_info = []
if time_col in row.index:
record_info.append(f"日期: {row[time_col]}")
if '解禁股数' in row.index:
record_info.append(f"解禁股数: {row['解禁股数']}")
if '解禁市值' in row.index:
record_info.append(f"解禁市值: {row['解禁市值']}")
if '股东名称' in row.index:
record_info.append(f"股东: {row['股东名称']}")
if record_info:
summary.append(" | ".join(record_info))
else:
# 如果没有标准字段,只记录有数据
summary.append(f"获取到 {len(df_result)} 条相关记录")
result['summary'] = "\n".join(summary) if summary else "有限售解禁数据"
except Exception as e:
result['error'] = str(e)
return result
def _get_shareholder_reduction_data(self, symbol: str) -> Dict[str, Any]:
"""获取大股东减持公告数据"""
result = {
'has_data': False,
'query': f"{symbol}大股东减持公告",
'data': None,
'summary': None
}
try:
# 构建问句
query = f"{symbol}大股东减持公告"
# 使用pywencai查询
response = pywencai.get(query=query, loop=True)
if response is None:
return result
# 处理返回结果
df_result = self._convert_to_dataframe(response)
if df_result is None or df_result.empty:
return result
# 提取有用的信息
result['has_data'] = True
result['data'] = df_result
# 生成摘要
summary = []
# 尝试提取关键字段
if '公告日期' in df_result.columns or '减持日期' in df_result.columns:
date_col = '公告日期' if '公告日期' in df_result.columns else '减持日期'
summary.append(f"发现 {len(df_result)} 条减持公告")
# 提取最近的减持记录
recent_records = df_result.head(5)
for idx, row in recent_records.iterrows():
record_info = []
if date_col in row.index:
record_info.append(f"日期: {row[date_col]}")
if '股东名称' in row.index:
record_info.append(f"股东: {row['股东名称']}")
if '减持股数' in row.index:
record_info.append(f"减持股数: {row['减持股数']}")
if '减持比例' in row.index:
record_info.append(f"减持比例: {row['减持比例']}")
if record_info:
summary.append(" | ".join(record_info))
else:
# 如果没有标准字段,只记录有数据
summary.append(f"获取到 {len(df_result)} 条相关记录")
result['summary'] = "\n".join(summary) if summary else "有大股东减持数据"
except Exception as e:
result['error'] = str(e)
return result
def _get_important_events_data(self, symbol: str) -> Dict[str, Any]:
"""获取近期重要事件数据"""
result = {
'has_data': False,
'query': f"{symbol}近期重要事件",
'data': None,
'summary': None
}
try:
# 构建问句
query = f"{symbol}近期重要事件"
# 使用pywencai查询
response = pywencai.get(query=query, loop=True)
if response is None:
return result
# 处理返回结果
df_result = self._convert_to_dataframe(response)
if df_result is None or df_result.empty:
return result
# 提取有用的信息
result['has_data'] = True
result['data'] = df_result
# 生成摘要
summary = []
# 尝试提取关键字段
if '事件时间' in df_result.columns or '公告日期' in df_result.columns:
time_col = '事件时间' if '事件时间' in df_result.columns else '公告日期'
summary.append(f"发现 {len(df_result)} 条重要事件")
# 提取最近的事件
recent_events = df_result.head(10)
for idx, row in recent_events.iterrows():
event_info = []
if time_col in row.index:
event_info.append(f"时间: {row[time_col]}")
if '事件类型' in row.index:
event_info.append(f"类型: {row['事件类型']}")
if '事件内容' in row.index:
content = str(row['事件内容'])[:100] # 限制长度
event_info.append(f"内容: {content}")
elif '标题' in row.index:
title = str(row['标题'])[:100]
event_info.append(f"标题: {title}")
if event_info:
summary.append(" | ".join(event_info))
else:
# 如果没有标准字段,只记录有数据
summary.append(f"获取到 {len(df_result)} 条相关记录")
result['summary'] = "\n".join(summary) if summary else "有重要事件数据"
except Exception as e:
result['error'] = str(e)
return result
def _convert_to_dataframe(self, result) -> pd.DataFrame:
"""将pywencai返回结果转换为DataFrame"""
try:
if result is None:
return None
df_result = None
if isinstance(result, dict):
try:
df_result = pd.DataFrame([result])
except Exception:
return None
elif isinstance(result, pd.DataFrame):
df_result = result
else:
return None
if df_result is None or df_result.empty:
return None
# 处理嵌套结构(tableV1
if 'tableV1' in df_result.columns and len(df_result.columns) == 1:
table_v1_data = df_result.iloc[0]['tableV1']
if isinstance(table_v1_data, pd.DataFrame):
df_result = table_v1_data
elif isinstance(table_v1_data, list) and len(table_v1_data) > 0:
df_result = pd.DataFrame(table_v1_data)
else:
return None
# 处理嵌套结构(title_content等单列嵌套)
# 如果只有一列,且该列的值是DataFrame,则展开
if len(df_result.columns) == 1:
col_name = df_result.columns[0]
first_value = df_result.iloc[0][col_name]
if isinstance(first_value, pd.DataFrame):
print(f" 检测到嵌套DataFrame(列名: {col_name}),正在展开...")
df_result = first_value
return df_result if not df_result.empty else None
except Exception as e:
print(f" 转换DataFrame时出错: {str(e)}")
return None
def format_risk_data_for_ai(self, risk_data: Dict[str, Any]) -> str:
"""格式化风险数据供AI分析使用 - 直接转换DataFrame为字符串"""
if not risk_data or not risk_data.get('data_success'):
return "未获取到风险数据"
formatted_text = []
try:
# 1. 限售解禁数据
lifting_ban = risk_data.get('lifting_ban')
if lifting_ban and lifting_ban.get('has_data') and lifting_ban.get('data') is not None:
formatted_text.append("=" * 80)
formatted_text.append("【限售解禁数据】")
formatted_text.append("=" * 80)
formatted_text.append(f"查询语句: {lifting_ban.get('query', '')}")
formatted_text.append("")
# 直接将DataFrame转换为字符串(最多50行)
df = lifting_ban.get('data')
try:
df_str = df.head(50).to_string(index=False, max_rows=50, max_cols=20)
formatted_text.append(f"{len(df)} 条记录,显示前50条:")
formatted_text.append(df_str)
except Exception as e:
formatted_text.append(f"数据转换失败: {str(e)}")
formatted_text.append("")
# 2. 大股东减持数据
reduction = risk_data.get('shareholder_reduction')
if reduction and reduction.get('has_data') and reduction.get('data') is not None:
formatted_text.append("=" * 80)
formatted_text.append("【大股东减持数据】")
formatted_text.append("=" * 80)
formatted_text.append(f"查询语句: {reduction.get('query', '')}")
formatted_text.append("")
# 直接将DataFrame转换为字符串(最多50行)
df = reduction.get('data')
try:
df_str = df.head(50).to_string(index=False, max_rows=50, max_cols=20)
formatted_text.append(f"{len(df)} 条记录,显示前50条:")
formatted_text.append(df_str)
except Exception as e:
formatted_text.append(f"数据转换失败: {str(e)}")
formatted_text.append("")
# 3. 重要事件数据
events = risk_data.get('important_events')
if events and events.get('has_data') and events.get('data') is not None:
formatted_text.append("=" * 80)
formatted_text.append("【重要事件数据】")
formatted_text.append("=" * 80)
formatted_text.append(f"查询语句: {events.get('query', '')}")
formatted_text.append("")
# 直接将DataFrame转换为字符串(最多50行)
df = events.get('data')
try:
df_str = df.head(50).to_string(index=False, max_rows=50, max_cols=20)
formatted_text.append(f"{len(df)} 条记录,显示前50条:")
formatted_text.append(df_str)
except Exception as e:
formatted_text.append(f"数据转换失败: {str(e)}")
formatted_text.append("")
return "\n".join(formatted_text) if formatted_text else "暂无风险数据"
except Exception as e:
print(f"格式化风险数据时出错: {str(e)}")
import traceback
traceback.print_exc()
return f"格式化风险数据时出错: {str(e)}"
def _format_dataframe_for_ai(self, df: pd.DataFrame, data_type: str) -> str:
"""将DataFrame格式化为AI易读的文本格式"""
lines = []
# 显示数据总数
lines.append(f"{len(df)}{data_type}记录")
lines.append("")
# 显示列名
lines.append(f"数据字段:{', '.join(df.columns.tolist())}")
lines.append("")
# 逐行显示数据(最多显示50条,避免数据过大)
max_rows = min(50, len(df))
for idx, row in df.head(max_rows).iterrows():
lines.append(f"【记录 {idx + 1}")
# 显示每个字段的值
for col in df.columns:
value = row[col]
# 处理不同类型的值
if pd.isna(value):
value_str = "无数据"
elif isinstance(value, (int, float)):
value_str = str(value)
else:
value_str = str(value)
# 限制过长的字符串
if len(value_str) > 200:
value_str = value_str[:200] + "..."
lines.append(f" {col}: {value_str}")
lines.append("")
if len(df) > max_rows:
lines.append(f"... 还有 {len(df) - max_rows} 条记录(已省略)")
lines.append("")
return "\n".join(lines)
# 测试代码
if __name__ == "__main__":
fetcher = RiskDataFetcher()
# 测试获取风险数据
test_symbol = "600000"
print(f"测试获取 {test_symbol} 的风险数据...")
risk_data = fetcher.get_risk_data(test_symbol)
print("\n" + "=" * 60)
print("获取结果:")
print("=" * 60)
print(f"数据获取成功: {risk_data['data_success']}")
if risk_data['data_success']:
print("\n格式化的风险数据:")
print(fetcher.format_risk_data_for_ai(risk_data))
+28
View File
@@ -816,6 +816,34 @@ class StockDataFetcher:
# - 新方案:使用 akshare 的 stock_individual_fund_flow 接口
# - 新方案优势:数据标准化、准确获取最近20个交易日、6类资金详细分类
def get_risk_data(self, symbol):
"""
获取股票风险数据(限售解禁、大股东减持、重要事件)
只支持中国A股
"""
try:
# 只有中国A股才支持风险数据查询
if not self._is_chinese_stock(symbol):
return {
'symbol': symbol,
'data_success': False,
'error': '仅支持中国A股风险数据查询'
}
# 使用风险数据获取器
from risk_data_fetcher import RiskDataFetcher
fetcher = RiskDataFetcher()
risk_data = fetcher.get_risk_data(symbol)
return risk_data
except Exception as e:
return {
'symbol': symbol,
'data_success': False,
'error': f'获取风险数据失败: {str(e)}'
}
def _safe_convert(self, value):
"""安全地转换数值"""
if value is None or value == '' or (isinstance(value, float) and np.isnan(value)):
+173
View File
@@ -0,0 +1,173 @@
"""
风险数据获取功能测试脚本
测试pywencai获取限售解禁、大股东减持、重要事件数据
"""
from risk_data_fetcher import RiskDataFetcher
def test_risk_data_fetcher():
"""测试风险数据获取器"""
print("=" * 80)
print("风险数据获取功能测试")
print("=" * 80)
# 初始化获取器
fetcher = RiskDataFetcher()
# 测试股票列表(建议使用大盘股,数据更全)
test_symbols = [
"600000", # 浦发银行
"000001", # 平安银行
"600519", # 茅台
]
for symbol in test_symbols:
print(f"\n{'=' * 80}")
print(f"测试股票: {symbol}")
print(f"{'=' * 80}")
# 获取风险数据
risk_data = fetcher.get_risk_data(symbol)
# 显示结果
print(f"\n数据获取成功: {risk_data.get('data_success', False)}")
if risk_data.get('error'):
print(f"错误信息: {risk_data['error']}")
continue
# 显示限售解禁数据
lifting_ban = risk_data.get('lifting_ban')
if lifting_ban and lifting_ban.get('has_data'):
print(f"\n✅ 限售解禁数据:")
print(f" 查询语句: {lifting_ban.get('query')}")
df_data = lifting_ban.get('data')
if df_data is not None and not df_data.empty:
print(f" 记录数: {len(df_data)}")
print(f" 字段: {', '.join(df_data.columns.tolist())}")
print(f" 前3条记录:")
print(df_data.head(3).to_string(index=False))
else:
print(f"\n️ 暂无限售解禁数据")
# 显示股东减持数据
reduction = risk_data.get('shareholder_reduction')
if reduction and reduction.get('has_data'):
print(f"\n✅ 大股东减持数据:")
print(f" 查询语句: {reduction.get('query')}")
df_data = reduction.get('data')
if df_data is not None and not df_data.empty:
print(f" 记录数: {len(df_data)}")
print(f" 字段: {', '.join(df_data.columns.tolist())}")
print(f" 前3条记录:")
print(df_data.head(3).to_string(index=False))
else:
print(f"\n️ 暂无大股东减持数据")
# 显示重要事件数据
events = risk_data.get('important_events')
if events and events.get('has_data'):
print(f"\n✅ 重要事件数据:")
print(f" 查询语句: {events.get('query')}")
df_data = events.get('data')
if df_data is not None and not df_data.empty:
print(f" 记录数: {len(df_data)}")
print(f" 字段: {', '.join(df_data.columns.tolist())}")
print(f" 前3条记录:")
print(df_data.head(3).to_string(index=False))
else:
print(f"\n️ 暂无重要事件数据")
# 显示格式化后的数据
print(f"\n{'=' * 80}")
print("格式化供AI分析的数据:")
print(f"{'=' * 80}")
formatted_data = fetcher.format_risk_data_for_ai(risk_data)
print(formatted_data)
print(f"\n{'=' * 80}")
print("测试完成!")
print(f"{'=' * 80}")
def test_risk_management_agent():
"""测试风险管理师功能"""
print("\n" + "=" * 80)
print("风险管理师AI分析测试")
print("=" * 80)
# 需要配置API Key才能测试
try:
from ai_agents import StockAnalysisAgents
from risk_data_fetcher import RiskDataFetcher
# 测试股票
test_symbol = "600000"
print(f"\n测试股票: {test_symbol}")
# 获取风险数据
fetcher = RiskDataFetcher()
risk_data = fetcher.get_risk_data(test_symbol)
if not risk_data.get('data_success'):
print("未获取到风险数据,跳过AI分析测试")
return
# 模拟股票信息和指标
stock_info = {
'symbol': test_symbol,
'name': '测试股票',
'current_price': 10.5,
'beta': 1.2,
'52_week_high': 12.0,
'52_week_low': 8.5
}
indicators = {
'rsi': 65.5,
'macd': 0.15
}
# 初始化AI分析系统
print("\n初始化AI分析系统...")
agents = StockAnalysisAgents()
# 运行风险管理师分析
print("\n运行风险管理师分析...")
result = agents.risk_management_agent(stock_info, indicators, risk_data)
# 显示分析结果
print(f"\n{'=' * 80}")
print(f"分析师: {result['agent_name']}")
print(f"职责: {result['agent_role']}")
print(f"关注领域: {', '.join(result['focus_areas'])}")
print(f"{'=' * 80}")
print("\n分析报告:")
print(result['analysis'])
except ImportError as e:
print(f"⚠️ 导入模块失败: {e}")
print("提示:请确保已安装所有依赖并配置API Key")
except Exception as e:
print(f"❌ 测试失败: {e}")
if __name__ == "__main__":
# 测试1:风险数据获取
test_risk_data_fetcher()
# 测试2AI分析(需要API Key
print("\n\n是否测试AI分析功能?(需要配置DeepSeek API Key")
user_input = input("输入 y 继续,其他键跳过: ")
if user_input.lower() == 'y':
test_risk_management_agent()
else:
print("\n跳过AI分析测试")
print("\n✅ 所有测试完成!")
+146
View File
@@ -0,0 +1,146 @@
"""
调试风险数据获取和格式化
检查数据是否真的传递给AI
"""
from risk_data_fetcher import RiskDataFetcher
import json
def test_full_data_flow(symbol="300433"):
"""测试完整数据流程"""
print("=" * 80)
print(f"测试股票: {symbol}")
print("=" * 80)
fetcher = RiskDataFetcher()
# 1. 获取原始数据
print("\n【步骤1】获取原始风险数据...")
risk_data = fetcher.get_risk_data(symbol)
print(f"\n数据获取成功: {risk_data.get('data_success', False)}")
# 2. 查看每类数据的详情
print("\n【步骤2】查看原始数据详情...")
# 限售解禁
lifting_ban = risk_data.get('lifting_ban')
if lifting_ban:
print(f"\n限售解禁数据:")
print(f" has_data: {lifting_ban.get('has_data')}")
df = lifting_ban.get('data')
if df is not None:
print(f" 记录数: {len(df)}")
print(f" 列名: {list(df.columns)}")
print(f"\n 前3条数据:")
print(df.head(3))
else:
print(f" data字段为None")
# 大股东减持
reduction = risk_data.get('shareholder_reduction')
if reduction:
print(f"\n大股东减持数据:")
print(f" has_data: {reduction.get('has_data')}")
df = reduction.get('data')
if df is not None:
print(f" 记录数: {len(df)}")
print(f" 列名: {list(df.columns)}")
print(f"\n 前3条数据:")
print(df.head(3))
print(f"\n 完整第1条数据:")
if len(df) > 0:
first_row = df.iloc[0]
for col in df.columns:
print(f" {col}: {first_row[col]}")
else:
print(f" data字段为None")
# 重要事件
events = risk_data.get('important_events')
if events:
print(f"\n重要事件数据:")
print(f" has_data: {events.get('has_data')}")
df = events.get('data')
if df is not None:
print(f" 记录数: {len(df)}")
print(f" 列名: {list(df.columns)}")
print(f"\n 前3条数据:")
print(df.head(3))
else:
print(f" data字段为None")
# 3. 测试格式化后的数据
print("\n" + "=" * 80)
print("【步骤3】测试格式化后传给AI的数据")
print("=" * 80)
formatted_data = fetcher.format_risk_data_for_ai(risk_data)
print("\n格式化后的数据长度:", len(formatted_data), "字符")
print("\n格式化后的数据内容:")
print(formatted_data)
# 4. 保存到文件供查看
print("\n" + "=" * 80)
print("【步骤4】保存数据到文件")
print("=" * 80)
with open("risk_data_debug_output.txt", "w", encoding="utf-8") as f:
f.write("=" * 80 + "\n")
f.write("原始数据结构\n")
f.write("=" * 80 + "\n\n")
# 保存原始数据(不包含DataFrame,只保存基本信息)
f.write("data_success: " + str(risk_data.get('data_success')) + "\n\n")
for key in ['lifting_ban', 'shareholder_reduction', 'important_events']:
data = risk_data.get(key)
if data:
f.write(f"\n{key}:\n")
f.write(f" has_data: {data.get('has_data')}\n")
f.write(f" query: {data.get('query')}\n")
df = data.get('data')
if df is not None:
f.write(f" 记录数: {len(df)}\n")
f.write(f" 列名: {list(df.columns)}\n")
f.write("\n" + "=" * 80 + "\n")
f.write("格式化后传给AI的数据\n")
f.write("=" * 80 + "\n\n")
f.write(formatted_data)
print("✓ 数据已保存到 risk_data_debug_output.txt")
# 5. 检查数据是否完整
print("\n" + "=" * 80)
print("【步骤5】数据完整性检查")
print("=" * 80)
if "【大股东减持数据】" in formatted_data:
print("✓ 格式化数据中包含大股东减持数据")
# 检查是否有实际内容
reduction_section = formatted_data.split("【大股东减持数据】")[1].split("=" * 80)[0]
if len(reduction_section.strip()) > 100:
print(f"✓ 减持数据部分有 {len(reduction_section)} 字符")
else:
print(f"⚠️ 减持数据部分只有 {len(reduction_section)} 字符,可能数据不完整")
else:
print("❌ 格式化数据中未找到大股东减持数据")
print("\n" + "=" * 80)
print("测试完成!请查看 risk_data_debug_output.txt 文件")
print("=" * 80)
if __name__ == "__main__":
import sys
# 默认测试300433,也可以通过命令行参数指定其他股票
symbol = sys.argv[1] if len(sys.argv) > 1 else "300433"
test_full_data_flow(symbol)
+371
View File
@@ -0,0 +1,371 @@
# 风险管理师增强功能说明 ⚠️
## 📋 功能概述
风险管理师功能已全面增强,现在使用**问财(pywencai)**实时获取三大类风险数据,为投资决策提供更全面的风险评估。
## ✨ 新增功能
### 1. 三大风险数据源 🔍
风险管理师现在会自动查询以下三类关键风险信息:
| 风险类型 | 查询问句 | 数据内容 |
|---------|---------|---------|
| **限售解禁** | `股票代码限售解禁` | 解禁时间、解禁股数、解禁市值、股东名称 |
| **大股东减持** | `股票代码大股东减持公告` | 减持日期、股东名称、减持股数、减持比例 |
| **重要事件** | `股票代码近期重要事件` | 事件时间、事件类型、事件内容 |
### 2. 完整数据传递 📊
系统会将pywencai返回的**完整原始数据**喂给AI,包括:
- **所有字段**:不遗漏任何信息
- **所有记录**:最多传递50条记录(避免过载)
- **结构化格式**:AI易于解析的格式
- **详细信息**:时间、金额、比例、股东名称等
**数据传递示例:**
```
================================================================================
【限售解禁完整数据】
================================================================================
查询语句: 600000限售解禁
共 10 条限售解禁记录
数据字段:股票代码, 股票简称, 解禁时间, 解禁股数, 解禁市值, 股东名称, ...
【记录 1】
股票代码: 600000
股票简称: 浦发银行
解禁时间: 2024-03-15
解禁股数: 500000000
解禁市值: 5000000000
股东名称: 某某投资公司
...
【记录 2】
...
```
### 3. 智能风险分析 🧠
风险管理师会**深度解析所有原始数据**,进行**11个维度**的全面风险评估:
#### ⭐ 三大重点风险分析
1. **限售解禁风险分析**
- 解禁时间和规模评估
- 解禁对股价的潜在冲击
- 解禁股东类型分析(创始人/投资机构/其他)
- 历史解禁后股价走势参考
- 风险等级评定和应对建议
2. **股东减持风险分析**
- 减持频率和力度评估
- 减持股东身份和意图分析
- 减持对市场信心的影响
- 是否存在连续减持或集中减持
- 风险警示和投资建议
3. **重要事件风险分析**
- 识别可能影响股价的重大事件
- 事件性质判断(利好/利空/中性)
- 事件影响的时间维度(短期/中期/长期)
- 事件的确定性和不确定性
- 风险提示和关注要点
#### 📊 其他风险维度
4. **市场风险**(系统性风险)
5. **个股风险**(非系统性风险)
6. **流动性风险**
7. **波动性风险**
8. **估值风险**
9. **行业风险**
10. **综合风险评定**
11. **风险控制建议**
### 4. AI深度解析能力 🤖
AI会对每条原始记录进行深度分析:
- **时间识别**:自动识别日期字段,关注最近和即将发生的事件
- **金额量化**:评估解禁市值、减持金额的规模和影响
- **股东分析**:识别股东类型(创始人/机构/高管)
- **频率统计**:分析减持频率,判断是否连续减持
- **趋势预判**:基于历史数据预测未来风险
### 5. 可操作的风险控制建议 📋
风险管理师会提供具体的风险控制建议:
- **仓位控制建议**:具体的仓位比例建议
- **止损位设置**:具体的止损价位建议
- **风险规避策略**:明确什么情况下不建议投资
- **风险对冲方案**:如果适用,提供对冲策略
- **持仓时间建议**:建议的持仓周期
- **重点关注指标**:需要持续监控的关键指标
## 🎯 使用方法
### 自动获取(推荐)✅
风险管理师默认启用,系统会自动获取风险数据:
1. 在股票分析界面,确保**风险管理师**处于勾选状态
2. 输入股票代码(仅支持**A股6位代码**)
3. 点击"开始分析"
4. 系统自动获取风险数据并进行分析
### 数据获取流程 🔄
```
1. 查询限售解禁数据 → 2秒延迟
2. 查询大股东减持数据 → 2秒延迟
3. 查询重要事件数据 → 完成
4. 综合所有数据进行AI分析
```
## 📊 数据展示
### 获取成功提示
```
✅ 成功获取风险数据:限售解禁, 大股东减持, 重要事件
```
### 部分数据提示
```
️ 成功获取风险数据:大股东减持
️ 暂无限售解禁和重要事件数据
```
### 无数据提示
```
ℹ️ 暂无风险相关数据,将基于基本信息进行风险分析
```
### 美股提示
```
ℹ️ 美股暂不支持风险数据(限售解禁、大股东减持等)
```
## 💡 使用场景
### 场景1:发现解禁风险 🚨
**案例**:某股票即将迎来大规模限售解禁
**风险管理师分析**
- 识别解禁时间窗口
- 评估解禁规模对股价的压力
- 分析解禁股东类型(创始人vs机构)
- 建议:解禁前减仓,解禁后观察
### 场景2:监控股东减持 ⚠️
**案例**:大股东连续减持
**风险管理师分析**
- 识别减持频率和强度
- 分析减持股东身份(实际控制人/财务投资者)
- 评估对市场信心的影响
- 建议:降低仓位,设置止损位
### 场景3:追踪重要事件 📰
**案例**:公司面临重大诉讼或政策变动
**风险管理师分析**
- 识别事件性质和影响范围
- 评估短中长期影响
- 判断事件的不确定性
- 建议:谨慎持仓,关注后续进展
## 🔧 技术实现
### 核心模块
```
risk_data_fetcher.py # 风险数据获取模块
├── RiskDataFetcher # 风险数据获取类
│ ├── get_risk_data() # 主函数:获取所有风险数据
│ ├── _get_lifting_ban_data() # 获取限售解禁
│ ├── _get_shareholder_reduction_data() # 获取股东减持
│ ├── _get_important_events_data() # 获取重要事件
│ ├── format_risk_data_for_ai() # 格式化供AI分析(完整数据)
│ └── _format_dataframe_for_ai() # DataFrame转文本格式
```
### 数据处理流程
```
pywencai查询 → DataFrame原始数据 → 格式化为文本 → 喂给AI
↓ ↓ ↓ ↓
问句查询 保留所有字段 逐行格式化 深度解析
实时数据 完整记录 AI可读 量化评估
```
### 数据流程
```
用户输入股票代码
stock_data.py → get_risk_data()
risk_data_fetcher.py → 调用pywencai
获取三类风险数据
ai_agents.py → risk_management_agent()
DeepSeek AI 深度分析
生成风险评估报告
```
## 📈 数据来源
- **数据源**:问财(pywencai
- **更新频率**:实时查询
- **查询语法**:自然语言问句
- **数据类型**:结构化表格数据
## ⚙️ 配置说明
### 默认配置
```python
# 风险管理师默认启用
enable_risk = True
# 仅支持A股
supported_markets = ["中国A股(6位代码)"]
# 查询延迟(避免频繁请求)
query_delay = 1
```
### 在批量分析中使用
```python
# 批量分析也支持风险管理功能
enabled_analysts_config = {
'technical': True,
'fundamental': True,
'fund_flow': True,
'risk': True, # 启用风险管理师
'sentiment': False,
'news': False
}
```
## 🎓 最佳实践
### 1. 结合其他分析师 👥
风险管理师的分析应该与其他分析师综合判断:
- **技术分析师**:识别技术面风险信号
- **基本面分析师**:评估基本面恶化风险
- **资金面分析师**:监控主力资金流出
- **风险管理师**:识别特定风险事件
### 2. 重点关注时间窗口 ⏰
特别关注以下时间窗口的风险:
- 解禁前1个月:提前规避风险
- 减持公告后:评估市场反应
- 重大事件发生时:及时调整策略
### 3. 设置风险预警 🔔
可以结合监测功能,设置风险预警:
- 解禁预警:提前30天预警
- 减持预警:出现减持公告时预警
- 事件预警:重大负面事件预警
## 📝 注意事项
### 支持范围
**支持**:中国A股(6位数字代码)
**不支持**:美股、港股
### 数据时效性
- 数据来自问财实时查询
- 可能存在1-2个交易日的延迟
- 建议定期刷新分析
### 网络依赖
- 需要稳定的网络连接
- 查询可能需要5-10秒
- 失败时会自动降级到基础分析
### 数据准确性
- 数据来源于公开信息
- 建议交叉验证重要信息
- 以官方公告为准
## 🚀 未来规划
### 计划新增功能
- [ ] 风险评分量化模型
- [ ] 风险历史对比分析
- [ ] 风险预警阈值设置
- [ ] 风险事件影响回测
- [ ] 多股票风险对比分析
### 优化方向
- 增加更多风险数据源
- 优化数据查询速度
- 提升AI分析准确度
- 增强可视化展示
## 📞 问题反馈
如果在使用过程中遇到问题:
1. **数据获取失败**:检查网络连接,稍后重试
2. **分析结果异常**:确认股票代码正确(6位A股代码)
3. **其他问题**:查看控制台错误信息
## 📚 相关文档
- `主力选股使用指南.md` - 了解主力资金分析
- `分析师团队选择功能说明.md` - 了解如何选择分析师
- `智瞰龙虎功能说明.md` - 了解龙虎榜风险分析
## 🎯 总结
风险管理师增强功能通过**问财实时数据**和**完整数据传递**,为投资者提供:
**三大风险数据**:限售解禁、大股东减持、重要事件
**完整原始数据**:所有字段、所有记录,不遗漏任何信息
**AI深度解析**:自动识别关键风险点,量化评估
**11维度分析**:全面评估各类风险
**可操作建议**:具体的风险控制策略
**实时更新**:基于最新公开信息
**智能分析**DeepSeek AI从海量数据中提取关键信息
**核心优势:**
- 不再是摘要或概括,而是**完整原始数据**
- AI自主解析每条记录的**每个字段**
- 基于实际数据给出**量化风险评估**
- 让AI的分析更准确、更深入、更可靠
让投资决策更安全、更可靠!⚠️📊💡