From 9562b8c598eaca32460b7ed2dc0108013772f39d Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 28 Aug 2026 18:17:53 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A9=BA=E8=B7=91=E8=A1=A5=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E5=B8=A6=E7=AD=BE=E5=90=8D=E8=AF=B7=E6=B1=82=EF=BC=8C=E5=90=A6?= =?UTF-8?q?=E5=88=99=E5=AF=86=E9=92=A5=E4=B8=8E=E7=99=BD=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E6=A0=B9=E6=9C=AC=E6=B2=A1=E8=A2=AB=E9=AA=8C=E5=88=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dryrun 第 5 步声称"会真实验到密钥与白名单",但 start() 在空跑下于密钥检查 之前就 return,全程只发了 contracts() —— 那是公开端点,不验签。于是空跑会 "通过"却什么都没测到。这种假保证比不测更糟:等第一个真信号来时才暴露,而 信号那时正在过期,没有从容排查的余地。 现在空跑返回前发一次 account()(只读),失败即退出并给出码表(40018 白名单 / 40037 key 不存在 / 40001,40009 secret,passphrase / 40099 权限)。顺带报 可用余额,并在不足 MAX_OPEN 笔并发所需保证金时告警。 两处 SystemExit 会绕过 run() 的收尾,退出前不关 aiohttp 会话,日志尾部一串 Unclosed client session 会把真正的报错顶出视野。都补上了 close()。 BITGET_PASSPHRASE 现在也接受 BITGET_API_PASSPHRASE:另两项都带 API_,只有 它不带,很容易顺手写错,而后果是"像是填了"但签名一直失败。我自己就踩了。 启动时打一行 Telegram 是否启用。配错 token 的表现原本是完全静默。 Co-authored-by: Cursor --- live/bitget_rest.py | 7 ++++++- live/deploy/dryrun.sh | 5 +++-- live/live_exec.py | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/live/bitget_rest.py b/live/bitget_rest.py index ca86f2c..5d96356 100644 --- a/live/bitget_rest.py +++ b/live/bitget_rest.py @@ -44,7 +44,12 @@ class Bitget: dry: bool = False): self.key = key or os.environ.get("BITGET_API_KEY", "") self.secret = secret or os.environ.get("BITGET_API_SECRET", "") - self.passphrase = passphrase or os.environ.get("BITGET_PASSPHRASE", "") + # 两个名字都收:另外两项是 BITGET_API_KEY / BITGET_API_SECRET, + # 这一项却没有 API_,很容易顺手写成 BITGET_API_PASSPHRASE。写错的 + # 后果是"密钥像是填了"但签名一直失败,排查起来很绕 + self.passphrase = (passphrase + or os.environ.get("BITGET_PASSPHRASE", "") + or os.environ.get("BITGET_API_PASSPHRASE", "")) self.dry = dry self._sess = None diff --git a/live/deploy/dryrun.sh b/live/deploy/dryrun.sh index 9cdde39..1ecec60 100755 --- a/live/deploy/dryrun.sh +++ b/live/deploy/dryrun.sh @@ -85,8 +85,9 @@ else fi echo "── 5. 执行器空跑 ${SECS}s(真连交易所,不下单)──" -# --dry-run 下 Bitget 客户端只读不写:合约规则、持仓、费率照查, -# 下单一律只打印。所以这一步会真实验到密钥与白名单 +# --dry-run 下只读不写:下单一律只打印。合约规则那个端点是**公开**的、不验签, +# 所以空跑里专门补了一次带签名的 account() —— 否则这一步会"通过"却根本没测到 +# 密钥与 IP 白名单,等第一个真信号来时才暴露,而那时信号正在过期 # 让 chan 自己 source 配置($CONF 是 640 root:chan,它读得到)。 # 不用 `env "$(grep ...)"` 那种拼法:值里有空格就会被切开 set +e diff --git a/live/live_exec.py b/live/live_exec.py index 1867784..9d5dc44 100644 --- a/live/live_exec.py +++ b/live/live_exec.py @@ -258,12 +258,44 @@ class Exec: # 启动推送兼作"通道通不通"的自检:配错了这里就收不到,而不是等到 # 几小时后第一个真信号来时才发现 await tg.started(NOTIONAL, LEVERAGE, len(SYMS), self.dry) + print(f" Telegram {'已启用' if tg.ENABLED else '未配置(不推送)'}", + flush=True) if self.dry: print(" ⚠ 空跑模式:不下真单", flush=True) + # 但仍要发一次**带签名**的请求。上面的 contracts() 是公开端点, + # 不验签,光靠它空跑会"通过"却根本没测到密钥与 IP 白名单—— + # 那种假保证比不测更糟:等到第一个真信号来时才暴露,而信号那时 + # 正在过期,没有从容排查的余地 + if self.api.key and self.api.secret and self.api.passphrase: + try: + acc = await self.api.account() + av = float((acc or {}).get("available") or 0) + print(f" ✓ 密钥与 IP 白名单通(可用 {av:.2f} " + f"USDT,名义 {NOTIONAL:.0f} 需保证金约 " + f"{NOTIONAL / LEVERAGE:.0f})", flush=True) + need = NOTIONAL / LEVERAGE * MAX_OPEN + if av < need: + print(f" ⚠ 可用不足:{MAX_OPEN} 笔并发共需约 " + f"{need:.0f} USDT 保证金", flush=True) + except Exception as e: # noqa: BLE001 + # 退出前要关会话。SystemExit 会绕过 run() 里的收尾, + # 漏了就在日志尾部留一串 "Unclosed client session", + # 把真正的报错顶出视野 + await self.api.close() + raise SystemExit( + f"⛔ 带签名的请求失败:{type(e).__name__}: {e}\n" + f" 40018 = 出口 IP 不在该 key 的白名单里;\n" + f" 40037 = key 不存在(填错或已删);\n" + f" 40001/40009 = secret/passphrase 不对;\n" + f" 40099 之类 = 权限没开够(要读+交易)。") + else: + print(" ⚠ 没填密钥,本次**未**验证密钥与 IP 白名单", + flush=True) return if not (self.api.key and self.api.secret and self.api.passphrase): - raise SystemExit("⛔ 缺 BITGET_API_KEY / SECRET / PASSPHRASE。" - "先跑 --dry-run。") + await self.api.close() + raise SystemExit("⛔ 缺 BITGET_API_KEY / BITGET_API_SECRET / " + "BITGET_PASSPHRASE(末项无 API_)。先跑 --dry-run。") await self.setup_symbols() await self.reconcile()