实盘首日丢掉 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>
95 lines
3.8 KiB
Bash
Executable File
95 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 生产机一屏体检。不改任何状态,随时可跑。
|
||
set -uo pipefail
|
||
|
||
APP=/opt/chan
|
||
STATE=/var/lib/chan-live/state
|
||
CONF=/etc/chan-live/live.env
|
||
|
||
hr() { printf '─── %s\n' "$1"; }
|
||
|
||
hr "服务"
|
||
for u in chan-live-ship chan-live-exec; do
|
||
act="$(systemctl is-active "$u" 2>/dev/null)"
|
||
since="$(systemctl show -p ActiveEnterTimestamp --value "$u" 2>/dev/null)"
|
||
nrs="$(systemctl show -p NRestarts --value "$u" 2>/dev/null)"
|
||
printf ' %-16s %-8s 自 %s · 重启 %s 次\n' \
|
||
"$u" "$act" "${since:-?}" "${nrs:-0}"
|
||
done
|
||
|
||
hr "时钟(staleness 闸依赖它)"
|
||
if command -v chronyc >/dev/null; then
|
||
chronyc tracking 2>/dev/null | grep -E "Reference ID|System time" | sed 's/^/ /'
|
||
else
|
||
echo " ⚠ 没装 chrony"
|
||
fi
|
||
|
||
hr "信号总线"
|
||
bus="${SIGNAL_BUS:-$STATE/signals_live.jsonl}"
|
||
[[ -f "$CONF" ]] && bus="$(grep -E '^SIGNAL_BUS=' "$CONF" | tail -1 | cut -d= -f2-)"
|
||
bus="${bus:-$STATE/signals_live.jsonl}"
|
||
if [[ -s "$bus" ]]; then
|
||
n="$(wc -l < "$bus")"
|
||
last_ts="$(tail -1 "$bus" | grep -o '"kline_ts":[0-9]*' | cut -d: -f2)"
|
||
if [[ -n "$last_ts" ]]; then
|
||
age=$(( $(date +%s) - last_ts / 1000 ))
|
||
printf ' %s 条 · 最近一条 %d 分钟前\n' "$n" "$((age / 60))"
|
||
else
|
||
echo " $n 条(最后一行没有 kline_ts)"
|
||
fi
|
||
# 信号 6.8 个/天,所以「几小时没有」是正常的。真要看的是搬运连着没有
|
||
echo " 注:6.8 个/天,长时间没有新信号是正常的;要判健康看下面的搬运心跳"
|
||
else
|
||
echo " 空或不存在:$bus"
|
||
fi
|
||
|
||
hr "闸的状态"
|
||
# 在场仓位**不落盘**:重启时由 reconcile 查交易所并平掉(见 live_exec.py
|
||
# 的 reconcile 注释)。所以这里只报当日计数,仓位要看交易所或下面的成交流
|
||
if [[ -f "$STATE/live_state.json" ]]; then
|
||
python3 - "$STATE/live_state.json" <<'PY'
|
||
import json, sys, time
|
||
d = json.load(open(sys.argv[1]))
|
||
today = time.strftime("%Y-%m-%d")
|
||
day = d.get("day", "?")
|
||
stale = "" if day == today else f" ⚠ 是 {day} 的,跨日后首次开仓时才归零"
|
||
print(f" 当日 {day}{stale}")
|
||
pnl, n = d.get("pnl_day", 0.0), d.get("n_day", 0)
|
||
# pnl 恒为 0 而又确实开过仓,说明 watch() 没在记账 → MAX_DAY_LOSS 是死的
|
||
warn = " ⚠ 开过仓但盈亏仍为 0,查 watch() 是否在跑" if n and pnl == 0 else ""
|
||
print(f" 已开 {n} 笔 · 盈亏 {pnl:+.2f} USDT{warn}")
|
||
print(f" 已处理信号键 {len(d.get('done', []))} 个(幂等去重用,留最近 5000)")
|
||
PY
|
||
else
|
||
echo " 还没有状态文件(没开过仓)"
|
||
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
|
||
|
||
hr "搬运心跳(最近 3 条)"
|
||
journalctl -u chan-live-ship -n 200 --no-pager 2>/dev/null \
|
||
| grep -F "[心跳]" | tail -3 | sed 's/^/ /' \
|
||
|| echo " 还没有心跳(每 5 分钟一条)"
|
||
|
||
hr "最近报错"
|
||
journalctl -u chan-live-exec -u chan-live-ship -n 400 --no-pager -p warning 2>/dev/null \
|
||
| tail -8 | sed 's/^/ /' || echo " 无"
|