下单失败必须推 Telegram,并让心跳判读「做了却没建上」

实盘首日丢掉 4 个信号、白跑 5 小时,根因是 40774,但**没有任何推送**。
日志里一直在报,可外在表现和"没信号"一模一样——这正是整套通知设计要防的
那类静默经济损失,却恰好漏了下单失败这一条。

现在:入场被拒或止盈挂不上都推。按信号聚合成一条,不按腿推(避免一个信号
两条)。两腿全失败时说明"这个信号丢了"并带累计失败次数与常见原因;部分腿
失败时说明"收益结构已偏离设计"——那种情况仓位是半的,不是设计的两腿结构。

心跳原先只把数字并排列出来:"已做 4 · 在场 0/3" 那 5 小时一直在打,但没有
一处说这是异常。现在分开计 n_built 与 n_order_fail,做了却一次没建上直接
打 。只看 n_took 分不出"做了"和"建上了"。

status.sh 同样加判读:统计 entry_fail 次数并打出最后一条错误与码表。

tp_fail 现在也记进 live_trades.jsonl,原先只打日志不落盘。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jack
2026-08-29 00:48:56 +08:00
co-authored by Cursor
parent 77056a8318
commit 3bbd92784c
2 changed files with 48 additions and 3 deletions
+13
View File
@@ -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
+35 -3
View File
@@ -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: