加跨地部署脚本与站点对比,数据全表加 site 列

目的是在第二台机器(AWS)上跑一套完全相同的采集,看不同地理位置的数据差异。
滑点里最大的一项是延迟漂移,而延迟含网络传输,所以机房选址是可优化参数。

数据侧两处必需改动:

- 全部输出加 site 列(三张 CSV 加 gzip 里的盘口与成交流)。没有这一列,两台
  机器的数据合起来就分不清来源。为免四处 writerow 漏加一处产生静默空值,
  改在 _SiteWriter 里统一注入。
- start.sh 在时钟未同步或偏移超 10ms 时**拒绝启动**。所有延迟数字都是
  「本地时钟 − 交易所 K 线收盘」,时钟偏 50ms 就全部同向偏 50ms,且不报错,
  只会让跨地对比得出一个干净且完全错误的结论。

deploy/ 下四个文件:setup.sh(docker + chrony + 拉镜像)、start.sh(校验时钟、
写运行元数据、起容器)、status.sh(健康速查)、README。运行元数据记 git commit、
镜像摘要、时钟偏移——两地数据对不上时,这三项任一不同都足以解释差异。

compare_sites.py 做配对对比:只取各站都有的 K 线(不取交集可能在比不同时段,
而延迟对市场活跃度敏感),并报配对差的符号占比而非两个中位数相减。已用注入
120ms 的合成数据验证能精确还原。另有一条自检:同一固定延迟点上两站漂移应当
相同——漂移是市场性质,若也差很多则先查时钟与时段对齐。

status.sh 里按列名取字段下标而非写死数字:加 site 列时字段整体右移过一次,
写死 $8 会静默变成读 lag_signal_ms 而非 lag_data_ms。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jack
2026-08-28 02:39:47 +08:00
co-authored by Cursor
parent 01c4a4ab4d
commit 631d97e493
6 changed files with 580 additions and 6 deletions
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# 启动影子采集器。跑之前先 setup.sh。
#
# SHADOW_SITE=aws-tokyo bash research/live/deploy/start.sh
# SHADOW_SITE=aws-tokyo HOURS=168 WORKERS=2 bash research/live/deploy/start.sh
#
# 为什么 SHADOW_SITE 必填:它写进每一行数据,是两台机器的数据合起来之后
# 唯一的来源区分。缺了就只能靠文件路径猜,一合并就分不清了。
#
# 为什么时钟不同步就拒绝启动:所有延迟数字都是「本地时钟 − 交易所 K 线收盘
# 时间戳」。时钟偏 50ms,全部延迟就凭空偏 50ms,而这**不会有任何报错**,
# 只会让跨地对比得出一个完全错误的结论。这类静默错误必须在启动就挡掉。
set -euo pipefail
NAME="${NAME:-shadow}"
IMAGE="${SHADOW_IMAGE:-hummingbot/hummingbot:latest}"
HOURS="${HOURS:-168}"
WORKERS="${WORKERS:-2}"
MAX_OFFSET_MS="${MAX_OFFSET_MS:-10}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
OUT="$REPO_ROOT/research/out"
die() { printf '\033[31m错误:%s\033[0m\n' "$*" >&2; exit 1; }
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
[[ -n "${SHADOW_SITE:-}" ]] || die "必须设 SHADOW_SITE,例如 SHADOW_SITE=aws-tokyo。
它写进每一行数据,是跨地对比时区分来源的唯一依据。"
say "站点 $SHADOW_SITE"
say "校验时钟同步"
offset_ms=""
if command -v chronyc >/dev/null 2>&1; then
if ! chronyc tracking >/dev/null 2>&1; then
die "chrony 没在跑。先 sudo systemctl start chrony(或 chronyd"
fi
# System time 那行形如 "0.000058703 seconds fast of NTP time"
line="$(chronyc tracking | grep '^System time' || true)"
secs="$(awk '{print $4}' <<<"$line")"
offset_ms="$(awk -v s="$secs" 'BEGIN{printf "%.3f", s*1000}')"
echo "$line"
leap="$(chronyc tracking | awk -F': *' '/Leap status/{print $2}')"
[[ "$leap" == "Normal" ]] || die "chrony leap status = $leap,尚未收敛。等几分钟再试"
over="$(awk -v o="$offset_ms" -v m="$MAX_OFFSET_MS" 'BEGIN{print (o>m)?1:0}')"
[[ "$over" == "0" ]] || die "时钟偏移 ${offset_ms}ms 超过阈值 ${MAX_OFFSET_MS}ms。
所有延迟测量都会同向偏这么多且不报错,跨地对比会得出错误结论。
先等 chrony 收敛,或调 MAX_OFFSET_MS(不建议)。"
echo "偏移 ${offset_ms}ms,在 ${MAX_OFFSET_MS}ms 阈值内"
elif command -v timedatectl >/dev/null 2>&1; then
timedatectl | grep -qi 'synchronized: yes' \
|| die "系统时钟未同步。装 chrony:见 setup.sh"
echo "timedatectl 报已同步(无 chronyc,拿不到具体偏移)"
else
die "既无 chronyc 也无 timedatectl,无法确认时钟。装 chrony 后再启动"
fi
say "检查镜像"
docker image inspect "$IMAGE" >/dev/null 2>&1 || die "没有镜像 $IMAGE,先跑 setup.sh"
digest="$(docker image inspect "$IMAGE" --format '{{if .RepoDigests}}{{index .RepoDigests 0}}{{end}}' 2>/dev/null || true)"
say "停掉旧容器"
docker rm -f "$NAME" >/dev/null 2>&1 || true
mkdir -p "$OUT"
# 运行元数据。两地数据对不上时,先看这个文件——镜像摘要、代码版本、时钟偏移
# 三者任一不同都足以解释差异,不必去猜。
meta="$OUT/run_meta_${SHADOW_SITE}.json"
cat >"$meta" <<EOF
{
"site": "$SHADOW_SITE",
"hostname": "$(hostname)",
"started_utc": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"tz": "$(date +%Z%z)",
"clock_offset_ms": ${offset_ms:-null},
"git_commit": "$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)",
"git_dirty": $(git -C "$REPO_ROOT" diff --quiet 2>/dev/null && echo false || echo true),
"image": "$IMAGE",
"image_digest": "${digest:-unknown}",
"hours": $HOURS,
"workers": $WORKERS,
"kernel": "$(uname -r)",
"nproc": $(nproc),
"mem_gb": $(free -g | awk '/^Mem:/{print $2}')
}
EOF
echo "元数据已写 $meta"
say "启动容器 $NAME"
docker run -d --name "$NAME" -w /home/hummingbot \
--restart unless-stopped \
-e PYTHONPATH=/home/hummingbot:/repo/research:/repo/research/live:/repo \
-e SHADOW_SITE="$SHADOW_SITE" \
-v "$REPO_ROOT:/repo:ro" \
-v "$OUT:/out" \
--entrypoint /opt/conda/envs/hummingbot/bin/python \
"$IMAGE" /repo/research/live/shadow_hb.py \
--hours "$HOURS" --workers "$WORKERS" >/dev/null
echo "已启动。等启动自检(补丁断言 + 历史回填,约 60 秒)…"
sleep 45
docker logs "$NAME" 2>&1 | tail -12
cat <<EOF
看日志: docker logs -f $NAME
看状态: bash research/live/deploy/status.sh
停止: docker rm -f $NAME
启动日志里应能看到:
[补丁] 覆盖生效 —— 换根解析补丁有效(缺了会静默慢 1.06 秒)
成交流已挂 [...] —— maker 成交率要用
就绪 … 根 —— 历史回填完成
EOF