fix: CTP 连接捕获 SimNow 真实报错并增加诊断脚本

显式设置柜台环境=实盘;连接失败时解析 4097/登录拒单;scripts/test_simnow.py 供服务器排查。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-24 11:20:30 +08:00
parent 8726760b12
commit 28d6ae52b2
5 changed files with 353 additions and 20 deletions
+58 -15
View File
@@ -22,7 +22,7 @@ def _env(key: str, default: str = "") -> str:
def _simnow_setting() -> dict[str, str]:
"""SimNow 7×24 仿真默认前置(可在 .env 覆盖)。"""
"""SimNow 仿真前置(可在 .env 覆盖)。看穿式前置需「柜台环境=实盘」。"""
return {
"用户名": _env("SIMNOW_USER"),
"密码": _env("SIMNOW_PASSWORD"),
@@ -31,7 +31,7 @@ def _simnow_setting() -> dict[str, str]:
"行情服务器": _env("SIMNOW_MD_ADDRESS", "tcp://180.168.146.187:10211"),
"产品名称": _env("SIMNOW_APP_ID", "simnow_client_test"),
"授权编码": _env("SIMNOW_AUTH_CODE", "0000000000000000"),
"产品信息": _env("SIMNOW_PRODUCT_INFO", "simnow_client_test"),
"柜台环境": _env("SIMNOW_ENV", "实盘"),
}
@@ -44,7 +44,7 @@ def _live_setting() -> dict[str, str]:
"行情服务器": _env("CTP_LIVE_MD_ADDRESS"),
"产品名称": _env("CTP_LIVE_APP_ID"),
"授权编码": _env("CTP_LIVE_AUTH_CODE"),
"产品信息": _env("CTP_LIVE_PRODUCT_INFO"),
"柜台环境": _env("CTP_LIVE_ENV", "实盘"),
}
@@ -56,6 +56,25 @@ def _mode_label(mode: str) -> str:
return "SimNow 模拟" if mode == "simulation" else "期货公司实盘"
def _format_ctp_failure(ctp_logs: list[str]) -> str:
"""根据 CTP 网关日志拼出可读错误。"""
text = "\n".join(ctp_logs)
if "4097" in text or "Decrypt handshake" in text or "shake hand" in text.lower():
return (
"CTP 握手失败(4097)vnpy_ctp 与 SimNow 前置加密不匹配。"
"请执行 pip install -U vnpy vnpy_ctp 后重启,并确认 .env 中 SIMNOW_ENV=实盘"
)
if "不合法的登录" in text or "密码" in text or "账号" in text:
tail = ctp_logs[-1] if ctp_logs else ""
return f"CTP 登录被拒:{tail or '请检查投资者代码与密码(快期能否登录)'}"
if "连接断开" in text or "disconnect" in text.lower():
tail = ctp_logs[-1] if ctp_logs else ""
return f"CTP 连接断开:{tail or '请检查前置地址与网络'}"
if ctp_logs:
return f"CTP 连接失败:{ctp_logs[-1]}"
return "CTP 连接超时:未收到柜台回报。请检查 SimNow 账号、前置地址、网络(nc 测端口),并用快期验证账号"
class CtpBridge:
def __init__(self) -> None:
self._engine = None
@@ -127,18 +146,42 @@ class CtpBridge:
self._connected_mode = None
time.sleep(1)
self._engine.connect(setting, GATEWAY_NAME)
# 等待登录与结算信息
for _ in range(30):
accounts = self._engine.get_all_accounts()
if accounts:
self._connected_mode = mode
self._last_error = ""
logger.info("CTP 已连接 [%s] account=%s", mode, len(accounts))
return
time.sleep(0.5)
self._last_error = "CTP 连接超时,请检查 SimNow 账号、前置地址与交易时段"
raise RuntimeError(self._last_error)
ctp_logs: list[str] = []
from vnpy.trader.event import EVENT_LOG
def _on_log(event) -> None:
msg = getattr(event.data, "msg", "") or str(event.data)
if msg:
ctp_logs.append(str(msg))
if len(ctp_logs) > 20:
ctp_logs.pop(0)
logger.info("CTP | %s", msg)
self._ee.register(EVENT_LOG, _on_log)
try:
logger.info(
"CTP 连接 [%s] user=%s td=%s env=%s",
mode,
setting.get("用户名"),
setting.get("交易服务器"),
setting.get("柜台环境", "实盘"),
)
self._engine.connect(setting, GATEWAY_NAME)
# 等待登录与结算信息(最多约 30 秒)
for _ in range(60):
accounts = self._engine.get_all_accounts()
if accounts:
self._connected_mode = mode
self._last_error = ""
logger.info("CTP 已连接 [%s] account=%s", mode, len(accounts))
return
time.sleep(0.5)
finally:
self._ee.unregister(EVENT_LOG, _on_log)
hint = _format_ctp_failure(ctp_logs)
self._last_error = hint
raise RuntimeError(hint)
def ensure_connected(self, mode: str) -> None:
if self._connected_mode != mode: