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: # 处理中国A股 if self._is_chinese_stock(symbol): return self._get_chinese_stock_info(symbol) # 处理港股 elif self._is_hk_stock(symbol): return self._get_hk_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) elif self._is_hk_stock(symbol): return self._get_hk_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): """判断是否为中国A股""" # 简单判断:包含数字且长度为6位的认为是中国A股 return symbol.isdigit() and len(symbol) == 6 def _is_hk_stock(self, symbol): """判断是否为港股""" # 港股代码通常是1-5位数字,或者前面带HK/hk前缀 if symbol.upper().startswith('HK'): return True # 纯数字且长度在1-5位之间,认为可能是港股 if symbol.isdigit() and 1 <= len(symbol) <= 5: return True return False def _normalize_hk_code(self, symbol): """规范化港股代码为5位格式(如700 -> 00700)""" # 移除HK前缀 if symbol.upper().startswith('HK'): symbol = symbol[2:] # 补齐到5位 return symbol.zfill(5) 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_hk_stock_info(self, symbol): """获取港股基本信息""" try: # 规范化港股代码 hk_code = self._normalize_hk_code(symbol) # 初始化基本信息 info = { "symbol": hk_code, "name": "未知", "current_price": "N/A", "change_percent": "N/A", "pe_ratio": "N/A", "pb_ratio": "N/A", "market_cap": "N/A", "market": "香港股市", "exchange": "香港交易所" } # 方法1: 获取港股实时行情 try: # 使用akshare获取港股实时数据 realtime_df = ak.stock_hk_spot_em() if realtime_df is not None and not realtime_df.empty: # 查找对应股票 stock_data = realtime_df[realtime_df['代码'] == hk_code] if not stock_data.empty: row = stock_data.iloc[0] info['name'] = row.get('名称', '未知') info['current_price'] = row.get('最新价', 'N/A') info['change_percent'] = row.get('涨跌幅', 'N/A') # 市值(港元) market_cap = row.get('总市值', 'N/A') if market_cap != 'N/A': try: info['market_cap'] = float(market_cap) except: pass # 市盈率 pe = row.get('市盈率', 'N/A') if pe != 'N/A' and pe != '-': try: pe_val = float(pe) if 0 < pe_val <= 1000: info['pe_ratio'] = pe_val except: pass except Exception as e: print(f"获取港股实时数据失败: {e}") # 方法2: 尝试使用历史数据获取价格信息 if info['current_price'] == 'N/A': try: hist_df = ak.stock_hk_hist(symbol=hk_code, 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_df is not None and not hist_df.empty: latest = hist_df.iloc[-1] info['current_price'] = latest['收盘'] # 计算涨跌幅 if len(hist_df) > 1: prev_close = hist_df.iloc[-2]['收盘'] change_pct = ((latest['收盘'] - prev_close) / prev_close) * 100 info['change_percent'] = round(change_pct, 2) 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": "香港股市", "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_hk_stock_data(self, symbol, period="1y"): """获取港股历史数据""" try: # 规范化港股代码 hk_code = self._normalize_hk_code(symbol) # 计算日期范围 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') elif period == "1mo": start_date = (datetime.now() - timedelta(days=30)).strftime('%Y%m%d') else: start_date = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d') # 获取港股历史数据 df = ak.stock_hk_hist(symbol=hk_code, 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) elif self._is_hk_stock(symbol): return self._get_hk_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_hk_financial_data(self, symbol): """获取港股财务数据""" hk_code = self._normalize_hk_code(symbol) financial_data = { "symbol": hk_code, "balance_sheet": None, "income_statement": None, "cash_flow": None, "financial_ratios": {}, "quarter_data": None, "data_source": "eastmoney", "note": "港股财务数据来自东方财富" } try: # 使用akshare获取港股财务指标(东方财富数据源) print(f"正在获取港股 {hk_code} 的财务指标...") try: financial_indicator = ak.stock_hk_financial_indicator_em(symbol=hk_code) if financial_indicator is not None and not financial_indicator.empty: # 将财务指标数据转换为字典 indicator_dict = financial_indicator.iloc[0].to_dict() # 整理财务比率数据 financial_data["financial_ratios"] = { "基本每股收益": self._safe_convert(indicator_dict.get('基本每股收益(元)', 'N/A')), "每股净资产": self._safe_convert(indicator_dict.get('每股净资产(元)', 'N/A')), "每股股息TTM": self._safe_convert(indicator_dict.get('每股股息TTM(港元)', 'N/A')), "派息比率": self._safe_convert(indicator_dict.get('派息比率(%)', 'N/A')), "每股经营现金流": self._safe_convert(indicator_dict.get('每股经营现金流(元)', 'N/A')), "股息率TTM": self._safe_convert(indicator_dict.get('股息率TTM(%)', 'N/A')), "总市值": self._safe_convert(indicator_dict.get('总市值(港元)', 'N/A')), "港股市值": self._safe_convert(indicator_dict.get('港股市值(港元)', 'N/A')), "营业总收入": self._safe_convert(indicator_dict.get('营业总收入', 'N/A')), "营业收入环比增长": self._safe_convert(indicator_dict.get('营业总收入滚动环比增长(%)', 'N/A')), "销售净利率": self._safe_convert(indicator_dict.get('销售净利率(%)', 'N/A')), "净利润": self._safe_convert(indicator_dict.get('净利润', 'N/A')), "净利润环比增长": self._safe_convert(indicator_dict.get('净利润滚动环比增长(%)', 'N/A')), "ROE股东权益回报率": self._safe_convert(indicator_dict.get('股东权益回报率(%)', 'N/A')), "市盈率": self._safe_convert(indicator_dict.get('市盈率', 'N/A')), "市净率": self._safe_convert(indicator_dict.get('市净率', 'N/A')), "ROA总资产回报率": self._safe_convert(indicator_dict.get('总资产回报率(%)', 'N/A')), "法定股本": self._safe_convert(indicator_dict.get('法定股本(股)', 'N/A')), "已发行股本": self._safe_convert(indicator_dict.get('已发行股本(股)', 'N/A')), "每手股": self._safe_convert(indicator_dict.get('每手股', 'N/A')), } print(f"✅ 成功获取港股 {hk_code} 的财务指标") print(f" ROE: {financial_data['financial_ratios']['ROE股东权益回报率']}") print(f" 市盈率: {financial_data['financial_ratios']['市盈率']}") print(f" 市净率: {financial_data['financial_ratios']['市净率']}") else: print(f"⚠️ 未获取到港股 {hk_code} 的财务指标数据") financial_data["note"] = "未获取到财务数据" except Exception as e: print(f"⚠️ 获取港股财务指标失败: {e}") financial_data["note"] = f"获取财务数据失败: {str(e)}" return financial_data except Exception as e: print(f"获取港股财务数据异常: {e}") financial_data["note"] = f"获取失败: {str(e)}" return financial_data 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'