diff --git a/backend/api/routes/system.py b/backend/api/routes/system.py index 3a9cef3..e9032c9 100644 --- a/backend/api/routes/system.py +++ b/backend/api/routes/system.py @@ -917,14 +917,28 @@ async def list_trading_services(_admin: Dict[str, Any] = Depends(require_system_ "raw": status_all } except Exception as e: - # 如果 supervisorctl status 失败,可能返回非 0 exit code - # 但 _run_supervisorctl 已经处理了 status 命令的 exit 3 (stopped) - # 如果是其他错误,记录日志并返回空列表 + # supervisor 未安装/未运行时(如 unix socket 不存在)避免刷 ERROR,改为 WARNING 并返回友好说明 + err_msg = str(e).strip() + if not err_msg: + err_msg = repr(e) + is_supervisor_unavailable = ( + "no such file" in err_msg.lower() + or "connection refused" in err_msg.lower() + or "sock" in err_msg.lower() + or "unix://" in err_msg.lower() + ) + if is_supervisor_unavailable: + logger.warning(f"列出服务失败(supervisor 未运行或不可用): {err_msg}") + return { + "summary": {"total": 0, "running": 0, "stopped": 0, "unknown": 0}, + "services": [], + "error": "supervisor 未安装或未运行,请检查 supervisord 或配置 SUPERVISOR_CONF" + } logger.error(f"列出服务失败: {e}") return { "summary": {"total": 0, "running": 0, "stopped": 0, "unknown": 0}, "services": [], - "error": str(e) + "error": err_msg } diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index dd25517..7787345 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -2032,30 +2032,41 @@ class BinanceClient: # 优先尝试 WebSocket 下单(减少 REST 超时) if self._ws_trade_client and self._ws_trade_client.is_connected(): try: - # 准备 WS 参数(需包含 apiKey, timestamp, signature) + # 准备 WS 参数(与币安 WS 文档一致:apiKey, timestamp, recvWindow, 其余同 REST) ws_params = dict(params) ws_params["apiKey"] = self.api_key + ts_ms = int(time.time() * 1000) if "timestamp" not in ws_params: - ws_params["timestamp"] = int(time.time() * 1000) - # 计算签名:币安要求参与签名的值格式与 REST 一致(布尔小写、空值不参与或为空串) + ws_params["timestamp"] = ts_ms + else: + ws_params["timestamp"] = int(ws_params["timestamp"]) + # 扩大接收窗口,避免服务器与本地时钟偏差导致签名被拒 + ws_params["recvWindow"] = 60000 + # 币安 WS 文档中 closePosition 为 STRING,发送与签名均用 "true"/"false" + if ws_params.get("closePosition") is True: + ws_params["closePosition"] = "true" + elif ws_params.get("closePosition") is False: + ws_params["closePosition"] = "false" + # 计算签名:参与签名的字符串必须与 REST 一致(键按字母序、值统一为字符串,布尔为 true/false) if "signature" not in ws_params: import hmac import hashlib from urllib.parse import urlencode - def _param_val_for_signature(v): + def _val_for_sig(v): if v is True: return "true" if v is False: return "false" if v is None: return "" - s = str(v).strip() - return s if s else "" - sign_params = [(k, _param_val_for_signature(v)) for k, v in ws_params.items() if k != "signature"] - query_string = urlencode(sorted(sign_params)) + if isinstance(v, (int, float)): + return str(int(v)) if isinstance(v, float) and v == int(v) else str(v) + return str(v).strip() + to_sign = [(k, _val_for_sig(v)) for k, v in ws_params.items() if k != "signature"] + query_string = urlencode(sorted(to_sign)) signature = hmac.new( - self.api_secret.encode('utf-8'), - query_string.encode('utf-8'), + self.api_secret.encode("utf-8"), + query_string.encode("utf-8"), hashlib.sha256 ).hexdigest() ws_params["signature"] = signature @@ -2357,7 +2368,7 @@ class BinanceClient: # 如果还是失败,记录详细参数用于调试 logger.error(f"{symbol} ❌ 所有重试都失败,保护单挂单失败") logger.error(f" 参数: {params}") - logger.error(f" 对冲模式: {dual}") + logger.error(f" 对冲模式: {dual}(None 表示无法读取持仓模式,请检查网络或 API 权限)") return None except BinanceAPIException as e: diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 672c7c8..71c030e 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2383,7 +2383,8 @@ class PositionManager: continue except Exception as e: - logger.error(f"检查止损止盈失败: {e}") + err_msg = str(e).strip() or repr(e) or type(e).__name__ + logger.error(f"检查止损止盈失败: {err_msg}") return closed_positions