整点在线推 Telegram,并带上搬运管道的新鲜度

执行器活着不代表链路活着——搬运死了一样心跳正常、一样什么都不做。
所以每小时那条必须读 ship_alive.json:ssh 是否在连、文件是否还在刷。
连上/断开立刻落盘,不靠 5 分钟心跳,否则第一轮会误报上游断了。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jack
2026-08-29 00:57:11 +08:00
co-authored by Cursor
parent 3bbd92784c
commit 080ff7d20e
6 changed files with 152 additions and 7 deletions
+24
View File
@@ -109,6 +109,9 @@ STALE_S = float(os.environ.get("LIVE_STALE_S", "20"))
# 盯交易所侧出场的轮询间隔。10s 足够:出场后要做的只是记账与放开 MAX_OPEN
# 名额,不涉及下单时效。太密会白耗 API 配额
WATCH_S = float(os.environ.get("LIVE_WATCH_S", "10"))
# 整点在线推送的间隔(分钟),0 关掉。60 分钟 = 24 条/天,和成交推送量级相当
# 不会淹掉真事。调到 5 以下没意义:日志心跳就是 5 分钟一次
TG_HB_MIN = float(os.environ.get("TG_HB_MIN", "60"))
STATE = Path(os.environ.get("LIVE_STATE", LIVE_HOME / "state" / "live_state.json"))
TRADES = Path(os.environ.get("LIVE_TRADES", LIVE_HOME / "state" / "live_trades.jsonl"))
@@ -257,6 +260,10 @@ class Exec:
# 建仓成功/失败分开计。只看 n_took 分不出"做了但下单被拒"——首日
# 那 5 小时里 n_took=4 而实际一笔都没建上
self.n_built = self.n_order_fail = 0
self.t0 = time.time()
# 置 0 让第一次 5 分钟心跳就推,不必等满一个周期:重启后最该尽早确认
# 的是「上游也通」,而这个只有在线那条带得出来
self.tg_hb_at = 0.0
# ── 启动 ──────────────────────────────────────────────────────
async def start(self) -> None:
@@ -757,9 +764,26 @@ class Exec:
continue
self.execs.pop(key, None)
def ship_state(self) -> dict | None:
"""读搬运器落的存活文件。读不到返回 None——那本身就是要报的事。"""
try:
p = self.bus.parent / "ship_alive.json"
with open(p, encoding="utf-8") as f:
return json.load(f)
except Exception: # noqa: BLE001
return None
async def heartbeat(self) -> None:
while True:
await asyncio.sleep(300)
if TG_HB_MIN and time.time() - self.tg_hb_at >= TG_HB_MIN * 60:
self.tg_hb_at = time.time()
await tg.alive(time.time() - self.t0, self.n_seen,
self.n_built, self.n_took,
sum(1 for v in self.execs.values() if v),
MAX_OPEN, self.guard.n_day, MAX_DAY,
self.guard.pnl_day, MAX_DAY_LOSS,
self.n_order_fail, self.ship_state(), self.dry)
# 跨日结算是 roll() 里同步留下的,在这里发出去
self.guard.roll()
if self.guard.pending_roll: