"""对比两个(或多个)采集站点的数据差异。 ## 该比什么(2026-08-28 实测修正) 原以为该比地理位置,实测下来不是。腾讯云新加坡的延迟构成: 数据到达 506ms 其中网络仅 2ms 往返(ws.bitget.com 是 CloudFront 边缘) 信号计算 646ms 本机 2 核,三币同时收盘还要排队 合计 1315ms 换机房只能动那 2ms。**主指标是 compute_ms,不是 lag_data_ms。** lag_data_ms 仍然要看,但作用是**自检**:两站应当几乎相同;若差很多,先怀疑 时钟没对齐——那比网络差异的可能性大得多。 ## 判读前必须先看的两件事 1. **时钟。** 两台机器的时钟偏移差多少,延迟对比就凭空差多少,且不报错。 run_meta_*.json 里有各站启动时的 chrony 偏移,先确认都在 10ms 内。 2. **同期。** 只比两站都有数据的那些 kline_ts。不取交集的话,比的可能是 不同时段的市场状态,而延迟对市场活跃度是敏感的。 ## 用法 把各站的 research/out/ 收到一处(文件名相同会覆盖,所以先按站点改名或 分目录放),然后: python research/live/compare_sites.py --glob 'collected/*/shadow_latency.csv' """ from __future__ import annotations import argparse import glob import json from pathlib import Path import numpy as np import pandas as pd def load(patterns: list[str]) -> pd.DataFrame: paths: list[str] = [] for p in patterns: paths.extend(sorted(glob.glob(p))) if not paths: raise SystemExit(f"没有匹配到文件:{patterns}") frames = [] for p in paths: df = pd.read_csv(p) if "site" not in df.columns: raise SystemExit( f"{p} 没有 site 列。这是 2026-08-28 之前采的旧数据," f"无法确定来源,不能用于跨地对比") df["_src"] = p frames.append(df) out = pd.concat(frames, ignore_index=True) print(f"读入 {len(paths)} 个文件、{len(out):,} 行、" f"站点 {sorted(out['site'].unique())}") return out def show_meta(out_dirs: list[Path]) -> None: print("\n########## 一、运行元数据 ##########") metas = [] for d in out_dirs: metas.extend(sorted(d.glob("run_meta_*.json"))) if not metas: print(" 没找到 run_meta_*.json。时钟偏移与代码版本无法核对——") print(" 两站数据若有差异,分不清是地理位置还是环境不同造成的") return rows = [] for m in metas: try: rows.append(json.loads(m.read_text())) except Exception as e: print(f" {m.name} 读取失败:{e!r}") if not rows: return df = pd.DataFrame(rows) # net 是嵌套 dict,摊平出关心的几项 if "net" in df.columns: for k in ("icmp_min_ms", "tcp_connect_min_ms", "ttfb_min_ms", "self_org", "edge_org"): df[k] = df["net"].apply( lambda v, k=k: v.get(k) if isinstance(v, dict) else None) keep = [c for c in ("site", "clock_offset_ms", "nproc", "cpu_model", "icmp_min_ms", "ttfb_min_ms", "git_commit", "git_dirty", "image_digest", "mem_gb", "tz", "started_utc") if c in df.columns] print(df[keep].to_string(index=False)) if "icmp_min_ms" in df and df["icmp_min_ms"].notna().any(): x = df["icmp_min_ms"].astype(float) print(f"\n 到 Bitget 边缘的往返:{x.min():.1f}~{x.max():.1f}ms。" f"站间极差 {x.max() - x.min():.1f}ms 就是换机房的全部空间") if "clock_offset_ms" in df and df["clock_offset_ms"].notna().any(): o = df["clock_offset_ms"].astype(float) spread = float(o.max() - o.min()) flag = "" if spread < 5 else " ⚠ 这个差会直接叠加到延迟对比上" print(f"\n 站点间时钟偏移极差 {spread:.3f}ms{flag}") if "git_commit" in df and df["git_commit"].nunique() > 1: print(" ⚠ 各站代码版本不同,差异可能来自代码而非地理位置") if "git_dirty" in df and df["git_dirty"].any(): print(" ⚠ 有站点带未提交改动,无法复现") def compare_latency(df: pd.DataFrame, col: str = "lag_data_ms") -> None: """延迟对比。只取各站都有的 kline_ts,避免比到不同时段。""" if col not in df.columns: print(f"\n没有 {col} 列") return sites = sorted(df["site"].unique()) if len(sites) < 2: print(f"\n只有一个站点({sites[0]}),无从对比。" f"等第二台机器的数据到齐") return label = {"compute_ms": "信号计算耗时(主指标,取决于 CPU)", "lag_data_ms": "数据到达延迟(自检项,两站应当接近)", "lag_signal_ms": "合计到可下单"}.get(col, col) print(f"\n########## {label}({col}) ##########") print("\n 全量(各站各自的样本,时段可能不同)") for s in sites: x = df[df["site"] == s][col].dropna().astype(float) print(f" {s:<16} n={len(x):>6} 中位 {x.median():>7.0f}ms " f"P90 {np.percentile(x, 90):>7.0f}ms " f"P99 {np.percentile(x, 99):>7.0f}ms") # 取交集:同一根 K 线在各站都有记录 key = ["sym", "kline_ts"] piv = df.pivot_table(index=key, columns="site", values=col, aggfunc="first") both = piv.dropna() if both.empty: print("\n 各站没有共同的 K 线。可能是采集时段不重叠,") print(" 或 kline_ts 对不上(先查两站时区与时钟)") return print(f"\n 同根对比({len(both):,} 根 K 线,各站都有)") for s in sites: x = both[s].astype(float) print(f" {s:<16} 中位 {x.median():>7.0f}ms " f"P90 {np.percentile(x, 90):>7.0f}ms") base = sites[0] for s in sites[1:]: d = (both[s] - both[base]).astype(float) # 配对差的符号检验:同根配对消掉了市场状态,比两个中位数相减干净 n_pos = int((d > 0).sum()) print(f"\n {s} − {base}:中位差 {d.median():+.0f}ms " f"· 均值差 {d.mean():+.0f}ms") print(f" {s} 更慢的根占 {n_pos / len(d) * 100:.1f}%" f"(50% 表示无系统性差异)") for sym in sorted(both.index.get_level_values("sym").unique()): ds = d.xs(sym, level="sym") print(f" {sym:<5} 中位差 {ds.median():+7.0f}ms (n={len(ds)})") def compare_drift(patterns: list[str]) -> None: """漂移对比。延迟差若能兑换成漂移差,才是钱上的差别。""" paths: list[str] = [] for p in patterns: paths.extend(sorted(glob.glob(p))) if not paths: return frames = [] for p in paths: d = pd.read_csv(p) if "site" in d.columns: frames.append(d) if not frames: return df = pd.concat(frames, ignore_index=True) if df["site"].nunique() < 2: return print("\n########## 三、延迟漂移(无条件,每根都记) ##########") for label in sorted(df["delay_label"].dropna().unique()): sub = df[df["delay_label"] == label] line = f" {label:>7}" for s in sorted(sub["site"].unique()): x = sub[sub["site"] == s]["drift_bp_long"].dropna().astype(float) if len(x): line += f" · {s} {x.abs().median():.3f}bp(n={len(x)})" print(line) print("\n 同一个固定延迟点上,两站的漂移应当几乎相同——漂移是市场性质,") print(" 与机器位置无关。若差异明显,先查时钟与采集时段是否对齐") def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--glob", action="append", default=None, help="shadow_latency.csv 的路径模式,可给多次") ap.add_argument("--drift-glob", action="append", default=None) ap.add_argument("--meta-dir", action="append", default=None) a = ap.parse_args() lat = a.glob or ["research/out/shadow_latency.csv", "collected/*/shadow_latency.csv"] drf = a.drift_glob or ["research/out/shadow_drift.csv", "collected/*/shadow_drift.csv"] metas = [Path(p) for p in (a.meta_dir or ["research/out", "collected"])] show_meta([p for p in metas if p.is_dir()]) df = load(lat) # compute_ms 是主指标:它是延迟里唯一有大幅改善空间的一项(646ms vs # 网络的 2ms)。lag_data_ms 放后面,作用是自检两站是否可比 compare_latency(df, "compute_ms") compare_latency(df, "lag_data_ms") compare_latency(df, "lag_signal_ms") compare_drift(drf) if __name__ == "__main__": main()