空跑补一次带签名请求,否则密钥与白名单根本没被验到

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 <cursoragent@cursor.com>
This commit is contained in:
jack
2026-08-28 18:17:53 +08:00
co-authored by Cursor
parent d797adc77e
commit 9562b8c598
3 changed files with 43 additions and 5 deletions
+6 -1
View File
@@ -44,7 +44,12 @@ class Bitget:
dry: bool = False): dry: bool = False):
self.key = key or os.environ.get("BITGET_API_KEY", "") self.key = key or os.environ.get("BITGET_API_KEY", "")
self.secret = secret or os.environ.get("BITGET_API_SECRET", "") 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.dry = dry
self._sess = None self._sess = None
+3 -2
View File
@@ -85,8 +85,9 @@ else
fi fi
echo "── 5. 执行器空跑 ${SECS}s(真连交易所,不下单)──" echo "── 5. 执行器空跑 ${SECS}s(真连交易所,不下单)──"
# --dry-run 下 Bitget 客户端只读不写:合约规则、持仓、费率照查 # --dry-run 下只读不写:下单一律只打印。合约规则那个端点是**公开**的、不验签
# 下单一律只打印。所以这一步会真实验到密钥与白名单 # 所以空跑里专门补了一次带签名的 account() —— 否则这一步会"通过"却根本没测到
# 密钥与 IP 白名单,等第一个真信号来时才暴露,而那时信号正在过期
# 让 chan 自己 source 配置($CONF 是 640 root:chan,它读得到)。 # 让 chan 自己 source 配置($CONF 是 640 root:chan,它读得到)。
# 不用 `env "$(grep ...)"` 那种拼法:值里有空格就会被切开 # 不用 `env "$(grep ...)"` 那种拼法:值里有空格就会被切开
set +e set +e
+34 -2
View File
@@ -258,12 +258,44 @@ class Exec:
# 启动推送兼作"通道通不通"的自检:配错了这里就收不到,而不是等到 # 启动推送兼作"通道通不通"的自检:配错了这里就收不到,而不是等到
# 几小时后第一个真信号来时才发现 # 几小时后第一个真信号来时才发现
await tg.started(NOTIONAL, LEVERAGE, len(SYMS), self.dry) await tg.started(NOTIONAL, LEVERAGE, len(SYMS), self.dry)
print(f" Telegram {'已启用' if tg.ENABLED else '未配置(不推送)'}",
flush=True)
if self.dry: if self.dry:
print(" ⚠ 空跑模式:不下真单", flush=True) 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 return
if not (self.api.key and self.api.secret and self.api.passphrase): if not (self.api.key and self.api.secret and self.api.passphrase):
raise SystemExit("⛔ 缺 BITGET_API_KEY / SECRET / PASSPHRASE。" await self.api.close()
"先跑 --dry-run。") raise SystemExit("⛔ 缺 BITGET_API_KEY / BITGET_API_SECRET / "
"BITGET_PASSPHRASE(末项无 API_)。先跑 --dry-run。")
await self.setup_symbols() await self.setup_symbols()
await self.reconcile() await self.reconcile()