diff --git a/live/deploy/status.sh b/live/deploy/status.sh index 17e2a7b..a75f74e 100755 --- a/live/deploy/status.sh +++ b/live/deploy/status.sh @@ -67,6 +67,19 @@ fi hr "最近成交" if [[ -s "$STATE/live_trades.jsonl" ]]; then tail -5 "$STATE/live_trades.jsonl" | sed 's/^/ /' + # 「下过单但一次都没建上」是明确的故障,而它的外在表现和"没信号"一样, + # 不主动判读就会白跑几小时。首日就是这么丢掉 4 个信号的 + nf="$(grep -c '"ev": "entry_fail"' "$STATE/live_trades.jsonl" || true)" + nb="$(grep -c '"ev": "opened", "key": [^]]*\[{' "$STATE/live_trades.jsonl" || true)" + ne="$(grep -c '"ev": "entry"' "$STATE/live_trades.jsonl" || true)" + if [[ "${nf:-0}" -gt 0 ]]; then + echo + echo " ⛔ 有 $nf 次入场被拒(共尝试 $ne 个信号)" + echo " 最后一条错误:" + grep '"ev": "entry_fail"' "$STATE/live_trades.jsonl" | tail -1 \ + | sed 's/^/ /' + echo " 40774 = 持仓模式不匹配 · 40762/40786 = 保证金不足" + fi else echo " 还没有成交记录" fi diff --git a/live/live_exec.py b/live/live_exec.py index 474b885..62f8da3 100644 --- a/live/live_exec.py +++ b/live/live_exec.py @@ -254,6 +254,9 @@ class Exec: self.execs: dict = {} # key → 该笔的腿与超时时刻 self.offset = 0 # 已读到总线的哪一行 self.n_seen = self.n_took = self.n_skip = 0 + # 建仓成功/失败分开计。只看 n_took 分不出"做了但下单被拒"——首日 + # 那 5 小时里 n_took=4 而实际一笔都没建上 + self.n_built = self.n_order_fail = 0 # ── 启动 ────────────────────────────────────────────────────── async def start(self) -> None: @@ -574,7 +577,7 @@ class Exec: hold = "long" if d > 0 else "short" stop_px = self.snap(sym, entry - d * SL_ATR * a) - opened = [] + opened, failed = [], [] for x in lg: oid = oid_of(r["key"], x["tag"]) try: @@ -583,6 +586,7 @@ class Exec: print(f" ⛔ {x['tag']} 入场失败 {e}", flush=True) log_trade({"ev": "entry_fail", "key": r["key"], "tag": x["tag"], "err": str(e)}) + failed.append(f"{x['tag']} 入场:{e}") continue tp_px = self.snap(sym, entry + d * x["atr"] * a) try: @@ -593,6 +597,9 @@ class Exec: # 超时腿会兜住它,所以只告警不强平 print(f" ⚠ {x['tag']} 止盈挂单失败 {e}" f"(仓位有服务端止损,超时腿会兜)", flush=True) + log_trade({"ev": "tp_fail", "key": r["key"], + "tag": x["tag"], "err": str(e)}) + failed.append(f"{x['tag']} 止盈:{e}") opened.append({"tag": x["tag"], "oid": oid, "size": half, "tp_px": tp_px, "stop_px": stop_px}) print(f" {x['tag']:<7}{half} 币 · 止损 {stop_px} · " @@ -600,7 +607,23 @@ class Exec: log_trade({"ev": "opened", "key": r["key"], "legs": opened, "stop_px": stop_px}) + # 下单失败必须推。这类失败是**静默的经济损失**:日志里在报,但表现只是 + # "一直没开仓",看起来和"没信号"一样。实盘首日就因为这个白跑 5 小时—— + # 8 次下单全被 40774 拒掉而无人知道。所以推送不是可选的 + if failed: + self.n_order_fail += 1 + if not opened: + await tg.error( + f"{r['key']} 建仓全部失败,这个信号丢了", + "\n".join(failed) + + f"\n\n累计失败 {self.n_order_fail} 次。" + f"链路正常但下不了单——常见是下单体字段被拒" + f"(40774 持仓模式不匹配)、保证金不足、或该币被限制交易") + else: + await tg.error(f"{r['key']} 部分腿失败,收益结构已偏离设计", + "\n".join(failed)) if opened: + self.n_built += 1 self.execs[r["key"]] = { "sym": sym, "pair": pair, "hold": hold, "deadline": time.time() + MAXB * 60, "legs": opened, @@ -743,10 +766,19 @@ class Exec: await tg.day_rolled(*self.guard.pending_roll) self.guard.pending_roll = None n_open = sum(1 for v in self.execs.values() if v) + # 「已做 N 但建仓 0」是明确的故障,不能只把数字并排列出来让人自己 + # 看。首日那 5 小时的心跳里 "已做 4 · 在场 0/3" 一直在打,但没有 + # 任何一处说这是异常 + bad = "" + if self.n_took and not self.n_built: + bad = f" ⛔ 做了 {self.n_took} 笔却一次都没建上,下单被拒" + elif self.n_order_fail: + bad = f" ⚠ 有 {self.n_order_fail} 次下单失败" print(f" [心跳] 见信号 {self.n_seen} · 已做 {self.n_took} · " - f"跳过 {self.n_skip} · 在场 {n_open}/{MAX_OPEN} · " + f"建仓 {self.n_built} · 跳过 {self.n_skip} · " + f"在场 {n_open}/{MAX_OPEN} · " f"当日 {self.guard.n_day}/{MAX_DAY} 笔 · " - f"当日 PnL {self.guard.pnl_day:+.2f}/-{MAX_DAY_LOSS}", + f"当日 PnL {self.guard.pnl_day:+.2f}/-{MAX_DAY_LOSS}{bad}", flush=True) async def shutdown(self) -> None: