"""把 1030ms 归因到具体环节。 前面的测量已经排除两个可能: · Bitget 换根首条推送就是 update,Hummingbot 没有因丢弃 snapshot 而等待 · 容器内同进程里,Hummingbot 的 feed 只比原始 WS 慢 2~6ms,处理开销可忽略 剩下的变量是「位置」和「客户端」。本脚本对同一批 K 线三方对齐: host_ccxt 宿主机 ccxt.pro watch_ohlcv latency_ccxt.csv host_raw 宿主机 原始 aiohttp WS probe_raw_host.csv cont_raw 容器内 原始 aiohttp WS probe_hb_vs_raw.csv cont_hb 容器内 Hummingbot candles feed probe_hb_vs_raw.csv 据此可分离两件事: cont_raw − host_raw 容器网络的代价(同一份代码,只换位置) host_ccxt − host_raw ccxt.pro 客户端的差异(同一位置,只换客户端) .venv/bin/python research/live/latency_attribute.py """ from __future__ import annotations from pathlib import Path import numpy as np import pandas as pd OUT = Path(__file__).resolve().parents[1] / "out" SYMS = ("BTC", "ETH", "SOL") def load() -> pd.DataFrame | None: parts = [] f = OUT / "latency_ccxt.csv" if f.exists(): d = pd.read_csv(f) if not d.empty: parts.append(d[["kline_ts", "sym", "t_data_ms"]] .rename(columns={"t_data_ms": "host_ccxt"})) f = OUT / "probe_raw_host.csv" if f.exists(): d = pd.read_csv(f) if not d.empty: parts.append(d[["kline_ts", "sym", "raw_ms"]] .rename(columns={"raw_ms": "host_raw"})) f = OUT / "probe_hb_vs_raw.csv" if f.exists(): d = pd.read_csv(f) if not d.empty: parts.append(d[["kline_ts", "sym", "raw_ms", "hb_ms"]] .rename(columns={"raw_ms": "cont_raw", "hb_ms": "cont_hb"})) if not parts: return None m = parts[0] for p in parts[1:]: m = m.merge(p, on=["kline_ts", "sym"], how="outer") return m def main() -> None: m = load() if m is None or m.empty: print("四路数据均缺失") return cols = [c for c in ("host_ccxt", "host_raw", "cont_raw", "cont_hb") if c in m.columns] print(f"各路样本数(重叠前):") for c in cols: print(f" {c}: {int(m[c].notna().sum())}") print("\n########## 各路 t_data − t_close(ms,中位)##########") print(f"{'币':<5}" + "".join(f"{c:>11}" for c in cols)) for s in SYMS: g = m[m["sym"] == s] line = f"{s:<5}" for c in cols: v = (g[c] - g["kline_ts"]).dropna() line += f"{np.median(v):>11.0f}" if len(v) else f"{'—':>11}" print(line) # 只在四路都有的 K 线上做差,避免不同子集的中位数互相错位 full = m.dropna(subset=cols) print(f"\n########## 归因(仅四路齐全的 {len(full)} 根)##########") if full.empty: print(" 无四路齐全的 K 线;检查三个采集窗口是否重叠") return pairs = [] if "cont_raw" in cols and "host_raw" in cols: pairs.append(("容器网络代价", "cont_raw", "host_raw")) if "host_ccxt" in cols and "host_raw" in cols: pairs.append(("ccxt.pro 客户端差异", "host_ccxt", "host_raw")) if "cont_hb" in cols and "cont_raw" in cols: pairs.append(("Hummingbot 处理开销", "cont_hb", "cont_raw")) if "cont_hb" in cols and "host_ccxt" in cols: pairs.append(("合计:容器 HB vs 宿主 ccxt", "cont_hb", "host_ccxt")) print(f"{'环节':<26}" + "".join(f"{s:>9}" for s in SYMS)) for name, a, b in pairs: line = f"{name:<26}" for s in SYMS: g = full[full["sym"] == s] if g.empty: line += f"{'—':>9}" continue line += f"{np.median(g[a] - g[b]):>9.0f}" print(line) print("\n(单位 ms,正数表示前者更慢)") if __name__ == "__main__": main()