This commit is contained in:
aaron 2026-04-23 17:36:07 +08:00
parent c03d5a88e8
commit 23dd333ae4
7 changed files with 57 additions and 22 deletions

View File

@ -61,8 +61,8 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem
# ── 用东方财富 clist API 统计涨停跌停(比腾讯涨停价字段更可靠) ── # ── 用东方财富 clist API 统计涨停跌停(比腾讯涨停价字段更可靠) ──
try: try:
limit_up_count = 0 realtime_limit_up_count = 0
limit_down_count = 0 realtime_limit_down_count = 0
for fs, threshold in [ for fs, threshold in [
("m:0+t:6,m:0+t:80,m:0+t:81+s:2048", 9.9), # 主板 10% ("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: if pct == "-" or pct is None:
continue continue
if float(pct) >= threshold: if float(pct) >= threshold:
limit_up_count += 1 realtime_limit_up_count += 1
# 跌停:按涨幅升序取 top 200 # 跌停:按涨幅升序取 top 200
params_down = { params_down = {
@ -102,8 +102,10 @@ async def intraday_market_temperature(prev_temp: MarketTemperature) -> MarketTem
if pct == "-" or pct is None: if pct == "-" or pct is None:
continue continue
if float(pct) <= neg_threshold: 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}") logger.info(f"东方财富盘中涨跌停: 涨停={limit_up_count} 跌停={limit_down_count}")
except Exception as e: except Exception as e:
logger.warning(f"东方财富涨跌停统计失败,使用基线数据: {e}") logger.warning(f"东方财富涨跌停统计失败,使用基线数据: {e}")

View File

@ -26,6 +26,23 @@ def calculate_market_temperature(trade_date: str = None) -> MarketTemperature:
# 1. 涨跌家数 # 1. 涨跌家数
daily_df = tushare_client.get_daily_all(trade_date) daily_df = tushare_client.get_daily_all(trade_date)
if daily_df.empty: 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) return MarketTemperature(trade_date=trade_date, temperature=50)
up_count = len(daily_df[daily_df["pct_chg"] > 0]) up_count = len(daily_df[daily_df["pct_chg"] > 0])

View File

@ -21,6 +21,12 @@ _scan_lock = asyncio.Lock()
_scan_running = False _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: async def refresh_recommendations(trade_date: str = None, scan_session: str = "manual") -> dict:
"""刷新推荐列表(带扫描锁防止并发)""" """刷新推荐列表(带扫描锁防止并发)"""
global _scan_running global _scan_running
@ -633,23 +639,32 @@ async def _save_to_db(result: dict):
# 保存市场温度 # 保存市场温度
mt = result.get("market_temp") mt = result.get("market_temp")
if mt: if mt:
# 使用 INSERT OR REPLACE 确保重复扫描能更新数据 if _has_valid_market_breadth(mt):
stmt = text( # 使用 INSERT OR REPLACE 确保重复扫描能更新数据
"INSERT OR REPLACE INTO market_temperature " stmt = text(
"(trade_date, up_count, down_count, limit_up_count, limit_down_count, " "INSERT OR REPLACE INTO market_temperature "
"max_streak, broken_rate, temperature) " "(trade_date, up_count, down_count, limit_up_count, limit_down_count, "
"VALUES (:td, :up, :down, :lu, :ld, :ms, :br, :temp)" "max_streak, broken_rate, temperature) "
) "VALUES (:td, :up, :down, :lu, :ld, :ms, :br, :temp)"
await db.execute(stmt, { )
"td": mt.trade_date, await db.execute(stmt, {
"up": mt.up_count, "td": mt.trade_date,
"down": mt.down_count, "up": mt.up_count,
"lu": mt.limit_up_count, "down": mt.down_count,
"ld": mt.limit_down_count, "lu": mt.limit_up_count,
"ms": mt.max_streak, "ld": mt.limit_down_count,
"br": mt.broken_rate, "ms": mt.max_streak,
"temp": mt.temperature, "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 的旧数据,再批量插入)
trade_date_val = mt.trade_date if mt else "" trade_date_val = mt.trade_date if mt else ""
@ -769,7 +784,8 @@ async def _load_today_from_db() -> dict:
result = await db.execute( result = await db.execute(
text( text(
"SELECT * FROM market_temperature " "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() mt_row = result.fetchone()

Binary file not shown.