Files
aiagents-stock/risk_data_fetcher.py
songzhuoyuan befdc32aea tushare
2026-08-11 20:41:31 +08:00

470 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
风险数据获取模块
使用pywencai获取股票风险相关信息:
1. 限售解禁数据
2. 大股东减持公告
3. 近期重要事件
"""
import pywencai
import pandas as pd
from typing import Dict, Any
import time
import warnings
import os
from http_timeout import call_with_timeout
# 屏蔽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 = call_with_timeout(pywencai.get, timeout=30, 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 = call_with_timeout(pywencai.get, timeout=30, 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 = call_with_timeout(pywencai.get, timeout=30, 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))