功能细节优化
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 资金流向(行业)
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user