Files
aiagents-stock/stock_data.py
T
2025-10-09 20:51:10 +08:00

772 lines
34 KiB
Python

import yfinance as yf
import akshare as ak
import pandas as pd
import numpy as np
import ta
from datetime import datetime, timedelta
import requests
import json
import pywencai
class StockDataFetcher:
"""股票数据获取类"""
def __init__(self):
self.data = None
self.info = None
self.financial_data = None
def get_stock_info(self, symbol):
"""获取股票基本信息"""
try:
# 处理中国股票代码
if self._is_chinese_stock(symbol):
return self._get_chinese_stock_info(symbol)
else:
return self._get_us_stock_info(symbol)
except Exception as e:
return {"error": f"获取股票信息失败: {str(e)}"}
def get_stock_data(self, symbol, period="1y", interval="1d"):
"""获取股票历史数据"""
try:
if self._is_chinese_stock(symbol):
return self._get_chinese_stock_data(symbol, period)
else:
return self._get_us_stock_data(symbol, period, interval)
except Exception as e:
return {"error": f"获取股票数据失败: {str(e)}"}
def _is_chinese_stock(self, symbol):
"""判断是否为中国股票"""
# 简单判断:包含数字且长度为6位的认为是中国股票
return symbol.isdigit() and len(symbol) == 6
def _get_chinese_stock_info(self, symbol):
"""获取中国股票基本信息"""
try:
# 初始化基本信息
info = {
"symbol": symbol,
"name": "未知",
"current_price": "N/A",
"change_percent": "N/A",
"pe_ratio": "N/A",
"pb_ratio": "N/A",
"market_cap": "N/A",
"market": "中国A股",
"exchange": "上海/深圳证券交易所"
}
# 方法1: 尝试获取个股详细信息
try:
stock_info = ak.stock_individual_info_em(symbol=symbol)
if stock_info is not None and not stock_info.empty:
for _, row in stock_info.iterrows():
key = row['item']
value = row['value']
if key == '股票简称':
info['name'] = value
elif key == '总市值':
try:
if value and value != '-':
info['market_cap'] = float(value)
except:
pass
elif key == '市盈率-动态':
try:
if value and value != '-':
pe_value = float(value)
if 0 < pe_value <= 1000:
info['pe_ratio'] = pe_value
except:
pass
elif key == '市净率':
try:
if value and value != '-':
pb_value = float(value)
if 0 < pb_value <= 100:
info['pb_ratio'] = pb_value
except:
pass
except Exception as e:
print(f"获取个股详细信息失败: {e}")
# 方法2: 尝试获取实时价格和涨跌幅(如果网络允许)
try:
# 使用更简单的接口获取实时价格
real_time_data = ak.stock_zh_a_spot_em()
if real_time_data is not None and not real_time_data.empty:
stock_real_time = real_time_data[real_time_data['代码'] == symbol]
if not stock_real_time.empty:
row = stock_real_time.iloc[0]
info['current_price'] = row.get('最新价', 'N/A')
info['change_percent'] = row.get('涨跌幅', 'N/A')
if info['name'] == '未知':
info['name'] = row.get('名称', '未知')
# 如果实时数据中有市盈率和市净率,优先使用
if '市盈率-动态' in row and info['pe_ratio'] == 'N/A':
try:
pe_val = row['市盈率-动态']
if pe_val and pe_val != '-':
pe_val = float(pe_val)
if 0 < pe_val <= 1000:
info['pe_ratio'] = pe_val
except:
pass
if '市净率' in row and info['pb_ratio'] == 'N/A':
try:
pb_val = row['市净率']
if pb_val and pb_val != '-':
pb_val = float(pb_val)
if 0 < pb_val <= 100:
info['pb_ratio'] = pb_val
except:
pass
except Exception as e:
print(f"获取实时数据失败: {e}")
# 如果实时数据获取失败,尝试使用历史数据获取价格
try:
hist_data = ak.stock_zh_a_hist(symbol=symbol, period="daily",
start_date=(datetime.now() - timedelta(days=5)).strftime('%Y%m%d'),
end_date=datetime.now().strftime('%Y%m%d'), adjust="qfq")
if hist_data is not None and not hist_data.empty:
latest = hist_data.iloc[-1]
info['current_price'] = latest['收盘']
# 计算涨跌幅
if len(hist_data) > 1:
prev_close = hist_data.iloc[-2]['收盘']
change_pct = ((latest['收盘'] - prev_close) / prev_close) * 100
info['change_percent'] = round(change_pct, 2)
except Exception as e2:
print(f"获取历史数据也失败: {e2}")
# 方法3: 使用百度估值数据获取市盈率和市净率
if info['pe_ratio'] == 'N/A':
try:
pe_data = ak.stock_zh_valuation_baidu(symbol=symbol, indicator="市盈率(TTM)")
if pe_data is not None and not pe_data.empty:
latest_pe = pe_data.iloc[-1]['value']
if latest_pe and latest_pe != '-':
pe_val = float(latest_pe)
if 0 < pe_val <= 1000:
info['pe_ratio'] = pe_val
except Exception as e:
print(f"获取市盈率失败: {e}")
if info['pb_ratio'] == 'N/A':
try:
pb_data = ak.stock_zh_valuation_baidu(symbol=symbol, indicator="市净率")
if pb_data is not None and not pb_data.empty:
latest_pb = pb_data.iloc[-1]['value']
if latest_pb and latest_pb != '-':
pb_val = float(latest_pb)
if 0 < pb_val <= 100:
info['pb_ratio'] = pb_val
except Exception as e:
print(f"获取市净率失败: {e}")
return info
except Exception as e:
print(f"获取中国股票信息完全失败: {e}")
# 返回基本信息,避免完全失败
return {
"symbol": symbol,
"name": f"股票{symbol}",
"current_price": "N/A",
"change_percent": "N/A",
"pe_ratio": "N/A",
"pb_ratio": "N/A",
"market_cap": "N/A",
"market": "中国A股",
"exchange": "上海/深圳证券交易所"
}
def _get_us_stock_info(self, symbol):
"""获取美股基本信息"""
import time
try:
# 添加延迟避免频率限制
time.sleep(1)
ticker = yf.Ticker(symbol)
# 先尝试获取历史数据(通常更稳定)
try:
hist = ticker.history(period="2d")
if not hist.empty:
current_price = hist['Close'].iloc[-1]
if len(hist) > 1:
prev_close = hist['Close'].iloc[-2]
change_percent = ((current_price - prev_close) / prev_close) * 100
else:
change_percent = 'N/A'
else:
current_price = 'N/A'
change_percent = 'N/A'
except:
current_price = 'N/A'
change_percent = 'N/A'
# 获取基本信息
try:
info = ticker.info
# 获取市盈率,优先使用trailing PE,其次forward PE
pe_ratio = info.get('trailingPE', info.get('forwardPE', 'N/A'))
if pe_ratio == 'N/A' or pe_ratio is None or (isinstance(pe_ratio, float) and np.isnan(pe_ratio)):
pe_ratio = 'N/A'
# 获取市净率
pb_ratio = info.get('priceToBook', 'N/A')
if pb_ratio == 'N/A' or pb_ratio is None or (isinstance(pb_ratio, float) and np.isnan(pb_ratio)):
pb_ratio = 'N/A'
# 如果历史数据没有获取到价格,尝试从info获取
if current_price == 'N/A':
current_price = info.get('currentPrice', info.get('regularMarketPrice', 'N/A'))
if change_percent == 'N/A':
change_percent = info.get('regularMarketChangePercent', 'N/A')
if change_percent != 'N/A' and change_percent is not None:
change_percent = change_percent * 100 # 转换为百分比
return {
"symbol": symbol,
"name": info.get('longName', info.get('shortName', 'N/A')),
"current_price": current_price,
"change_percent": change_percent,
"market_cap": info.get('marketCap', 'N/A'),
"pe_ratio": pe_ratio,
"pb_ratio": pb_ratio,
"dividend_yield": info.get('dividendYield', 'N/A'),
"beta": info.get('beta', 'N/A'),
"52_week_high": info.get('fiftyTwoWeekHigh', 'N/A'),
"52_week_low": info.get('fiftyTwoWeekLow', 'N/A'),
"sector": info.get('sector', 'N/A'),
"industry": info.get('industry', 'N/A'),
"market": "美股",
"exchange": info.get('exchange', 'N/A')
}
except Exception as e:
# 如果获取详细信息失败,返回基本价格信息
return {
"symbol": symbol,
"name": f"美股{symbol}",
"current_price": current_price,
"change_percent": change_percent,
"market_cap": 'N/A',
"pe_ratio": 'N/A',
"pb_ratio": 'N/A',
"dividend_yield": 'N/A',
"beta": 'N/A',
"52_week_high": 'N/A',
"52_week_low": 'N/A',
"sector": 'N/A',
"industry": 'N/A',
"market": "美股",
"exchange": 'N/A'
}
except Exception as e:
return {"error": f"获取美股信息失败: {str(e)}"}
def _get_chinese_stock_data(self, symbol, period="1y"):
"""获取中国股票历史数据"""
try:
# 计算日期范围
end_date = datetime.now().strftime('%Y%m%d')
if period == "1y":
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
elif period == "6mo":
start_date = (datetime.now() - timedelta(days=180)).strftime('%Y%m%d')
elif period == "3mo":
start_date = (datetime.now() - timedelta(days=90)).strftime('%Y%m%d')
else:
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
# 获取历史数据
df = ak.stock_zh_a_hist(symbol=symbol, period="daily",
start_date=start_date, end_date=end_date, adjust="qfq")
if df is not None and not df.empty:
# 重命名列以匹配标准格式
df = df.rename(columns={
'日期': 'Date',
'开盘': 'Open',
'收盘': 'Close',
'最高': 'High',
'最低': 'Low',
'成交量': 'Volume'
})
df['Date'] = pd.to_datetime(df['Date'])
df.set_index('Date', inplace=True)
return df
else:
return {"error": "无法获取历史数据"}
except Exception as e:
return {"error": f"获取中国股票数据失败: {str(e)}"}
def _get_us_stock_data(self, symbol, period="1y", interval="1d"):
"""获取美股历史数据"""
try:
ticker = yf.Ticker(symbol)
df = ticker.history(period=period, interval=interval)
if not df.empty:
return df
else:
return {"error": "无法获取历史数据"}
except Exception as e:
return {"error": f"获取美股数据失败: {str(e)}"}
def calculate_technical_indicators(self, df):
"""计算技术指标"""
try:
if isinstance(df, dict) and "error" in df:
return df
# 移动平均线
df['MA5'] = ta.trend.sma_indicator(df['Close'], window=5)
df['MA10'] = ta.trend.sma_indicator(df['Close'], window=10)
df['MA20'] = ta.trend.sma_indicator(df['Close'], window=20)
df['MA60'] = ta.trend.sma_indicator(df['Close'], window=60)
# RSI
df['RSI'] = ta.momentum.rsi(df['Close'], window=14)
# MACD
macd = ta.trend.MACD(df['Close'])
df['MACD'] = macd.macd()
df['MACD_signal'] = macd.macd_signal()
df['MACD_histogram'] = macd.macd_diff()
# 布林带
bollinger = ta.volatility.BollingerBands(df['Close'])
df['BB_upper'] = bollinger.bollinger_hband()
df['BB_middle'] = bollinger.bollinger_mavg()
df['BB_lower'] = bollinger.bollinger_lband()
# KDJ指标
df['K'] = ta.momentum.stoch(df['High'], df['Low'], df['Close'])
df['D'] = ta.momentum.stoch_signal(df['High'], df['Low'], df['Close'])
# 成交量指标
df['Volume_MA5'] = ta.trend.sma_indicator(df['Volume'], window=5)
df['Volume_ratio'] = df['Volume'] / df['Volume_MA5']
return df
except Exception as e:
return {"error": f"计算技术指标失败: {str(e)}"}
def get_latest_indicators(self, df):
"""获取最新的技术指标值"""
try:
if isinstance(df, dict) and "error" in df:
return df
latest = df.iloc[-1]
return {
"price": latest['Close'],
"ma5": latest['MA5'],
"ma10": latest['MA10'],
"ma20": latest['MA20'],
"ma60": latest['MA60'],
"rsi": latest['RSI'],
"macd": latest['MACD'],
"macd_signal": latest['MACD_signal'],
"bb_upper": latest['BB_upper'],
"bb_lower": latest['BB_lower'],
"k_value": latest['K'],
"d_value": latest['D'],
"volume_ratio": latest['Volume_ratio']
}
except Exception as e:
return {"error": f"获取最新指标失败: {str(e)}"}
def get_financial_data(self, symbol):
"""获取详细财务数据"""
try:
if self._is_chinese_stock(symbol):
return self._get_chinese_financial_data(symbol)
else:
return self._get_us_financial_data(symbol)
except Exception as e:
return {"error": f"获取财务数据失败: {str(e)}"}
def _get_chinese_financial_data(self, symbol):
"""获取中国股票财务数据"""
financial_data = {
"symbol": symbol,
"balance_sheet": None, # 资产负债表
"income_statement": None, # 利润表
"cash_flow": None, # 现金流量表
"financial_ratios": {}, # 财务比率
"quarter_data": None, # 季度数据
}
try:
# 1. 获取资产负债表
try:
balance_sheet = ak.stock_financial_abstract_ths(symbol=symbol, indicator="资产负债表")
if balance_sheet is not None and not balance_sheet.empty:
financial_data["balance_sheet"] = balance_sheet.head(8).to_dict('records')
except Exception as e:
print(f"获取资产负债表失败: {e}")
# 2. 获取利润表
try:
income_statement = ak.stock_financial_abstract_ths(symbol=symbol, indicator="利润表")
if income_statement is not None and not income_statement.empty:
financial_data["income_statement"] = income_statement.head(8).to_dict('records')
except Exception as e:
print(f"获取利润表失败: {e}")
# 3. 获取现金流量表
try:
cash_flow = ak.stock_financial_abstract_ths(symbol=symbol, indicator="现金流量表")
if cash_flow is not None and not cash_flow.empty:
financial_data["cash_flow"] = cash_flow.head(8).to_dict('records')
except Exception as e:
print(f"获取现金流量表失败: {e}")
# 4. 获取主要财务指标
try:
financial_indicators = ak.stock_financial_analysis_indicator(symbol=symbol)
if financial_indicators is not None and not financial_indicators.empty:
latest_data = financial_indicators.iloc[0]
financial_data["financial_ratios"] = {
"报告期": latest_data.get('报告期', 'N/A'),
"净资产收益率ROE": latest_data.get('净资产收益率', 'N/A'),
"总资产收益率ROA": latest_data.get('总资产收益率', 'N/A'),
"销售毛利率": latest_data.get('销售毛利率', 'N/A'),
"销售净利率": latest_data.get('销售净利率', 'N/A'),
"资产负债率": latest_data.get('资产负债率', 'N/A'),
"流动比率": latest_data.get('流动比率', 'N/A'),
"速动比率": latest_data.get('速动比率', 'N/A'),
"存货周转率": latest_data.get('存货周转率', 'N/A'),
"应收账款周转率": latest_data.get('应收账款周转率', 'N/A'),
"总资产周转率": latest_data.get('总资产周转率', 'N/A'),
"营业收入同比增长": latest_data.get('营业收入同比增长', 'N/A'),
"净利润同比增长": latest_data.get('净利润同比增长', 'N/A'),
}
except Exception as e:
print(f"获取财务指标失败: {e}")
# 5. 获取季度业绩(使用问财)
try:
# 使用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
except Exception as e:
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 = {
"symbol": symbol,
"balance_sheet": None,
"income_statement": None,
"cash_flow": None,
"financial_ratios": {},
"quarter_data": None,
}
try:
stock = yf.Ticker(symbol)
info = stock.info
# 1. 资产负债表
try:
balance_sheet = stock.balance_sheet
if balance_sheet is not None and not balance_sheet.empty:
financial_data["balance_sheet"] = balance_sheet.iloc[:, :4].to_dict('index')
except Exception as e:
print(f"获取资产负债表失败: {e}")
# 2. 利润表
try:
income_stmt = stock.income_stmt
if income_stmt is not None and not income_stmt.empty:
financial_data["income_statement"] = income_stmt.iloc[:, :4].to_dict('index')
except Exception as e:
print(f"获取利润表失败: {e}")
# 3. 现金流量表
try:
cash_flow = stock.cashflow
if cash_flow is not None and not cash_flow.empty:
financial_data["cash_flow"] = cash_flow.iloc[:, :4].to_dict('index')
except Exception as e:
print(f"获取现金流量表失败: {e}")
# 4. 财务比率(从info中提取)
financial_data["financial_ratios"] = {
"ROE": info.get('returnOnEquity', 'N/A'),
"ROA": info.get('returnOnAssets', 'N/A'),
"毛利率": info.get('grossMargins', 'N/A'),
"营业利润率": info.get('operatingMargins', 'N/A'),
"净利率": info.get('profitMargins', 'N/A'),
"资产负债率": info.get('debtToEquity', 'N/A'),
"流动比率": info.get('currentRatio', 'N/A'),
"速动比率": info.get('quickRatio', 'N/A'),
"EPS": info.get('trailingEps', 'N/A'),
"每股账面价值": info.get('bookValue', 'N/A'),
"股息率": info.get('dividendYield', 'N/A'),
"派息率": info.get('payoutRatio', 'N/A'),
"收入增长": info.get('revenueGrowth', 'N/A'),
"盈利增长": info.get('earningsGrowth', 'N/A'),
}
return financial_data
except Exception as e:
print(f"获取美股财务数据失败: {e}")
return financial_data
def get_fund_flow_data(self, symbol):
"""使用问财获取资金流向数据
Args:
symbol: 股票代码(6位数字)
Returns:
dict: 包含问财原始数据的字典
"""
fund_flow_data = {
"symbol": symbol,
"query_success": False,
"raw_data": None, # 存储原始数据
"data_source": "pywencai"
}
# 只支持中国股票
if not self._is_chinese_stock(symbol):
fund_flow_data["error"] = "问财数据仅支持中国A股股票"
return fund_flow_data
try:
# 构建问句,查询近20个交易日的资金流向数据
query = f"{symbol}近20个交易日区间资金流向、区间主力资金流向、区间涨跌幅"
print(f"正在使用问财查询资金流向数据: {query}")
# 使用pywencai查询
result = pywencai.get(query=query, loop=True)
# 调试:打印result的类型
print(f"问财返回的数据类型: {type(result)}")
# 处理不同类型的返回结果,统一转换为DataFrame
df_result = None
if result is None:
fund_flow_data["error"] = "问财查询返回None"
print(f"问财查询返回None")
elif isinstance(result, dict):
# 如果返回的是字典,转换为DataFrame
print(f"问财返回字典,转换为DataFrame")
try:
df_result = pd.DataFrame([result])
print(f"成功转换,形状: {df_result.shape}")
except Exception as e:
fund_flow_data["error"] = f"无法转换为DataFrame: {str(e)}"
print(f"转换失败: {e}")
elif isinstance(result, pd.DataFrame):
# 如果已经是DataFrame
df_result = result
print(f"问财返回DataFrame,形状: {df_result.shape}")
else:
fund_flow_data["error"] = f"问财返回了未知类型: {type(result)}"
print(f"问财返回未知类型: {type(result)}")
# 如果成功获取到DataFrame
if df_result is not None and not df_result.empty and len(df_result) > 0:
# 打印列名以便调试
print(f"问财返回的列名: {df_result.columns.tolist()}")
# 检查是否是嵌套结构(tableV1字段包含实际数据)
if 'tableV1' in df_result.columns and len(df_result.columns) == 1:
print(f"检测到嵌套结构,提取tableV1中的数据")
table_v1_data = df_result.iloc[0]['tableV1']
# 检查tableV1的类型
print(f"tableV1的类型: {type(table_v1_data)}")
if isinstance(table_v1_data, pd.DataFrame):
# 如果是DataFrame,直接使用
df_result = table_v1_data
print(f"提取后的DataFrame形状: {df_result.shape}")
print(f"提取后的列名: {df_result.columns.tolist()}")
elif isinstance(table_v1_data, list) and len(table_v1_data) > 0:
# 如果是列表,转换为DataFrame
df_result = pd.DataFrame(table_v1_data)
print(f"从列表转换的DataFrame形状: {df_result.shape}")
print(f"从列表转换的列名: {df_result.columns.tolist()}")
else:
fund_flow_data["error"] = f"tableV1数据类型不支持: {type(table_v1_data)}"
print(f"tableV1数据类型不支持: {type(table_v1_data)}")
return fund_flow_data
# 再次检查是否有数据
if df_result is None or df_result.empty or len(df_result) == 0:
fund_flow_data["error"] = "提取后的数据为空"
print(f"提取后的数据为空")
return fund_flow_data
# 获取第一条记录
data = df_result.iloc[0]
# 标记查询成功
fund_flow_data["query_success"] = True
fund_flow_data["stock_name"] = str(data.get('股票简称', data.get('name', 'N/A')))
fund_flow_data["stock_code"] = str(data.get('股票代码', data.get('code', symbol)))
# 将所有数据转换为字典格式(方便AI阅读)
raw_data_dict = {}
for col in df_result.columns:
value = data.get(col)
# 转换为易读的格式
try:
if value is None or (isinstance(value, float) and pd.isna(value)):
raw_data_dict[col] = "N/A"
elif isinstance(value, (int, float)):
# 如果是大数字(可能是金额),转换为亿元
if abs(value) > 100000000:
raw_data_dict[col] = f"{value} ({value/100000000:.2f}亿元)"
else:
raw_data_dict[col] = value
elif isinstance(value, pd.DataFrame):
# 如果值本身是DataFrame,跳过
continue
else:
raw_data_dict[col] = str(value)
except Exception as e:
print(f"处理字段 {col} 时出错: {e}")
raw_data_dict[col] = str(value)
fund_flow_data["raw_data"] = raw_data_dict
fund_flow_data["columns"] = df_result.columns.tolist()
print(f"成功获取 {symbol} 的问财数据,共 {len(raw_data_dict)} 个字段")
else:
fund_flow_data["error"] = "问财查询返回空数据"
print(f"问财查询返回空数据")
except Exception as e:
fund_flow_data["error"] = f"获取资金流向数据失败: {str(e)}"
print(f"获取资金流向数据异常: {e}")
import traceback
traceback.print_exc()
return fund_flow_data
def _safe_convert(self, value):
"""安全地转换数值"""
if value is None or value == '' or (isinstance(value, float) and np.isnan(value)):
return 'N/A'
try:
if isinstance(value, str):
# 移除百分号和逗号
value = value.replace('%', '').replace(',', '')
return float(value)
return value
except:
return value
def _calculate_main_fund_ratio(self, main_fund, total_fund):
"""计算主力资金占比"""
try:
if main_fund != 'N/A' and total_fund != 'N/A' and total_fund != 0:
ratio = (main_fund / total_fund) * 100
return f"{ratio:.2f}%"
except:
pass
return 'N/A'