生产与研究分家:实盘执行器独立成 live/ 子树,加 AWS 部署

实盘要跑在 AWS(API key 绑了 IP 白名单),而信号在新加坡那台算。借这次
把生产从研究侧摘出来,四条具体代价里第一条已经咬过:

1. live_state.json 原先落在 research/out/,而那里 shadow_hb 会自动 rename
   归档、研究脚本会写、人也手工清过。那文件装的是 MAX_DAY_LOSS 累计与已
   处理信号键,被清掉不报错,只是两道闸静默失效。改到 LIVE_HOME。
2. 采集器十币清空 300~560ms 直接叠在信号到达执行器的延迟上。
3. 研究侧探针 OOM 过一次(14.9GB),当时若有仓位在场会连坐执行器。
4. 为读两个常量 import 研究侧 step43,把 numpy/pandas/pyarrow 拖进实盘
   进程。抽出 stdlib-only 的 live/exit_params.py,install.sh 加断言挡回归。

新增 live/ship_signals.py:AWS 侧 ssh tail 拉总线,每次重连从文件头重放
+ 按幂等键去重,断线期间的信号自愈;旧信号由 staleness 闸挡掉不补做。
带时钟倒流检测——两机时钟不同步会让那道闸静默放宽。

部署件:systemd 两单元(搬运挂了执行器仍管在场仓位的超时平仓)、
install.sh、dryrun.sh(验密钥/白名单/时钟/ssh/取整)、status.sh、README。

验证:live_exec 重构后端到端空跑,SOL 多头与 ADA 空头的止损/两级止盈/
数量取整逐项核对正确,isolated + post_only + reduceOnly 都在;搬运的去重、
重启不重复追加、脏数据跳过、断线重连重放均已测。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jack
2026-08-28 17:03:03 +08:00
co-authored by Cursor
parent af029bc26e
commit 335d891478
16 changed files with 884 additions and 15 deletions
+20 -5
View File
@@ -20,11 +20,14 @@ SHADOW_LEAN="${SHADOW_LEAN:-1}"
# 币池。默认三个流动性最好的做滑点测量;十币池是实际要交易的那批(TRX 剔除,
# ATR 门控几乎全刷掉)。
#
# 币数超过核数时排队会成为主项:所有币同一秒收盘2 核上十币实测清空要
# 约 640ms,最后一个币的信号落在 1376ms。此时**加 worker 没用**——CPU 密集
# 的活,worker 超过核数不增吞吐,只会把等待从 queue_ms 挪到 inner_ms。
# 唯一出路是压单币耗时,走 init_stream/append_bar 增量路径(实测约 3.7x
# 换算后十币 / 2 核清空约 265ms)。
# 币数超过核数时排队会成为主项:所有币同一秒收盘。此时**加 worker 没用**
# ——CPU 密集的活,worker 超过核数不增吞吐,只会把等待从 queue_ms 挪到
# inner_ms。增量路径(SHADOW_INCR=1,默认开)已把清空压到 300~560ms。
#
# 再往下压的顺序见 HANDOFF §5.72(口径对齐后的实测):单币 inner 100ms ≈
# 信号链 30ms + 追加 2.81 根 37ms + 2 核争抢 33ms。争抢只有 1.49x,所以
# **加核收益有限**;最便宜的一刀是按币绑定 worker(现在 symbol 随机落
# worker,每份缓存都漏掉对方处理过的根,于是人人要追 2.81 根而非 1 根)。
SYMS="${SYMS:-BTC,ETH,SOL}"
IMAGE="${SHADOW_IMAGE:-hummingbot/hummingbot:latest}"
HOURS="${HOURS:-168}"
@@ -33,6 +36,10 @@ MAX_OFFSET_MS="${MAX_OFFSET_MS:-10}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
OUT="$REPO_ROOT/research/out"
# 信号总线的宿主机目录。**刻意放在仓库外**:这是交给实盘执行器(另一台机)的
# 交接点,而仓库会被 git checkout/clean 动。执行器那台用 ssh tail 拉这个文件,
# 所以它也不能在容器内部,必须挂出来
BUS_DIR="${BUS_DIR:-$HOME/chan-live/state}"
die() { printf '\033[31m错误:%s\033[0m\n' "$*" >&2; exit 1; }
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
@@ -42,6 +49,12 @@ say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
say "站点 $SHADOW_SITE"
mkdir -p "$BUS_DIR"
# 容器内是 root,写出来的总线文件宿主机上归 root。执行器那台用普通用户
# ssh 过来 tail,所以目录要可进入、文件要可读
chmod 755 "$BUS_DIR" 2>/dev/null || true
echo "信号总线:$BUS_DIR/signals_live.jsonl(容器内挂成 /bus"
say "校验时钟同步"
offset_ms=""
if command -v chronyc >/dev/null 2>&1; then
@@ -119,8 +132,10 @@ docker run -d --name "$NAME" -w /home/hummingbot \
-e TG_NOTIONAL="${TG_NOTIONAL:-500}" \
-e TG_LEVERAGE="${TG_LEVERAGE:-10}" \
-e TG_STALE_S="${TG_STALE_S:-90}" \
-e SIGNAL_BUS=/bus/signals_live.jsonl \
-v "$REPO_ROOT:/repo:ro" \
-v "$OUT:/out" \
-v "$BUS_DIR:/bus" \
--entrypoint /opt/conda/envs/hummingbot/bin/python \
"$IMAGE" /repo/research/live/shadow_hb.py \
--hours "$HOURS" --workers "$WORKERS" --syms "$SYMS" >/dev/null