@@ -39,53 +39,12 @@ def save(updates: dict) -> dict:
|
||||
return current
|
||||
|
||||
|
||||
def get_realtime_quotes_enabled() -> bool:
|
||||
return load().get("realtime_quotes_enabled", False)
|
||||
|
||||
|
||||
def get_indices_nav_pinned() -> bool:
|
||||
"""侧栏指数报价卡片是否固定显示。默认 True(常驻)。
|
||||
关闭后,卡片跟随实时行情开关(仅实时开时显示)。"""
|
||||
return load().get("indices_nav_pinned", True)
|
||||
|
||||
|
||||
def get_realtime_quote_interval() -> float:
|
||||
return load().get("realtime_quote_interval", 10.0)
|
||||
|
||||
|
||||
def get_realtime_watchlist_symbols() -> list[str]:
|
||||
"""Free 档自选实时监控标的:直接取自选页前 5 个。"""
|
||||
try:
|
||||
from app.services import watchlist
|
||||
rows = watchlist.list_symbols()
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning("load watchlist for realtime failed: %s", e)
|
||||
return []
|
||||
out: list[str] = []
|
||||
for row in rows:
|
||||
symbol = str((row or {}).get("symbol") or "").strip().upper()
|
||||
if symbol and symbol not in out:
|
||||
out.append(symbol)
|
||||
if len(out) >= 5:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def set_realtime_watchlist_symbols(symbols: list[str]) -> list[str]: # noqa: ARG001
|
||||
"""兼容旧接口: Free 实时标的现在由自选页前 5 个决定。"""
|
||||
return get_realtime_watchlist_symbols()
|
||||
|
||||
|
||||
def set_realtime_quote_interval(interval: float) -> float:
|
||||
"""保存行情轮询间隔(不在此做 min/max 校验,由调用方按档位限制)。"""
|
||||
current = load()
|
||||
current["realtime_quote_interval"] = interval
|
||||
_path().write_text(
|
||||
json.dumps(current, indent=2, ensure_ascii=False), encoding="utf-8",
|
||||
)
|
||||
return interval
|
||||
|
||||
|
||||
def get_minute_sync_enabled() -> bool:
|
||||
return load().get("minute_sync_enabled", False)
|
||||
|
||||
@@ -116,11 +75,6 @@ def get_minute_data_provider() -> str:
|
||||
return provider if provider in _ALLOWED_DATA_PROVIDERS else "tickflow"
|
||||
|
||||
|
||||
def get_realtime_data_provider() -> str:
|
||||
# 盘中实时现阶段仅支持 TickFlow。
|
||||
return "tickflow"
|
||||
|
||||
|
||||
# ===== 盘后管道拉取内容开关 (A股 / ETF / 指数 独立控制) =====
|
||||
|
||||
def get_pipeline_pull_a_share() -> bool:
|
||||
@@ -227,44 +181,6 @@ def set_index_daily_batch_size(size: int) -> int:
|
||||
return size
|
||||
|
||||
|
||||
# ── 五档盘口 sealed(真假涨停) 配置 ──────────────────────
|
||||
|
||||
def get_limit_ladder_monitor_enabled() -> bool:
|
||||
"""连板梯队 5 档监控开关。关闭时 depth 不轮询(连板梯队降级显示)。"""
|
||||
return load().get("limit_ladder_monitor_enabled", False)
|
||||
|
||||
|
||||
def get_depth_polling_interval() -> float:
|
||||
"""depth 盘中轮询间隔(秒)。默认 20(Pro/Expert 都适用)。"""
|
||||
return float(load().get("depth_polling_interval", 20.0))
|
||||
|
||||
|
||||
def set_depth_polling_interval(interval: float) -> float:
|
||||
"""保存 depth 轮询间隔。套餐范围 clamp 由 depth_service 按档位做。"""
|
||||
interval = max(1.0, min(600.0, float(interval)))
|
||||
save({"depth_polling_interval": interval})
|
||||
return interval
|
||||
|
||||
|
||||
def get_depth_finalize_time() -> dict:
|
||||
"""盘后 sealed 定版时间 {"hour": 15, "minute": 2}。范围 15:01~18:00。"""
|
||||
d = load().get("depth_finalize_time", {"hour": 15, "minute": 2})
|
||||
return {"hour": d.get("hour", 15), "minute": d.get("minute", 2)}
|
||||
|
||||
|
||||
def set_depth_finalize_time(hour: int, minute: int) -> dict:
|
||||
"""保存盘后 sealed 定版时间,强制范围 15:01~18:00。"""
|
||||
h = max(0, min(23, hour))
|
||||
m = max(0, min(59, minute))
|
||||
# 下限 15:01, 上限 18:00
|
||||
if h * 60 + m < 15 * 60 + 1:
|
||||
h, m = 15, 1
|
||||
if h * 60 + m > 18 * 60:
|
||||
h, m = 18, 0
|
||||
save({"depth_finalize_time": {"hour": h, "minute": m}})
|
||||
return {"hour": h, "minute": m}
|
||||
|
||||
|
||||
# 复盘推送可选渠道白名单 (微信等暂未实现, 不在白名单内, 前端仅作占位)
|
||||
# 多选: 不推送 = 空数组, 而非 'none'
|
||||
REVIEW_PUSH_CHANNELS = {"feishu"}
|
||||
@@ -333,86 +249,9 @@ def set_review_push_channels(channels: list[str]) -> list[str]:
|
||||
|
||||
|
||||
|
||||
# ===== 实时监控 =====
|
||||
|
||||
# 页面 SSE 刷新配置: { "watchlist": true, "monitor": true, ... }
|
||||
# 可刷新的页面列表及其默认值
|
||||
SSE_REFRESH_PAGES_DEFAULT = {
|
||||
"watchlist": True,
|
||||
"limit-ladder": False,
|
||||
}
|
||||
|
||||
SIDEBAR_INDEX_SYMBOLS_DEFAULT = ["000001.SH", "399001.SZ", "399006.SZ", "000680.SH"]
|
||||
|
||||
|
||||
# ===== 盘中实时行情范围 (独立于盘后管道范围) =====
|
||||
|
||||
|
||||
def get_realtime_pull_stock() -> bool:
|
||||
return load().get("realtime_pull_stock", True)
|
||||
|
||||
|
||||
def get_realtime_pull_etf() -> bool:
|
||||
# 老用户兼容: ETF 实时默认关闭,避免升级后请求量/写盘量突然增加。
|
||||
return load().get("realtime_pull_etf", False)
|
||||
|
||||
|
||||
def get_realtime_pull_index() -> bool:
|
||||
return load().get("realtime_pull_index", True)
|
||||
|
||||
|
||||
def get_realtime_index_mode() -> str:
|
||||
mode = str(load().get("realtime_index_mode", "core") or "core").lower()
|
||||
return mode if mode in {"core", "all"} else "core"
|
||||
|
||||
|
||||
def get_realtime_index_symbols() -> list[str]:
|
||||
stored = load().get("realtime_index_symbols", SIDEBAR_INDEX_SYMBOLS_DEFAULT)
|
||||
if isinstance(stored, str):
|
||||
import re
|
||||
stored = [s.strip() for s in re.split(r"[,\s]+", stored) if s.strip()]
|
||||
return [str(s) for s in stored if str(s).strip()]
|
||||
|
||||
|
||||
def set_realtime_quote_scope(cfg: dict) -> dict:
|
||||
updates = {}
|
||||
for key in ("realtime_pull_stock", "realtime_pull_etf", "realtime_pull_index"):
|
||||
if key in cfg and cfg[key] is not None:
|
||||
updates[key] = bool(cfg[key])
|
||||
if "realtime_index_mode" in cfg and cfg["realtime_index_mode"] in {"core", "all"}:
|
||||
updates["realtime_index_mode"] = cfg["realtime_index_mode"]
|
||||
if "realtime_index_symbols" in cfg and cfg["realtime_index_symbols"] is not None:
|
||||
updates["realtime_index_symbols"] = cfg["realtime_index_symbols"]
|
||||
if updates:
|
||||
save(updates)
|
||||
return get_realtime_quote_scope()
|
||||
|
||||
|
||||
def get_realtime_quote_scope() -> dict:
|
||||
return {
|
||||
"realtime_pull_stock": get_realtime_pull_stock(),
|
||||
"realtime_pull_etf": get_realtime_pull_etf(),
|
||||
"realtime_pull_index": get_realtime_pull_index(),
|
||||
"realtime_index_mode": get_realtime_index_mode(),
|
||||
"realtime_index_symbols": get_realtime_index_symbols(),
|
||||
}
|
||||
|
||||
|
||||
def get_sse_refresh_pages() -> dict[str, bool]:
|
||||
"""返回每个页面的 SSE 刷新开关。"""
|
||||
stored = load().get("sse_refresh_pages", {})
|
||||
# 合并默认值 (新增页面自动出现)
|
||||
result = dict(SSE_REFRESH_PAGES_DEFAULT)
|
||||
result.update(stored)
|
||||
return result
|
||||
|
||||
|
||||
def set_sse_refresh_pages(pages: dict[str, bool]) -> dict[str, bool]:
|
||||
"""保存页面 SSE 刷新配置。"""
|
||||
save({"sse_refresh_pages": pages})
|
||||
return get_sse_refresh_pages()
|
||||
|
||||
|
||||
def get_sidebar_index_symbols() -> list[str]:
|
||||
"""返回左侧菜单显示的指数代码。"""
|
||||
stored = load().get("sidebar_index_symbols", SIDEBAR_INDEX_SYMBOLS_DEFAULT)
|
||||
@@ -420,22 +259,6 @@ def get_sidebar_index_symbols() -> list[str]:
|
||||
return [s for s in stored if s in allowed]
|
||||
|
||||
|
||||
def get_strategy_monitor_enabled() -> bool:
|
||||
"""策略告警评估总开关。"""
|
||||
return load().get("strategy_monitor_enabled", False)
|
||||
|
||||
|
||||
def get_system_notify_enabled() -> bool:
|
||||
"""系统通知开关 — 开启后监控告警同时推送到操作系统通知中心。"""
|
||||
return load().get("system_notify_enabled", False)
|
||||
|
||||
|
||||
def set_system_notify_enabled(enabled: bool) -> bool:
|
||||
"""保存系统通知开关。"""
|
||||
save({"system_notify_enabled": bool(enabled)})
|
||||
return bool(enabled)
|
||||
|
||||
|
||||
def get_feishu_webhook_url() -> str:
|
||||
"""飞书自定义机器人 Webhook 地址 — 全局共用一处, 所有启用推送的规则都推到这一个群。"""
|
||||
return load().get("feishu_webhook_url", "")
|
||||
@@ -446,73 +269,11 @@ def get_feishu_webhook_secret() -> str:
|
||||
return load().get("feishu_webhook_secret", "")
|
||||
|
||||
|
||||
def set_feishu_webhook_url(url: str) -> str:
|
||||
"""保存飞书 Webhook 地址。传入空串表示清空配置。"""
|
||||
save({"feishu_webhook_url": str(url or "").strip()})
|
||||
return get_feishu_webhook_url()
|
||||
|
||||
|
||||
def set_feishu_webhook_secret(secret: str) -> str:
|
||||
"""保存飞书签名密钥。传入空串表示不验签。"""
|
||||
save({"feishu_webhook_secret": str(secret or "").strip()})
|
||||
return get_feishu_webhook_secret()
|
||||
|
||||
|
||||
def get_webhook_enabled_default() -> bool:
|
||||
"""新建监控规则时是否默认勾选「飞书推送」。
|
||||
|
||||
数据模型当前只有一个 webhook_enabled 布尔 (即飞书), QMT/ptrade 待定。
|
||||
此默认值供规则编辑器新建规则时预填, 单条规则仍可独立修改。
|
||||
"""
|
||||
return load().get("webhook_enabled_default", False)
|
||||
|
||||
|
||||
def set_webhook_enabled_default(enabled: bool) -> bool:
|
||||
"""保存飞书推送默认勾选态。"""
|
||||
save({"webhook_enabled_default": bool(enabled)})
|
||||
return get_webhook_enabled_default()
|
||||
|
||||
|
||||
def get_screener_auto_run() -> bool:
|
||||
"""选股页进入时是否自动运行所有策略 (获取命中数)。默认开。"""
|
||||
return load().get("screener_auto_run", True)
|
||||
|
||||
|
||||
def get_strategy_monitor_ids() -> list[str]:
|
||||
"""返回监控池中的策略 ID。"""
|
||||
return load().get("strategy_monitor_ids", [])
|
||||
|
||||
|
||||
def set_realtime_monitor_config(cfg: dict) -> dict:
|
||||
"""批量更新实时监控配置。"""
|
||||
updates = {}
|
||||
if "sse_refresh_pages" in cfg:
|
||||
updates["sse_refresh_pages"] = cfg["sse_refresh_pages"]
|
||||
if "strategy_monitor_enabled" in cfg:
|
||||
updates["strategy_monitor_enabled"] = cfg["strategy_monitor_enabled"]
|
||||
if "strategy_monitor_ids" in cfg:
|
||||
updates["strategy_monitor_ids"] = cfg["strategy_monitor_ids"]
|
||||
if "sidebar_index_symbols" in cfg:
|
||||
allowed = set(SIDEBAR_INDEX_SYMBOLS_DEFAULT)
|
||||
updates["sidebar_index_symbols"] = [s for s in cfg["sidebar_index_symbols"] if s in allowed]
|
||||
if "screener_auto_run" in cfg:
|
||||
updates["screener_auto_run"] = bool(cfg["screener_auto_run"])
|
||||
if updates:
|
||||
save(updates)
|
||||
return get_realtime_monitor_config()
|
||||
|
||||
|
||||
def get_realtime_monitor_config() -> dict:
|
||||
"""返回完整的实时监控配置。"""
|
||||
return {
|
||||
"sse_refresh_pages": get_sse_refresh_pages(),
|
||||
"strategy_monitor_enabled": get_strategy_monitor_enabled(),
|
||||
"strategy_monitor_ids": get_strategy_monitor_ids(),
|
||||
"sidebar_index_symbols": get_sidebar_index_symbols(),
|
||||
"screener_auto_run": get_screener_auto_run(),
|
||||
}
|
||||
|
||||
|
||||
def get_nav_order() -> list[str]:
|
||||
"""返回左侧菜单的自定义排序(内置页面 path + 扩展分析菜单 id)。"""
|
||||
return load().get("nav_order", [])
|
||||
|
||||
Reference in New Issue
Block a user