diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 6c611ed..cdfe660 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -467,6 +467,8 @@ from lib.exchange.gate_ccxt_lib import gate_ccxt_class # Gate.io USDT 永续(swap) exchange = gate_ccxt_class()({ "enableRateLimit": True, + # 避免关键位监控/账户拉取无限挂起拖垮中控 + "timeout": int(os.getenv("GATE_CCXT_TIMEOUT_MS", "8000")), "options": { "defaultType": "swap", "defaultMarginMode": _GATE_DEFAULT_MARGIN_MODE, @@ -2832,13 +2834,13 @@ def friendly_exchange_error(err, available_usdt=None): return f"交易所下单失败:{clean}" -def get_exchange_capitals(force=False): - ok_live, _ = ensure_exchange_live_ready() - if not ok_live: - return None, None +_BALANCE_REFRESH_LOCK = threading.Lock() +_BALANCE_REFRESH_INFLIGHT = False + + +def _fetch_exchange_capitals_sync(): + """同步拉取资金/交易账户余额并写入缓存(会占用 ccxt,勿在中控热路径直接调用).""" now_ts = time.time() - if (not force) and ACCOUNT_BALANCE_CACHE["updated_at"] and now_ts - ACCOUNT_BALANCE_CACHE["updated_at"] < BALANCE_REFRESH_SECONDS: - return ACCOUNT_BALANCE_CACHE["funding_usdt"], ACCOUNT_BALANCE_CACHE["trading_usdt"] try: ACCOUNT_BALANCE_CACHE["funding_usdt"] = _fetch_gate_funding_usdt() except Exception: @@ -2852,6 +2854,43 @@ def get_exchange_capitals(force=False): return ACCOUNT_BALANCE_CACHE["funding_usdt"], ACCOUNT_BALANCE_CACHE["trading_usdt"] +def _kick_exchange_capitals_refresh(): + """后台刷新余额;与 key_rs 争用时不阻塞看板/中控请求.""" + global _BALANCE_REFRESH_INFLIGHT + with _BALANCE_REFRESH_LOCK: + if _BALANCE_REFRESH_INFLIGHT: + return + _BALANCE_REFRESH_INFLIGHT = True + + def _worker(): + global _BALANCE_REFRESH_INFLIGHT + try: + _fetch_exchange_capitals_sync() + except Exception: + pass + finally: + with _BALANCE_REFRESH_LOCK: + _BALANCE_REFRESH_INFLIGHT = False + + threading.Thread(target=_worker, name="gate-balance-refresh", daemon=True).start() + + +def get_exchange_capitals(force=False): + ok_live, _ = ensure_exchange_live_ready() + if not ok_live: + return None, None + now_ts = time.time() + has_cache = bool(ACCOUNT_BALANCE_CACHE["updated_at"]) + fresh = has_cache and (now_ts - ACCOUNT_BALANCE_CACHE["updated_at"] < BALANCE_REFRESH_SECONDS) + if (not force) and fresh: + return ACCOUNT_BALANCE_CACHE["funding_usdt"], ACCOUNT_BALANCE_CACHE["trading_usdt"] + if not force: + # 过期/冷缓存:先返回现有值(可空),后台刷新,避免 /api/hub/account 被 RS 监控拖死 + _kick_exchange_capitals_refresh() + return ACCOUNT_BALANCE_CACHE["funding_usdt"], ACCOUNT_BALANCE_CACHE["trading_usdt"] + return _fetch_exchange_capitals_sync() + + def execute_transfer_usdt(amount, from_account, to_account): from lib.exchange.gate_transfer_lib import execute_transfer_usdt as _gate_execute_transfer_usdt @@ -4611,14 +4650,25 @@ def _finalize_key_monitor_one_shot(conn, row, last_msg, close_reason): conn.execute("DELETE FROM key_monitors WHERE id=?", (row["id"],)) +_RS_BAR_CACHE: dict[str, dict] = {} +_RS_BAR_CACHE_TTL_SEC = float(os.getenv("GATE_RS_BAR_CACHE_SEC", "45")) + + def _fetch_last_closed_bar(symbol): - """最近一根闭合 K:[ts, o, h, l, c, v] 或 None.""" + """最近一根闭合 K:[ts, o, h, l, c, v] 或 None.短缓存减轻关键位监控打爆 ccxt.""" ex_sym = normalize_exchange_symbol(symbol) + now = time.time() + cached = _RS_BAR_CACHE.get(ex_sym) + if cached and now - float(cached.get("updated_at") or 0) < _RS_BAR_CACHE_TTL_SEC: + return cached.get("bar") bars = exchange.fetch_ohlcv(ex_sym, timeframe=KLINE_TIMEFRAME, limit=5) or [] if len(bars) < 2: + _RS_BAR_CACHE[ex_sym] = {"updated_at": now, "bar": None} return None closed = bars[:-1] - return closed[-1] if closed else None + bar = closed[-1] if closed else None + _RS_BAR_CACHE[ex_sym] = {"updated_at": now, "bar": bar} + return bar def _key_rs_gate_preview(symbol, upper, lower): @@ -9893,14 +9943,14 @@ def _hub_meta_bundle(): def _hub_account_bundle(): - funding_capital, trading_capital = get_exchange_capitals(force=True) + # 中控看板高频拉取:仅走余额缓存;不再额外 fetch_balance(会与关键位监控争用 ccxt) + funding_capital, trading_capital = get_exchange_capitals(force=False) funding_usdt = round(funding_capital, 2) if funding_capital is not None else None trading_usdt = round(trading_capital, 2) if trading_capital is not None else None - available = get_available_trading_usdt() return { "funding_usdt": funding_usdt, "trading_usdt": trading_usdt, - "available_trading_usdt": round(available, 2) if available is not None else None, + "available_trading_usdt": trading_usdt, "trading_day": get_trading_day(app_now()), }