杠杆只影响占用保证金,**不改**名义敞口、手续费、滑点、盈亏绝对值——费与滑点 都按名义额收。所以「用杠杆所以可以很小额」这个方向要说清:杠杆省的是本金 占用,不是成本。 它真正的用处是让「抬名义额」几乎免费,而抬名义额正好压掉步长取整。实测 取整到步长偶数倍后最差币的名义偏差:100U → 6.7%(SOL)、500U → 1.8%、 1000U → 0.6%。所以默认名义改 500、杠杆 10x,每笔占 50 USDT 保证金。 止损在 2 ATR ≈ 0.2%,而 10x 的强平约需逆向 10% = 100 个 ATR,差 50 倍。 这个止损紧到杠杆几乎不引入强平风险,所以这里没有「杠杆换风险」的取舍。 仍建议逐仓,让每笔最大损失被保证金封住。 推送里加上占用保证金与「触止损亏多少 USDT / 占保证金百分之几」,手工执行时 不必自己换算。 Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
6.2 KiB
Bash
Executable File
143 lines
6.2 KiB
Bash
Executable File
#!/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}"
|
||
# lean 模式让 TF_DF 只构建到中枢,跳过线段/走势中枢/MACD 状态机。等价性由
|
||
# verify_lean_parity.py 在影子这条路径上逐根验过(180 窗口 / 90 命中零分歧)。
|
||
# 设 0 可退回 full,用来复量两模式的耗时差。
|
||
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)。
|
||
SYMS="${SYMS:-BTC,ETH,SOL}"
|
||
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"
|
||
|
||
say "量网络距离"
|
||
netprobe="$(bash "$(dirname "${BASH_SOURCE[0]}")/netprobe.sh" 2>/dev/null || echo '{}')"
|
||
echo "$netprobe" | grep -E 'edge_org|self_org|self_city|icmp_min_ms|tcp_connect' || true
|
||
|
||
# 运行元数据。两地数据对不上时,先看这个文件——镜像摘要、代码版本、时钟偏移
|
||
# 三者任一不同都足以解释差异,不必去猜。网络探测并在这里,因为「换机房能省
|
||
# 多少」这个问题只有它能回答(到达延迟里网络只占约 2ms,用它比等于用公斤秤称克)。
|
||
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),
|
||
"cpu_model": "$(awk -F': ' '/model name/{print $2; exit}' /proc/cpuinfo 2>/dev/null || echo unknown)",
|
||
"mem_gb": $(free -g | awk '/^Mem:/{print $2}'),
|
||
"net": $netprobe
|
||
}
|
||
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" \
|
||
-e SHADOW_LEAN="$SHADOW_LEAN" \
|
||
-e SHADOW_INCR="${SHADOW_INCR:-1}" \
|
||
-e TG_TOKEN="${TG_TOKEN:-}" \
|
||
-e TG_CHAT="${TG_CHAT:-}" \
|
||
-e TG_NOTIONAL="${TG_NOTIONAL:-500}" \
|
||
-e TG_LEVERAGE="${TG_LEVERAGE:-10}" \
|
||
-e TG_STALE_S="${TG_STALE_S:-90}" \
|
||
-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" --syms "$SYMS" >/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
|