diff --git a/backend/app/analysis/__pycache__/intraday.cpython-313.pyc b/backend/app/analysis/__pycache__/intraday.cpython-313.pyc index 86170558..c434f173 100644 Binary files a/backend/app/analysis/__pycache__/intraday.cpython-313.pyc and b/backend/app/analysis/__pycache__/intraday.cpython-313.pyc differ diff --git a/backend/app/analysis/intraday.py b/backend/app/analysis/intraday.py index 2cb43c3b..2e8c47bf 100644 --- a/backend/app/analysis/intraday.py +++ b/backend/app/analysis/intraday.py @@ -56,8 +56,10 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem down_count = sum(1 for q in eastmoney_quotes if q.get("pct_chg", 0) < 0) limit_up_count = sum(1 for q in eastmoney_quotes if q.get("pct_chg", 0) >= _limit_threshold(q.get("ts_code", ""))) limit_down_count = sum(1 for q in eastmoney_quotes if q.get("pct_chg", 0) <= -_limit_threshold(q.get("ts_code", ""))) + source = eastmoney_quotes[0].get("source", "eastmoney") if eastmoney_quotes else "eastmoney" logger.info( - "东方财富实时市场温度: 上涨=%s 下跌=%s 涨停=%s 跌停=%s (共%s只)", + "%s实时市场温度: 上涨=%s 下跌=%s 涨停=%s 跌停=%s (共%s只)", + "腾讯兜底" if source == "tencent_fallback" else "东方财富", up_count, down_count, limit_up_count, diff --git a/backend/app/data/eastmoney_client.py b/backend/app/data/eastmoney_client.py index a946eccf..fafdd9e0 100644 --- a/backend/app/data/eastmoney_client.py +++ b/backend/app/data/eastmoney_client.py @@ -31,6 +31,13 @@ SECTOR_HEADERS = { } +def _describe_exception(exc: Exception) -> str: + text = str(exc).strip() + if text: + return f"{exc.__class__.__name__}: {text}" + return exc.__class__.__name__ + + def _ts_code_to_eastmoney(ts_code: str) -> str: """600519.SH -> 1.600519 (上海=1, 深圳=0)""" code, market = ts_code.split(".") @@ -209,6 +216,7 @@ async def get_a_share_realtime_ranking( "total_mv": _safe_float(item.get("f20")), "circ_mv": _safe_float(item.get("f21")), "main_net_inflow": _safe_float(item.get("f62")) or 0, + "source": "eastmoney", }) if len(result) == before_count or len(items) < page_size_per_request: break @@ -225,12 +233,79 @@ async def get_a_share_realtime_ranking( logger.info("东方财富A股实时行情: 获取 %s 只", len(result)) return result except Exception as e: - logger.error(f"东方财富A股实时行情获取失败: {e}") - await log_error( - "eastmoney", - f"东方财富A股实时行情获取失败: {e}", - detail=f"sort_by={sort_by}, page_size={page_size}", + reason = _describe_exception(e) + logger.warning("东方财富A股实时行情获取失败,尝试腾讯兜底: %s", reason) + fallback = await _get_a_share_realtime_ranking_from_tencent( + sort_by=sort_by, + descending=descending, + page_size=page_size, ) + if fallback: + ttl = 60 if _is_trading_hours() else 300 + cache.set(cache_key, fallback, ttl) + logger.warning("东方财富A股实时行情获取失败,已切换腾讯兜底: %s", reason) + return fallback + + logger.error("A股实时行情双源获取失败: %s", reason) + await log_error( + "market_data", + f"A股实时行情双源获取失败: {reason}", + detail=f"primary=eastmoney, fallback=tencent, sort_by={sort_by}, page_size={page_size}", + ) + return [] + + +async def _get_a_share_realtime_ranking_from_tencent( + sort_by: str, + descending: bool, + page_size: int, +) -> list[dict]: + """东方财富失败时,用腾讯批量行情兜底全市场快照。""" + try: + from app.data.tushare_client import tushare_client + from app.data.tencent_client import get_realtime_quotes_batch + + stock_basic = tushare_client.get_stock_basic() + if stock_basic.empty: + return [] + + ts_codes = stock_basic["ts_code"].dropna().astype(str).tolist() + quotes = await get_realtime_quotes_batch(ts_codes) + if not quotes: + return [] + + rows = [] + for ts_code, quote in quotes.items(): + if quote.price <= 0: + continue + rows.append({ + "ts_code": ts_code, + "name": quote.name, + "price": quote.price, + "pct_chg": quote.pct_chg, + "amount": quote.amount, + "turnover_rate": quote.turnover_rate, + "pe": quote.pe, + "pb": quote.pb, + "total_mv": quote.total_mv, + "circ_mv": quote.circ_mv, + "main_net_inflow": 0, + "source": "tencent_fallback", + }) + + sort_key_map = { + "f3": "pct_chg", + "f6": "amount", + "f8": "turnover_rate", + "f20": "total_mv", + "f21": "circ_mv", + } + sort_key = sort_key_map.get(sort_by, "pct_chg") + rows.sort(key=lambda item: float(item.get(sort_key, 0) or 0), reverse=descending) + logger.info("腾讯兜底A股实时行情: 获取 %s 只", len(rows)) + return rows[:page_size] + except Exception as e: + logger.warning("腾讯兜底A股实时行情失败: %s", _describe_exception(e)) return [] diff --git a/backend/astock.db b/backend/astock.db index 4c1bde48..789aa797 100644 Binary files a/backend/astock.db and b/backend/astock.db differ