增强风险管理师分析功能

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
+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))