Files
Chan/live/deploy/status.sh
T
jackandCursor 080ff7d20e 整点在线推 Telegram,并带上搬运管道的新鲜度
执行器活着不代表链路活着——搬运死了一样心跳正常、一样什么都不做。
所以每小时那条必须读 ship_alive.json:ssh 是否在连、文件是否还在刷。
连上/断开立刻落盘,不靠 5 分钟心跳,否则第一轮会误报上游断了。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 00:57:11 +08:00

117 lines
4.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "搬运存活文件"
if [[ -f "$STATE/ship_alive.json" ]]; then
python3 - "$STATE/ship_alive.json" <<'PY'
import json, sys, time
d = json.load(open(sys.argv[1]))
age = time.time() - d.get("ts", 0)
up = d.get("up_s", 0)
conn = d.get("connected")
if age > 720:
state = f"⛔ 已停更 {age/60:.0f} 分钟,进程可能死了"
elif conn is False:
state = "⛔ ssh 已断开,正在重连"
elif conn is True:
state = f"ssh 在线 {up/60:.0f} 分钟"
else:
state = "文件是旧格式(没有 connected),重启搬运后才会有"
print(f" {state} · 重连 {d.get('n_reconnect', 0)} 次 · 新增 {d.get('n_new', 0)} 条")
PY
else
echo " 还没有 ship_alive.json(搬运没起来,或还是没落盘的旧版本)"
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 " 无"