功能细节优化

This commit is contained in:
2026-06-15 01:26:39 +08:00
parent e524a3589a
commit 964c17c200
33 changed files with 6990 additions and 210 deletions

View File

@@ -12,6 +12,7 @@ from functools import wraps
from cachetools import TTLCache
import requests
from redis_cache import cache
try:
import akshare as ak
@@ -25,16 +26,35 @@ _cache = TTLCache(maxsize=256, ttl=30)
def cached(ttl: int):
"""缓存装饰器:优先使用 Redis降级到内存缓存"""
def deco(fn):
local = TTLCache(maxsize=64, ttl=ttl)
@wraps(fn)
def wrapper(*args, **kwargs):
key = (fn.__name__, args, tuple(sorted(kwargs.items())))
if key in local:
return local[key]
# 生成缓存键
key = f"akshare:{fn.__name__}:{args}:{tuple(sorted(kwargs.items()))}"
# 优先从 Redis 读取
if cache.enabled:
cached_value = cache.get(key)
if cached_value is not None:
return cached_value
# Redis 未命中,从内存缓存读取
local_key = (fn.__name__, args, tuple(sorted(kwargs.items())))
if local_key in local:
return local[local_key]
# 执行函数
val = fn(*args, **kwargs)
local[key] = val
# 写入 Redis
if cache.enabled:
cache.set(key, val, expire=ttl)
# 写入内存缓存(降级)
local[local_key] = val
return val
return wrapper
@@ -183,7 +203,19 @@ def get_stock_news(code: str, limit: int = 12):
return {"source": "mock", "list": []}
# 已知指数代码 → 新浪前缀映射
_INDEX_CODES = {"000001", "000300", "000016", "399001", "399006", "899050"}
def _is_index(code: str) -> bool:
return code in _INDEX_CODES or code.startswith(("sh0", "sz3990", "bj8990"))
def _sina_symbol(code: str) -> str:
if code in ("000001", "000016"): # 上证系列
return "sh" + code
if code in ("000300",): # 沪深300
return "sh" + code
if code in ("399001", "399006"): # 深证
return "sz" + code
if code.startswith("6"):
return "sh" + code
if code.startswith(("0", "3")):
@@ -194,9 +226,23 @@ def _sina_symbol(code: str) -> str:
@cached(60)
def get_kline(symbol: str = "600519", days: int = 120):
def get_kline(symbol: str = "000001", days: int = 120):
if AK_OK:
# 主源:新浪日线(更稳定);备源:腾讯
# 指数走专用接口
if symbol in _INDEX_CODES:
try:
sym = _sina_symbol(symbol)
df = ak.stock_zh_index_daily(symbol=sym)
if df is not None and not df.empty:
df = df.tail(days)
dates = [str(d)[5:].replace("-", "/") for d in df["date"]]
ohlc = [[float(r["open"]), float(r["close"]), float(r["low"]), float(r["high"])]
for _, r in df.iterrows()]
vols = [int(r["volume"]) if "volume" in df.columns else 0 for _, r in df.iterrows()]
return {"source": "akshare", "symbol": symbol, "dates": dates, "ohlc": ohlc, "vols": vols}
except Exception:
pass
# 个股主源:新浪日线(更稳定);备源:腾讯
for src in ("sina", "tx"):
try:
sym = _sina_symbol(symbol)
@@ -321,6 +367,90 @@ def get_treemap(mode: str = "sector"):
return {"source": boards["source"], "mode": "sector", "items": items}
@cached(120)
def get_us_treemap():
"""美股热门板块云图按成交额取前100只"""
if AK_OK:
try:
df = ak.stock_us_spot_em()
if df is not None and not df.empty:
top = df.sort_values("成交额", ascending=False).head(100)
items = [{"name": str(r.get("名称","")), "value": round(float(r.get("成交额",0))/1e8, 2),
"pct": round(float(r.get("涨跌幅",0)), 2)} for _, r in top.iterrows()]
items = [x for x in items if x["name"]]
return {"source": "akshare", "market": "us", "items": items}
except Exception:
pass
names = ["苹果","微软","谷歌","亚马逊","英伟达","特斯拉","Meta","台积电","巴菲特","摩根"]
return {"source": "mock", "market": "us",
"items": [{"name": n, "value": _rnd(10,200), "pct": round(_rnd(-4,4),2)} for n in names]}
@cached(120)
def get_hk_treemap():
"""港股热门板块云图按成交额取前100只"""
if AK_OK:
try:
df = ak.stock_hk_spot_em()
if df is not None and not df.empty:
top = df.sort_values("成交额", ascending=False).head(100)
items = [{"name": str(r.get("名称","")), "value": round(float(r.get("成交额",0))/1e4, 2),
"pct": round(float(r.get("涨跌幅",0)), 2)} for _, r in top.iterrows()]
items = [x for x in items if x["name"]]
return {"source": "akshare", "market": "hk", "items": items}
except Exception:
pass
names = ["腾讯","阿里巴巴","美团","京东","小米","百度","网易","中国平安","汇丰","友邦"]
return {"source": "mock", "market": "hk",
"items": [{"name": n, "value": _rnd(5,100), "pct": round(_rnd(-4,4),2)} for n in names]}
@cached(120)
def get_all_sector_leaders(top_n: int = 5):
"""一次性获取所有板块的前N只龙头股"""
boards = get_industry_boards()
result = {}
for b in boards.get("list", []):
name = b["name"]
try:
r = get_sector_stocks(name, top_n + 1)
result[name] = r.get("stocks", [])[:top_n]
except Exception:
result[name] = []
return {"source": "akshare", "sectors": result}
@cached(300)
def get_sector_stocks(sector_name: str, limit: int = 20):
"""获取板块成分股,按成交额排序"""
if AK_OK:
try:
df = ak.stock_board_industry_cons_em(symbol=sector_name)
if df is not None and not df.empty:
if "成交额" in df.columns:
df = df.sort_values("成交额", ascending=False)
stocks = []
for _, r in df.head(limit).iterrows():
try:
stocks.append({
"code": str(r.get("代码", "")),
"name": str(r.get("名称", "")),
"pct": round(float(r.get("涨跌幅", 0)), 2),
"price": round(float(r.get("最新价", 0)), 2),
"amount": round(float(r.get("成交额", 0)) / 1e8, 2),
})
except Exception:
continue
return {"source": "akshare", "name": sector_name, "stocks": stocks}
except Exception:
pass
# mock
stocks = [{"code": f"60000{i}", "name": f"{sector_name}{i+1}",
"pct": round(_rnd(-5, 5), 2), "price": round(_rnd(5, 100), 2), "amount": round(_rnd(1, 50), 2)}
for i in range(10)]
return {"source": "mock", "name": sector_name, "stocks": stocks}
# ============================================================
# 资金流向(行业)
# ============================================================