diff --git a/backend/app/analysis/__pycache__/intraday.cpython-313.pyc b/backend/app/analysis/__pycache__/intraday.cpython-313.pyc index 666c13d0..5459eee6 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/__pycache__/market_temp.cpython-313.pyc b/backend/app/analysis/__pycache__/market_temp.cpython-313.pyc index fb381c1b..773583a0 100644 Binary files a/backend/app/analysis/__pycache__/market_temp.cpython-313.pyc and b/backend/app/analysis/__pycache__/market_temp.cpython-313.pyc differ diff --git a/backend/app/analysis/intraday.py b/backend/app/analysis/intraday.py index 0a9aebf0..789b1b7f 100644 --- a/backend/app/analysis/intraday.py +++ b/backend/app/analysis/intraday.py @@ -61,8 +61,8 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem # ── 用东方财富 clist API 统计涨停跌停(比腾讯涨停价字段更可靠) ── try: - limit_up_count = 0 - limit_down_count = 0 + realtime_limit_up_count = 0 + realtime_limit_down_count = 0 for fs, threshold in [ ("m:0+t:6,m:0+t:80,m:0+t:81+s:2048", 9.9), # 主板 10% @@ -84,7 +84,7 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem if pct == "-" or pct is None: continue if float(pct) >= threshold: - limit_up_count += 1 + realtime_limit_up_count += 1 # 跌停:按涨幅升序取 top 200 params_down = { @@ -102,8 +102,10 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem if pct == "-" or pct is None: continue if float(pct) <= neg_threshold: - limit_down_count += 1 + realtime_limit_down_count += 1 + limit_up_count = realtime_limit_up_count + limit_down_count = realtime_limit_down_count logger.info(f"东方财富盘中涨跌停: 涨停={limit_up_count} 跌停={limit_down_count}") except Exception as e: logger.warning(f"东方财富涨跌停统计失败,使用基线数据: {e}") diff --git a/backend/app/analysis/market_temp.py b/backend/app/analysis/market_temp.py index 55df9290..e6ee7f5a 100644 --- a/backend/app/analysis/market_temp.py +++ b/backend/app/analysis/market_temp.py @@ -26,6 +26,23 @@ def calculate_market_temperature(trade_date: str = None) -> MarketTemperature: # 1. 涨跌家数 daily_df = tushare_client.get_daily_all(trade_date) if daily_df.empty: + original_trade_date = trade_date + trade_dates = tushare_client.get_trade_dates() + fallback_dates = [d for d in trade_dates if d < trade_date][-5:] + for fallback_date in reversed(fallback_dates): + fallback_df = tushare_client.get_daily_all(fallback_date) + if not fallback_df.empty: + daily_df = fallback_df + trade_date = fallback_date + logger.warning( + "市场温度 %s 日线数据为空,回退到最近有效交易日 %s", + original_trade_date, + fallback_date, + ) + break + + if daily_df.empty: + logger.warning("市场温度 %s 无有效日线数据,返回占位温度", trade_date) return MarketTemperature(trade_date=trade_date, temperature=50) up_count = len(daily_df[daily_df["pct_chg"] > 0]) diff --git a/backend/app/engine/__pycache__/recommender.cpython-313.pyc b/backend/app/engine/__pycache__/recommender.cpython-313.pyc index e27dd600..c6b9611d 100644 Binary files a/backend/app/engine/__pycache__/recommender.cpython-313.pyc and b/backend/app/engine/__pycache__/recommender.cpython-313.pyc differ diff --git a/backend/app/engine/recommender.py b/backend/app/engine/recommender.py index d8a276c5..609faa12 100644 --- a/backend/app/engine/recommender.py +++ b/backend/app/engine/recommender.py @@ -21,6 +21,12 @@ _scan_lock = asyncio.Lock() _scan_running = False +def _has_valid_market_breadth(market_temp: MarketTemperature | None) -> bool: + if not market_temp: + return False + return (market_temp.up_count or 0) + (market_temp.down_count or 0) > 0 + + async def refresh_recommendations(trade_date: str = None, scan_session: str = "manual") -> dict: """刷新推荐列表(带扫描锁防止并发)""" global _scan_running @@ -633,23 +639,32 @@ async def _save_to_db(result: dict): # 保存市场温度 mt = result.get("market_temp") if mt: - # 使用 INSERT OR REPLACE 确保重复扫描能更新数据 - stmt = text( - "INSERT OR REPLACE INTO market_temperature " - "(trade_date, up_count, down_count, limit_up_count, limit_down_count, " - "max_streak, broken_rate, temperature) " - "VALUES (:td, :up, :down, :lu, :ld, :ms, :br, :temp)" - ) - await db.execute(stmt, { - "td": mt.trade_date, - "up": mt.up_count, - "down": mt.down_count, - "lu": mt.limit_up_count, - "ld": mt.limit_down_count, - "ms": mt.max_streak, - "br": mt.broken_rate, - "temp": mt.temperature, - }) + if _has_valid_market_breadth(mt): + # 使用 INSERT OR REPLACE 确保重复扫描能更新数据 + stmt = text( + "INSERT OR REPLACE INTO market_temperature " + "(trade_date, up_count, down_count, limit_up_count, limit_down_count, " + "max_streak, broken_rate, temperature) " + "VALUES (:td, :up, :down, :lu, :ld, :ms, :br, :temp)" + ) + await db.execute(stmt, { + "td": mt.trade_date, + "up": mt.up_count, + "down": mt.down_count, + "lu": mt.limit_up_count, + "ld": mt.limit_down_count, + "ms": mt.max_streak, + "br": mt.broken_rate, + "temp": mt.temperature, + }) + else: + logger.warning( + "跳过无效市场温度快照: trade_date=%s temperature=%s up=%s down=%s", + mt.trade_date, + mt.temperature, + mt.up_count, + mt.down_count, + ) # 保存板块热度(先清除同一 trade_date 的旧数据,再批量插入) trade_date_val = mt.trade_date if mt else "" @@ -769,7 +784,8 @@ async def _load_today_from_db() -> dict: result = await db.execute( text( "SELECT * FROM market_temperature " - "ORDER BY REPLACE(trade_date, '-', '') DESC, id DESC LIMIT 1" + "ORDER BY CASE WHEN COALESCE(up_count, 0) + COALESCE(down_count, 0) > 0 THEN 0 ELSE 1 END, " + "REPLACE(trade_date, '-', '') DESC, id DESC LIMIT 1" ) ) mt_row = result.fetchone() diff --git a/backend/astock.db b/backend/astock.db index 6c8e9bca..e72a35bc 100644 Binary files a/backend/astock.db and b/backend/astock.db differ