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()