This commit is contained in:
2026-09-09 11:35:02 +08:00
parent bc1c72d558
commit d656c05b3d
35 changed files with 711 additions and 3194 deletions

View File

@@ -17,7 +17,6 @@
from __future__ import annotations
import asyncio
import math
import time
from datetime import date, datetime, timedelta
@@ -26,6 +25,7 @@ import pandas as pd
from .. import cache
from ..config import settings
from .sync_utils import d8_iso, f_clean
# (tushare代码, 名称, 地区, 腾讯符号) —— 展示顺序即列表顺序
# 首页聚焦中美(港股/国际指数在 /indexes 国际指数页标普500 腾讯符号是 s_usINX不是 s_usSPX
@@ -57,22 +57,6 @@ class MarketOverviewError(RuntimeError):
"""所有指数都拉不到token/网络故障)——接口层转 503。"""
def _f(v) -> float | None:
"""pandas 值 -> floatNaN/None -> None否则 JSON 里会出现 NaN"""
if v is None:
return None
try:
f = float(v)
except (TypeError, ValueError):
return None
return None if math.isnan(f) else f
def _d(v) -> str | None:
"""YYYYMMDD -> 'YYYY-MM-DD'(字符串便于 JSON 缓存pydantic 响应模型自动 coerce"""
return datetime.strptime(str(v), "%Y%m%d").date().isoformat() if v else None
def _get_pro():
if not settings.tushare_token:
raise MarketOverviewError("未配置 TUSHARE_TOKEN无法获取大盘行情backend/.env")
@@ -165,10 +149,10 @@ def _quote_from_df(df: pd.DataFrame) -> dict | None:
tail = df.tail(_SPARK_DAYS)
last = df.iloc[-1]
return {
"close": _f(last["close"]),
"change": _f(last.get("change")),
"pct_chg": _f(last.get("pct_chg")),
"trade_date": _d(last["trade_date"]),
"close": f_clean(last["close"]),
"change": f_clean(last.get("change")),
"pct_chg": f_clean(last.get("pct_chg")),
"trade_date": d8_iso(last["trade_date"]),
"spark": [round(float(c), 4) for c in tail["close"]],
"spark_dates": [str(d) for d in tail["trade_date"]],
}
@@ -193,10 +177,10 @@ def _fetch_stats_sync(pro) -> dict | None:
if sh_m is None or sz_m is None:
return None
# 两边各自取最新,日期不一致时以较旧一天为准凑齐口径(罕见,通常同日)
d = min(_d(sh_m["trade_date"]), _d(sz_m["trade_date"]))
d = min(d8_iso(sh_m["trade_date"]), d8_iso(sz_m["trade_date"]))
def _sum(col: str) -> float | None:
a, b = _f(sh_m.get(col)), _f(sz_m.get(col))
a, b = f_clean(sh_m.get(col)), f_clean(sz_m.get(col))
return None if a is None or b is None else round(a + b, 2)
return {
@@ -204,7 +188,7 @@ def _fetch_stats_sync(pro) -> dict | None:
"total_mv": _sum("total_mv"),
"float_mv": _sum("float_mv"),
"amount": _sum("amount"),
"turnover": _f(sh_m.get("tr")), # 换手率仅沪市有,展示口径注明沪市
"turnover": f_clean(sh_m.get("tr")), # 换手率仅沪市有,展示口径注明沪市
}
@@ -222,7 +206,7 @@ def _fetch_amount_history_sync(pro) -> list[dict]:
if len(common) == 0:
return []
total = (sh_m[common] + sz_m[common]).sort_index()
return [{"date": _d(d), "amount": round(float(v), 2)} for d, v in total.tail(_AMOUNT_HIST_BARS).items()]
return [{"date": d8_iso(d), "amount": round(float(v), 2)} for d, v in total.tail(_AMOUNT_HIST_BARS).items()]
# ---- EOD 的 SWRstale-while-revalidate新鲜期内直返过期先返旧值后台刷新 ----